From cbf425bc76d5797082cd63c8ecb4b6122f15910f Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 19 Oct 2018 02:45:35 +1100 Subject: [PATCH] Add Custom Fields and Advanced Panels to Options modal (#10676) * First pass at adding 'Advanced Panels' to Options modal Allows the user to enable or disable 'Advanced Panels' (AKA Meta Boxes) via the Options modal. --- docs/data/data-core-edit-post.md | 62 +++++++++++------ docs/reference/deprecated.md | 3 +- lib/meta-box-partial-page.php | 32 ++++++--- .../src/components/meta-boxes/index.js | 29 +++++--- .../meta-boxes/meta-box-visibility.js | 33 +++++++++ .../src/components/options-modal/index.js | 12 +++- packages/edit-post/src/store/actions.js | 56 +++++---------- packages/edit-post/src/store/effects.js | 16 +++-- packages/edit-post/src/store/reducer.js | 23 ++++--- packages/edit-post/src/store/selectors.js | 69 ++++++++++++++++--- packages/edit-post/src/store/test/reducer.js | 20 +++--- .../edit-post/src/store/test/selectors.js | 55 ++++++++++++--- 12 files changed, 288 insertions(+), 122 deletions(-) create mode 100644 packages/edit-post/src/components/meta-boxes/meta-box-visibility.js diff --git a/docs/data/data-core-edit-post.md b/docs/data/data-core-edit-post.md index 0c711665b345c..87ff8d8bb05a9 100644 --- a/docs/data/data-core-edit-post.md +++ b/docs/data/data-core-edit-post.md @@ -195,6 +195,19 @@ Returns an array of active meta box locations. Active meta box locations. +### isMetaBoxLocationVisible + +Returns true if a metabox location is active and visible + +*Parameters* + + * state: Post editor state. + * location: Meta box location to test. + +*Returns* + +Whether the meta box location is active and visible. + ### isMetaBoxLocationActive Returns true if there is an active meta box in the given location, or false @@ -209,6 +222,31 @@ otherwise. Whether the meta box location is active. +### getMetaBoxesPerLocation + +Returns the list of all the available meta boxes for a given location. + +*Parameters* + + * state: Global application state. + * location: Meta box location to test. + +*Returns* + +List of meta boxes. + +### getAllMetaBoxes + +Returns the list of all the available meta boxes. + +*Parameters* + + * state: Global application state. + +*Returns* + +List of meta boxes. + ### getMetaBox Returns the state of legacy meta boxes. @@ -326,30 +364,14 @@ Returns an action object used to toggle a plugin name flag. * pluginName: Plugin name. -### initializeMetaBoxState - -Returns an action object used to check the state of meta boxes at a location. - -This should only be fired once to initialize meta box state. If a meta box -area is empty, this will set the store state to indicate that React should -not render the meta box area. - -Example: metaBoxes = { side: true, normal: false }. - -This indicates that the sidebar has a meta box but the normal area does not. - -*Parameters* - - * metaBoxes: Whether meta box locations are active. - -### setActiveMetaBoxLocations +### setAvailableMetaBoxesPerLocation -Returns an action object used in signaling that the active meta box -locations have changed. +Returns an action object used in signaling +what Meta boxes are available in which location. *Parameters* - * locations: New active meta box locations. + * metaBoxesPerLocation: Meta boxes per location. ### requestMetaBoxUpdates diff --git a/docs/reference/deprecated.md b/docs/reference/deprecated.md index d7203f2edba18..8b064f1a5a2ce 100644 --- a/docs/reference/deprecated.md +++ b/docs/reference/deprecated.md @@ -12,7 +12,8 @@ Gutenberg's deprecation policy is intended to support backwards-compatibility fo - Writing resolvers as async generators has been removed. Use the controls plugin instead. - `wp.components.AccessibleSVG` component has been removed. Please use `wp.components.SVG` instead. - The `wp.editor.UnsavedChangesWarning` component no longer accepts a `forceIsDirty` prop. -- `initializeMetaBoxState` action (`core/edit-post`) has been removed. Use `setActiveMetaBoxLocations` action (`core/edit-post`) instead. +- `setActiveMetaBoxLocations` action (`core/edit-post`) has been removed. +- `initializeMetaBoxState` action (`core/edit-post`) has been removed. - `wp.editPost.initializeEditor` no longer returns an object. Use the `setActiveMetaBoxLocations` action (`core/edit-post`) in place of the existing object's `initializeMetaBoxes` function. - `setMetaBoxSavedData` action (`core/edit-post`) has been removed. - `getMetaBoxes` selector (`core/edit-post`) has been removed. Use `getActiveMetaBoxLocations` selector (`core/edit-post`) instead. diff --git a/lib/meta-box-partial-page.php b/lib/meta-box-partial-page.php index b6a7187813c5e..ad1a6805d327e 100644 --- a/lib/meta-box-partial-page.php +++ b/lib/meta-box-partial-page.php @@ -112,8 +112,8 @@ function gutenberg_filter_meta_boxes( $meta_boxes ) { $core_normal_meta_boxes = array( 'revisionsdiv', 'postexcerpt', - 'trackbacksdiv', 'postcustom', + 'trackbacksdiv', 'commentstatusdiv', 'commentsdiv', 'slugdiv', @@ -289,9 +289,9 @@ function the_gutenberg_metaboxes() { * * @param array $wp_meta_boxes Global meta box state. */ - $wp_meta_boxes = apply_filters( 'filter_gutenberg_meta_boxes', $wp_meta_boxes ); - $locations = array( 'side', 'normal', 'advanced' ); - $active_meta_box_locations = array(); + $wp_meta_boxes = apply_filters( 'filter_gutenberg_meta_boxes', $wp_meta_boxes ); + $locations = array( 'side', 'normal', 'advanced' ); + $priorities = array( 'high', 'sorted', 'core', 'default', 'low' ); // Render meta boxes. ?>
@@ -302,15 +302,11 @@ function the_gutenberg_metaboxes() { @@ -318,6 +314,22 @@ function the_gutenberg_metaboxes() { id ][ $location ][ $priority ]; + foreach ( $meta_boxes as $meta_box ) { + if ( ! empty( $meta_box['title'] ) ) { + $meta_boxes_per_location[ $location ][] = array( + 'id' => $meta_box['id'], + 'title' => $meta_box['title'], + ); + } + } + } + } + /** * Sadly we probably can not add this data directly into editor settings. * @@ -327,7 +339,7 @@ function the_gutenberg_metaboxes() { * this, and try to get this data to load directly into the editor settings. */ $script = 'window._wpLoadGutenbergEditor.then( function() { - wp.data.dispatch( \'core/edit-post\' ).setActiveMetaBoxLocations( ' . wp_json_encode( $active_meta_box_locations ) . ' ); + wp.data.dispatch( \'core/edit-post\' ).setAvailableMetaBoxesPerLocation( ' . wp_json_encode( $meta_boxes_per_location ) . ' ); } );'; wp_add_inline_script( 'wp-edit-post', $script ); diff --git a/packages/edit-post/src/components/meta-boxes/index.js b/packages/edit-post/src/components/meta-boxes/index.js index 01e59e0c53e5f..3f108b59ecef8 100644 --- a/packages/edit-post/src/components/meta-boxes/index.js +++ b/packages/edit-post/src/components/meta-boxes/index.js @@ -1,25 +1,36 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + /** * WordPress dependencies */ import { withSelect } from '@wordpress/data'; +import { Fragment } from '@wordpress/element'; /** * Internal dependencies */ import MetaBoxesArea from './meta-boxes-area'; +import MetaBoxVisibility from './meta-box-visibility'; -function MetaBoxes( { location, isActive } ) { - if ( ! isActive ) { - return null; - } - - return ; +function MetaBoxes( { location, isVisible, metaBoxes } ) { + return ( + + { map( metaBoxes, ( { id } ) => ( + + ) ) } + { isVisible && } + + ); } -export default withSelect( ( select, ownProps ) => { - const { isMetaBoxLocationActive } = select( 'core/edit-post' ); +export default withSelect( ( select, { location } ) => { + const { isMetaBoxLocationVisible, getMetaBoxesPerLocation } = select( 'core/edit-post' ); return { - isActive: isMetaBoxLocationActive( ownProps.location ), + metaBoxes: getMetaBoxesPerLocation( location ), + isVisible: isMetaBoxLocationVisible( location ), }; } )( MetaBoxes ); diff --git a/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js b/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js new file mode 100644 index 0000000000000..31bc0becf225f --- /dev/null +++ b/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; + +class MetaBoxVisibility extends Component { + componentDidMount() { + this.updateDOM(); + } + + componentDidUpdate( prevProps ) { + if ( this.props.isVisible !== prevProps.isVisible ) { + this.updateDOM(); + } + } + + updateDOM() { + const { id, isVisible } = this.props; + const element = document.getElementById( id ); + if ( element ) { + element.style.display = isVisible ? '' : 'none'; + } + } + + render() { + return null; + } +} + +export default withSelect( ( select, { id } ) => ( { + isVisible: select( 'core/edit-post' ).isEditorPanelEnabled( `meta-box-${ id }` ), +} ) )( MetaBoxVisibility ); diff --git a/packages/edit-post/src/components/options-modal/index.js b/packages/edit-post/src/components/options-modal/index.js index d9a133309bfdb..16342d851056c 100644 --- a/packages/edit-post/src/components/options-modal/index.js +++ b/packages/edit-post/src/components/options-modal/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get } from 'lodash'; +import { get, map } from 'lodash'; /** * WordPress dependencies @@ -20,7 +20,7 @@ import { EnablePublishSidebarOption, EnableTipsOption, EnablePanelOption } from const MODAL_NAME = 'edit-post/options'; -export function OptionsModal( { isModalActive, closeModal } ) { +export function OptionsModal( { isModalActive, closeModal, metaBoxes = [] } ) { if ( ! isModalActive ) { return null; } @@ -54,6 +54,13 @@ export function OptionsModal( { isModalActive, closeModal } ) { + { metaBoxes.length !== 0 && ( +
+ { map( metaBoxes, ( { title, id } ) => ( + + ) ) } +
+ ) } ); } @@ -61,6 +68,7 @@ export function OptionsModal( { isModalActive, closeModal } ) { export default compose( withSelect( ( select ) => ( { isModalActive: select( 'core/edit-post' ).isModalActive( MODAL_NAME ), + metaBoxes: select( 'core/edit-post' ).getAllMetaBoxes(), } ) ), withDispatch( ( dispatch ) => { return { diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index ef05f43097a14..12bfb1b8aa6a9 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { reduce } from 'lodash'; - /** * WordPress dependencies */ @@ -172,51 +167,38 @@ export function togglePinnedPluginItem( pluginName ) { }; } -/** - * Returns an action object used to check the state of meta boxes at a location. - * - * This should only be fired once to initialize meta box state. If a meta box - * area is empty, this will set the store state to indicate that React should - * not render the meta box area. - * - * Example: metaBoxes = { side: true, normal: false }. - * - * This indicates that the sidebar has a meta box but the normal area does not. - * - * @param {Object} metaBoxes Whether meta box locations are active. - * - * @return {Object} Action object. - */ -export function initializeMetaBoxState( metaBoxes ) { +export function initializeMetaBoxState() { deprecated( 'initializeMetaBoxState action (`core/edit-post`)', { - alternative: 'setActiveMetaBoxLocations', plugin: 'Gutenberg', version: '4.2', } ); + return { + type: 'DO_NOTHING', + }; +} - const locations = reduce( metaBoxes, ( result, isActive, location ) => { - if ( isActive ) { - result = result.concat( location ); - } - - return result; - }, [] ); - - return setActiveMetaBoxLocations( locations ); +export function setActiveMetaBoxLocations() { + deprecated( 'setActiveMetaBoxLocations action (`core/edit-post`)', { + plugin: 'Gutenberg', + version: '4.2', + } ); + return { + type: 'DO_NOTHING', + }; } /** - * Returns an action object used in signaling that the active meta box - * locations have changed. + * Returns an action object used in signaling + * what Meta boxes are available in which location. * - * @param {string[]} locations New active meta box locations. + * @param {Object} metaBoxesPerLocation Meta boxes per location. * * @return {Object} Action object. */ -export function setActiveMetaBoxLocations( locations ) { +export function setAvailableMetaBoxesPerLocation( metaBoxesPerLocation ) { return { - type: 'SET_ACTIVE_META_BOX_LOCATIONS', - locations, + type: 'SET_META_BOXES_PER_LOCATIONS', + metaBoxesPerLocation, }; } diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index 26c41915d22c6..a03043bbe1f1f 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -28,12 +28,7 @@ import { getMetaBoxContainer } from '../utils/meta-boxes'; import { onChangeListener } from './utils'; const effects = { - SET_ACTIVE_META_BOX_LOCATIONS( action, store ) { - const hasActiveMetaBoxes = action.locations.length > 0; - if ( ! hasActiveMetaBoxes ) { - return; - } - + SET_META_BOXES_PER_LOCATIONS( action, store ) { // Allow toggling metaboxes panels // We need to wait for all scripts to load // If the meta box loads the post script, it will already trigger this. @@ -52,9 +47,16 @@ const effects = { subscribe( () => { const isSavingPost = select( 'core/editor' ).isSavingPost(); const isAutosavingPost = select( 'core/editor' ).isAutosavingPost(); + const hasActiveMetaBoxes = select( 'core/edit-post' ).hasMetaBoxes(); // Save metaboxes on save completion when past save wasn't an autosave. - const shouldTriggerMetaboxesSave = wasSavingPost && ! wasAutosavingPost && ! isSavingPost && ! isAutosavingPost; + const shouldTriggerMetaboxesSave = ( + hasActiveMetaBoxes && + wasSavingPost && + ! wasAutosavingPost && + ! isSavingPost && + ! isAutosavingPost + ); // Save current state for next inspection. wasSavingPost = isSavingPost; diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index b65a2895df5c6..95067632740e6 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -185,29 +185,32 @@ export function isSavingMetaBoxes( state = false, action ) { } /** - * Reducer returning an array of active meta box locations after the given - * action. + * Reducer keeping track of the meta boxes per location. * - * @param {boolean} state Previous state. - * @param {Object} action Action Object. + * @param {boolean} state Previous state. + * @param {Object} action Action Object. * - * @return {string[]} Updated state. + * @return {Object} Updated state. */ -export function activeMetaBoxLocations( state = [], action ) { +export function metaBoxLocations( state = {}, action ) { switch ( action.type ) { - case 'SET_ACTIVE_META_BOX_LOCATIONS': - return action.locations; + case 'SET_META_BOXES_PER_LOCATIONS': + return action.metaBoxesPerLocation; } return state; } +const metaBoxes = combineReducers( { + isSaving: isSavingMetaBoxes, + locations: metaBoxLocations, +} ); + export default combineReducers( { preferences, activeGeneralSidebar, panel, activeModal, publishSidebarActive, - activeMetaBoxLocations, - isSavingMetaBoxes, + metaBoxes, } ); diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 72b2af7332687..0008d6a70990c 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -1,12 +1,13 @@ /** - * WordPress dependencies + * External dependencies */ -import deprecated from '@wordpress/deprecated'; +import createSelector from 'rememo'; +import { get, includes, some, flatten, values } from 'lodash'; /** - * External dependencies + * WordPress dependencies */ -import { get, includes } from 'lodash'; +import deprecated from '@wordpress/deprecated'; /** * Returns the current editing mode. @@ -222,8 +223,31 @@ export function getMetaBoxes( state ) { * * @return {string[]} Active meta box locations. */ -export function getActiveMetaBoxLocations( state ) { - return state.activeMetaBoxLocations; +export const getActiveMetaBoxLocations = createSelector( + ( state ) => { + return Object.keys( state.metaBoxes.locations ) + .filter( ( location ) => isMetaBoxLocationActive( state, location ) ); + }, + ( state ) => [ + state.metaBoxes.locations, + ] +); + +/** + * Returns true if a metabox location is active and visible + * + * @param {Object} state Post editor state. + * @param {string} location Meta box location to test. + * + * @return {boolean} Whether the meta box location is active and visible. + */ +export function isMetaBoxLocationVisible( state, location ) { + return ( + isMetaBoxLocationActive( state, location ) && + some( getMetaBoxesPerLocation( state, location ), ( { id } ) => { + return isEditorPanelEnabled( state, `meta-box-${ id }` ); + } ) + ); } /** @@ -236,9 +260,38 @@ export function getActiveMetaBoxLocations( state ) { * @return {boolean} Whether the meta box location is active. */ export function isMetaBoxLocationActive( state, location ) { - return getActiveMetaBoxLocations( state ).includes( location ); + const metaBoxes = getMetaBoxesPerLocation( state, location ); + return !! metaBoxes && metaBoxes.length !== 0; } +/** + * Returns the list of all the available meta boxes for a given location. + * + * @param {Object} state Global application state. + * @param {string} location Meta box location to test. + * + * @return {?Array} List of meta boxes. + */ +export function getMetaBoxesPerLocation( state, location ) { + return state.metaBoxes.locations[ location ]; +} + +/** + * Returns the list of all the available meta boxes. + * + * @param {Object} state Global application state. + * + * @return {Array} List of meta boxes. + */ +export const getAllMetaBoxes = createSelector( + ( state ) => { + return flatten( values( state.metaBoxes.locations ) ); + }, + ( state ) => [ + state.metaBoxes.locations, + ] +); + /** * Returns the state of legacy meta boxes. * @@ -276,5 +329,5 @@ export function hasMetaBoxes( state ) { * @return {boolean} Whether the metaboxes are being saved. */ export function isSavingMetaBoxes( state ) { - return state.isSavingMetaBoxes; + return state.metaBoxes.isSaving; } diff --git a/packages/edit-post/src/store/test/reducer.js b/packages/edit-post/src/store/test/reducer.js index 6b99cc4523f46..c64d90f48d155 100644 --- a/packages/edit-post/src/store/test/reducer.js +++ b/packages/edit-post/src/store/test/reducer.js @@ -12,7 +12,7 @@ import { activeGeneralSidebar, activeModal, isSavingMetaBoxes, - activeMetaBoxLocations, + metaBoxLocations, } from '../reducer'; describe( 'state', () => { @@ -291,22 +291,26 @@ describe( 'state', () => { } ); } ); - describe( 'activeMetaBoxLocations()', () => { + describe( 'metaBoxLocations()', () => { it( 'should return default state', () => { - const state = activeMetaBoxLocations( undefined, {} ); + const state = metaBoxLocations( undefined, {} ); - expect( state ).toEqual( [] ); + expect( state ).toEqual( {} ); } ); it( 'should set the active meta box locations', () => { const action = { - type: 'SET_ACTIVE_META_BOX_LOCATIONS', - locations: [ 'normal' ], + type: 'SET_META_BOXES_PER_LOCATIONS', + metaBoxesPerLocation: { + normal: [ 'postcustom' ], + }, }; - const state = activeMetaBoxLocations( undefined, action ); + const state = metaBoxLocations( undefined, action ); - expect( state ).toEqual( [ 'normal' ] ); + expect( state ).toEqual( { + normal: [ 'postcustom' ], + } ); } ); } ); } ); diff --git a/packages/edit-post/src/store/test/selectors.js b/packages/edit-post/src/store/test/selectors.js index 3e3888f88ef6d..cd70c00d02f32 100644 --- a/packages/edit-post/src/store/test/selectors.js +++ b/packages/edit-post/src/store/test/selectors.js @@ -367,7 +367,11 @@ describe( 'selectors', () => { describe( 'hasMetaBoxes', () => { it( 'should return true if there are active meta boxes', () => { const state = { - activeMetaBoxLocations: [ 'side' ], + metaBoxes: { + locations: { + side: [ 'postcustom' ], + }, + }, }; expect( hasMetaBoxes( state ) ).toBe( true ); @@ -375,7 +379,11 @@ describe( 'selectors', () => { it( 'should return false if there are no active meta boxes', () => { const state = { - activeMetaBoxLocations: [], + metaBoxes: { + locations: { + side: [], + }, + }, }; expect( hasMetaBoxes( state ) ).toBe( false ); @@ -385,7 +393,10 @@ describe( 'selectors', () => { describe( 'isSavingMetaBoxes', () => { it( 'should return true if some meta boxes are saving', () => { const state = { - isSavingMetaBoxes: true, + metaBoxes: { + isSaving: true, + locations: {}, + }, }; expect( isSavingMetaBoxes( state ) ).toBe( true ); @@ -393,7 +404,10 @@ describe( 'selectors', () => { it( 'should return false if no meta boxes are saving', () => { const state = { - isSavingMetaBoxes: false, + metaBoxes: { + isSaving: false, + locations: {}, + }, }; expect( isSavingMetaBoxes( state ) ).toBe( false ); @@ -403,7 +417,11 @@ describe( 'selectors', () => { describe( 'getMetaBoxes', () => { it( 'should return the state of all meta boxes', () => { const state = { - activeMetaBoxLocations: [ 'normal', 'side' ], + metaBoxes: { + locations: { + normal: [ 'postcustom' ], + }, + }, }; const result = getMetaBoxes( state ); @@ -417,7 +435,7 @@ describe( 'selectors', () => { isActive: false, }, side: { - isActive: true, + isActive: false, }, } ); } ); @@ -426,7 +444,11 @@ describe( 'selectors', () => { describe( 'getMetaBox', () => { it( 'should return the state of selected meta box', () => { const state = { - activeMetaBoxLocations: [ 'side' ], + metaBoxes: { + locations: { + side: [ 'postcustom' ], + }, + }, }; const result = getMetaBox( state, 'side' ); @@ -441,7 +463,12 @@ describe( 'selectors', () => { describe( 'getActiveMetaBoxLocations', () => { it( 'should return the active meta boxes', () => { const state = { - activeMetaBoxLocations: [ 'side' ], + metaBoxes: { + locations: { + side: [ 'postcustom' ], + normal: [], + }, + }, }; const result = getActiveMetaBoxLocations( state, 'side' ); @@ -453,7 +480,11 @@ describe( 'selectors', () => { describe( 'isMetaBoxLocationActive', () => { it( 'should return false if not active', () => { const state = { - activeMetaBoxLocations: [], + metaBoxes: { + locations: { + side: [], + }, + }, }; const result = isMetaBoxLocationActive( state, 'side' ); @@ -463,7 +494,11 @@ describe( 'selectors', () => { it( 'should return true if active', () => { const state = { - activeMetaBoxLocations: [ 'side' ], + metaBoxes: { + locations: { + side: [ 'postcustom' ], + }, + }, }; const result = isMetaBoxLocationActive( state, 'side' );