Skip to content

Commit

Permalink
Create a JS version of Page List for the editor (#31670)
Browse files Browse the repository at this point in the history
* Create a JS version of Page List for the editor

* Fix lint errors and remove setAttributes() calls

* Split page fetching into a hook

* Add navigation submenu markup

* Use per_page=-1

* Sort by menu_order,post_title

* Add back 'No pages to show' message

* Fix component not updating after fetch

* Remove TODOs

* Remove unused context attributes

* Update snapshot

* Fix typo in comment

* Remove unnecessary isNavigationChild checks

* Access navigation settings via context, not attributes

* Add loading state

* Memoize PageItems

Co-authored-by: Robert Anderson <[email protected]>
  • Loading branch information
georgeh and noisysocks authored Oct 25, 2021
1 parent e6c91b9 commit fd5d963
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 173 deletions.
33 changes: 0 additions & 33 deletions packages/block-library/src/page-list/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,6 @@
"keywords": [ "menu", "navigation" ],
"textdomain": "default",
"attributes": {
"textColor": {
"type": "string"
},
"customTextColor": {
"type": "string"
},
"backgroundColor": {
"type": "string"
},
"customBackgroundColor": {
"type": "string"
},
"overlayBackgroundColor": {
"type": "string"
},
"customOverlayBackgroundColor": {
"type": "string"
},
"overlayTextColor": {
"type": "string"
},
"customOverlayTextColor": {
"type": "string"
},
"isNavigationChild": {
"type": "boolean"
},
"showSubmenuIcon": {
"type": "boolean"
},
"openSubmenusOnClick" : {
"type": "boolean"
}
},
"usesContext": [
"textColor",
Expand Down
281 changes: 168 additions & 113 deletions packages/block-library/src/page-list/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,138 +2,58 @@
* External dependencies
*/
import classnames from 'classnames';
import { sortBy } from 'lodash';

/**
* WordPress dependencies
*/
import {
BlockControls,
useBlockProps,
store as blockEditorStore,
getColorClassName,
} from '@wordpress/block-editor';
import ServerSideRender from '@wordpress/server-side-render';
import { ToolbarButton } from '@wordpress/components';
import { ToolbarButton, Placeholder, Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { useEffect, useState, memo } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';

/**
* Internal dependencies
*/
import ConvertToLinksModal from './convert-to-links-modal';
import { ItemSubmenuIcon } from '../navigation-link/icons';

// We only show the edit option when page count is <= MAX_PAGE_COUNT
// Performance of Navigation Links is not good past this value.
const MAX_PAGE_COUNT = 100;

export default function PageListEdit( {
context,
clientId,
attributes,
setAttributes,
} ) {
// Copy context to attributes to make it accessible in the editor's
// ServerSideRender
useEffect( () => {
const {
textColor,
customTextColor,
backgroundColor,
customBackgroundColor,
overlayTextColor,
customOverlayTextColor,
overlayBackgroundColor,
customOverlayBackgroundColor,
} = context;
setAttributes( {
textColor,
customTextColor,
backgroundColor,
customBackgroundColor,
overlayTextColor,
customOverlayTextColor,
overlayBackgroundColor,
customOverlayBackgroundColor,
} );
}, [
context.textColor,
context.customTextColor,
context.backgroundColor,
context.customBackgroundColor,
context.overlayTextColor,
context.customOverlayTextColor,
context.overlayBackgroundColor,
context.customOverlayBackgroundColor,
] );

const { textColor, backgroundColor, style } = context || {};

const [ allowConvertToLinks, setAllowConvertToLinks ] = useState( false );

const blockProps = useBlockProps( {
className: classnames( {
'has-text-color': !! textColor,
[ getColorClassName( 'color', textColor ) ]: !! textColor,
'has-background': !! backgroundColor,
[ getColorClassName(
'background-color',
backgroundColor
) ]: !! backgroundColor,
} ),
style: { ...style?.color },
} );
export default function PageListEdit( { context, clientId } ) {
const { pagesByParentId, totalPages } = usePagesByParentId();

const isParentNavigation = useSelect(
( select ) => {
const { getBlockParentsByBlockName } = select( blockEditorStore );
return (
getBlockParentsByBlockName( clientId, 'core/navigation' )
.length > 0
);
},
[ clientId ]
);

useEffect( () => {
setAttributes( {
isNavigationChild: isParentNavigation,
openSubmenusOnClick: !! context.openSubmenusOnClick,
showSubmenuIcon: !! context.showSubmenuIcon,
} );
}, [ context.openSubmenusOnClick, context.showSubmenuIcon ] );

useEffect( () => {
if ( isParentNavigation ) {
apiFetch( {
path: addQueryArgs( '/wp/v2/pages', {
per_page: 1,
_fields: [ 'id' ],
} ),
parse: false,
} ).then( ( res ) => {
setAllowConvertToLinks(
res.headers.get( 'X-WP-Total' ) <= MAX_PAGE_COUNT
);
} );
} else {
setAllowConvertToLinks( false );
}
}, [ isParentNavigation ] );
const isNavigationChild = 'showSubmenuIcon' in context;
const allowConvertToLinks =
isNavigationChild && totalPages <= MAX_PAGE_COUNT;

const [ isOpen, setOpen ] = useState( false );
const openModal = () => setOpen( true );
const closeModal = () => setOpen( false );

// Update parent status before component first renders.
const attributesWithParentStatus = {
...attributes,
isNavigationChild: isParentNavigation,
openSubmenusOnClick: !! context.openSubmenusOnClick,
showSubmenuIcon: !! context.showSubmenuIcon,
};
const blockProps = useBlockProps( {
className: classnames( 'wp-block-page-list', {
'has-text-color': !! context.textColor,
[ getColorClassName(
'color',
context.textColor
) ]: !! context.textColor,
'has-background': !! context.backgroundColor,
[ getColorClassName(
'background-color',
context.backgroundColor
) ]: !! context.backgroundColor,
} ),
style: { ...context.style?.color },
} );

return (
<>
Expand All @@ -150,15 +70,150 @@ export default function PageListEdit( {
clientId={ clientId }
/>
) }
<div { ...blockProps }>
<ServerSideRender
block="core/page-list"
attributes={ attributesWithParentStatus }
EmptyResponsePlaceholder={ () => (
<span>{ __( 'Page List: No pages to show.' ) }</span>
) }
/>
</div>
{ totalPages === null && (
<div { ...blockProps }>
<Placeholder>
<Spinner />
</Placeholder>
</div>
) }
{ totalPages === 0 && (
<div { ...blockProps }>
<span>{ __( 'Page List: No pages to show.' ) }</span>
</div>
) }
{ totalPages > 0 && (
<ul { ...blockProps }>
<PageItems
context={ context }
pagesByParentId={ pagesByParentId }
/>
</ul>
) }
</>
);
}

function usePagesByParentId() {
const [ pagesByParentId, setPagesByParentId ] = useState( null );
const [ totalPages, setTotalPages ] = useState( null );

useEffect( () => {
async function performFetch() {
setPagesByParentId( null );
setTotalPages( null );

let pages = await apiFetch( {
path: addQueryArgs( '/wp/v2/pages', {
orderby: 'menu_order',
order: 'asc',
_fields: [ 'id', 'link', 'parent', 'title', 'menu_order' ],
per_page: -1,
} ),
} );

// TODO: Once the REST API supports passing multiple values to
// 'orderby', this can be removed.
// https://core.trac.wordpress.org/ticket/39037
pages = sortBy( pages, [ 'menu_order', 'title.rendered' ] );

setPagesByParentId(
pages.reduce( ( accumulator, page ) => {
const { parent } = page;
if ( accumulator.has( parent ) ) {
accumulator.get( parent ).push( page );
} else {
accumulator.set( parent, [ page ] );
}
return accumulator;
}, new Map() )
);
setTotalPages( pages.length );
}
performFetch();
}, [] );

return {
pagesByParentId,
totalPages,
};
}

const PageItems = memo( function PageItems( {
context,
pagesByParentId,
parentId = 0,
depth = 0,
} ) {
const pages = pagesByParentId.get( parentId );

if ( ! pages?.length ) {
return [];
}

return pages.map( ( page ) => {
const hasChildren = pagesByParentId.has( page.id );
const isNavigationChild = 'showSubmenuIcon' in context;
return (
<li
key={ page.id }
className={ classnames( 'wp-block-pages-list__item', {
'has-child': hasChildren,
'wp-block-navigation-item': isNavigationChild,
'open-on-click': context.openSubmenusOnClick,
'open-on-hover-click':
! context.openSubmenusOnClick &&
context.showSubmenuIcon,
} ) }
>
{ hasChildren && context.openSubmenusOnClick ? (
<ItemSubmenuToggle title={ page.title?.rendered } />
) : (
<a
className={ classnames(
'wp-block-pages-list__item__link',
{
'wp-block-navigation-item__content': isNavigationChild,
}
) }
href={ page.link }
>
{ page.title?.rendered }
</a>
) }
{ hasChildren && (
<>
{ ! context.openSubmenusOnClick &&
context.showSubmenuIcon && <ItemSubmenuToggle /> }
<ul
className={ classnames( 'submenu-container', {
'wp-block-navigation__submenu-container': isNavigationChild,
} ) }
>
<PageItems
context={ context }
pagesByParentId={ pagesByParentId }
parentId={ page.id }
depth={ depth + 1 }
/>
</ul>
</>
) }
</li>
);
} );
} );

function ItemSubmenuToggle( { title } ) {
return (
<button
className="wp-block-navigation-item__content wp-block-navigation-submenu__toggle"
aria-expanded="false"
>
{ title }
<span className="wp-block-page-list__submenu-icon wp-block-navigation__submenu-icon">
<ItemSubmenuIcon />
</span>
</button>
);
}
Loading

0 comments on commit fd5d963

Please sign in to comment.