-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add starter patterns to site editor new page #55117
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Modal } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { useMemo } from '@wordpress/element'; | ||
import { useSelect } from '@wordpress/data'; | ||
import { useAsyncList } from '@wordpress/compose'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { store as blockEditorStore } from '../../store'; | ||
import { __experimentalBlockPatternsList as BlockPatternsList } from '../'; | ||
|
||
function useStarterPatterns( postType, rootClientId ) { | ||
// A pattern is a start pattern if it includes 'core/post-content' in its blockTypes, | ||
// and it has no postTypes declared and the current post type is page or if | ||
// the current post type is part of the postTypes declared. | ||
const blockPatternsWithPostContentBlockType = useSelect( | ||
( select ) => | ||
select( blockEditorStore ).getPatternsByBlockTypes( | ||
'core/post-content', | ||
rootClientId | ||
), | ||
[ rootClientId ] | ||
); | ||
|
||
return useMemo( () => { | ||
// filter patterns without postTypes declared if the current postType is page | ||
// or patterns that declare the current postType in its post type array. | ||
return blockPatternsWithPostContentBlockType.filter( ( pattern ) => { | ||
return ( | ||
( postType === 'page' && ! pattern.postTypes ) || | ||
( Array.isArray( pattern.postTypes ) && | ||
pattern.postTypes.includes( postType ) ) | ||
); | ||
} ); | ||
}, [ postType, blockPatternsWithPostContentBlockType ] ); | ||
} | ||
|
||
export default function StarterPatternsModal( { | ||
onChoosePattern, | ||
onRequestClose, | ||
postType, | ||
rootClientId, | ||
} ) { | ||
const starterPatterns = useStarterPatterns( postType, rootClientId ); | ||
const shownStarterPatterns = useAsyncList( starterPatterns ); | ||
|
||
if ( starterPatterns.length === 0 ) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Modal | ||
title={ __( 'Choose a pattern' ) } | ||
isFullScreen | ||
onRequestClose={ onRequestClose } | ||
> | ||
<div className="block-editor-starter-patterns-modal__content"> | ||
<BlockPatternsList | ||
blockPatterns={ starterPatterns } | ||
shownPatterns={ shownStarterPatterns } | ||
onClickPattern={ onChoosePattern } | ||
/> | ||
</div> | ||
</Modal> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// 2 column masonry layout. | ||
.block-editor-starter-patterns-modal__content .block-editor-block-patterns-list { | ||
column-count: 2; | ||
column-gap: $grid-unit-30; | ||
|
||
@include break-medium() { | ||
column-count: 3; | ||
} | ||
|
||
@include break-wide() { | ||
column-count: 4; | ||
} | ||
|
||
.block-editor-block-patterns-list__list-item { | ||
break-inside: avoid-column; | ||
margin-bottom: $grid-unit-30; | ||
|
||
.block-editor-block-preview__container { | ||
min-height: 100px; | ||
} | ||
|
||
.block-editor-block-preview__content { | ||
width: 100%; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,108 +1,44 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Modal } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { useState, useMemo } from '@wordpress/element'; | ||
import { | ||
store as blockEditorStore, | ||
__experimentalBlockPatternsList as BlockPatternsList, | ||
} from '@wordpress/block-editor'; | ||
import { useSelect, useDispatch } from '@wordpress/data'; | ||
import { useAsyncList } from '@wordpress/compose'; | ||
import { store as editorStore } from '@wordpress/editor'; | ||
import { useState } from '@wordpress/element'; | ||
import { StarterPatternsModal } from '@wordpress/block-editor'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { store as editPostStore } from '../../store'; | ||
|
||
function useStartPatterns() { | ||
// A pattern is a start pattern if it includes 'core/post-content' in its blockTypes, | ||
// and it has no postTypes declared and the current post type is page or if | ||
// the current post type is part of the postTypes declared. | ||
const { blockPatternsWithPostContentBlockType, postType } = useSelect( | ||
( select ) => { | ||
const { getPatternsByBlockTypes } = select( blockEditorStore ); | ||
const { getCurrentPostType } = select( editorStore ); | ||
return { | ||
blockPatternsWithPostContentBlockType: | ||
getPatternsByBlockTypes( 'core/post-content' ), | ||
postType: getCurrentPostType(), | ||
}; | ||
}, | ||
[] | ||
); | ||
|
||
return useMemo( () => { | ||
// filter patterns without postTypes declared if the current postType is page | ||
// or patterns that declare the current postType in its post type array. | ||
return blockPatternsWithPostContentBlockType.filter( ( pattern ) => { | ||
return ( | ||
( postType === 'page' && ! pattern.postTypes ) || | ||
( Array.isArray( pattern.postTypes ) && | ||
pattern.postTypes.includes( postType ) ) | ||
); | ||
} ); | ||
}, [ postType, blockPatternsWithPostContentBlockType ] ); | ||
} | ||
|
||
function PatternSelection( { blockPatterns, onChoosePattern } ) { | ||
const shownBlockPatterns = useAsyncList( blockPatterns ); | ||
const { resetEditorBlocks } = useDispatch( editorStore ); | ||
return ( | ||
<BlockPatternsList | ||
blockPatterns={ blockPatterns } | ||
shownPatterns={ shownBlockPatterns } | ||
onClickPattern={ ( _pattern, blocks ) => { | ||
resetEditorBlocks( blocks ); | ||
onChoosePattern(); | ||
} } | ||
/> | ||
); | ||
} | ||
|
||
function StartPageOptionsModal( { onClose } ) { | ||
const startPatterns = useStartPatterns(); | ||
const hasStartPattern = startPatterns.length > 0; | ||
|
||
if ( ! hasStartPattern ) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Modal | ||
className="edit-post-start-page-options__modal" | ||
title={ __( 'Choose a pattern' ) } | ||
isFullScreen | ||
onRequestClose={ onClose } | ||
> | ||
<div className="edit-post-start-page-options__modal-content"> | ||
<PatternSelection | ||
blockPatterns={ startPatterns } | ||
onChoosePattern={ onClose } | ||
/> | ||
</div> | ||
</Modal> | ||
); | ||
} | ||
|
||
export default function StartPageOptions() { | ||
const [ isClosed, setIsClosed ] = useState( false ); | ||
const shouldEnableModal = useSelect( ( select ) => { | ||
const { isCleanNewPost } = select( editorStore ); | ||
const { resetEditorBlocks } = useDispatch( editorStore ); | ||
const { shouldEnableModal, postType } = useSelect( ( select ) => { | ||
const { isCleanNewPost, getCurrentPostType } = select( editorStore ); | ||
const { isEditingTemplate, isFeatureActive } = select( editPostStore ); | ||
|
||
return ( | ||
! isEditingTemplate() && | ||
! isFeatureActive( 'welcomeGuide' ) && | ||
isCleanNewPost() | ||
); | ||
return { | ||
shouldEnableModal: | ||
! isEditingTemplate() && | ||
! isFeatureActive( 'welcomeGuide' ) && | ||
isCleanNewPost(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's possible to replicate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The modal also showed again for me if the post did have content. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a play around with this and the only way I could get it to not load the modal when refreshing a draft page with content was by adding something like: const pageRecord = getEntityRecord(
'postType',
'page',
context?.postId
);
const hasContent = pageRecord?.content?.raw !== '';
if ( hasContent || ! isEditingPage || isWelcomeGuideOpen ) {
return { shouldOpenModal: false };
} I don't think it is a big issue for the modal to load again on a page refresh of a still empty page, but it might be worth stopping it for pages with content. But maybe I am missing something if you are only seeing it on empty page reloads? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We are protected against this, because a completely empty page cannot be saved. It needs to have a non-empty title, excerpt or content. See the |
||
postType: getCurrentPostType(), | ||
}; | ||
}, [] ); | ||
|
||
if ( ! shouldEnableModal || isClosed ) { | ||
return null; | ||
} | ||
|
||
return <StartPageOptionsModal onClose={ () => setIsClosed( true ) } />; | ||
return ( | ||
<StarterPatternsModal | ||
postType={ postType } | ||
onChoosePattern={ ( pattern, blocks ) => { | ||
resetEditorBlocks( blocks ); | ||
setIsClosed( true ); | ||
} } | ||
onRequestClose={ () => setIsClosed( true ) } | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
store as blockEditorStore, | ||
StarterPatternsModal, | ||
} from '@wordpress/block-editor'; | ||
import { useDispatch, useSelect } from '@wordpress/data'; | ||
import { useState } from '@wordpress/element'; | ||
import { store as preferencesStore } from '@wordpress/preferences'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { store as editSiteStore } from '../../store'; | ||
|
||
export default function StartPageOptions() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why duplicate this here. I thought that personally we should just move the whole component to the "editor" package (without extracting things to block-editor) and render the component directly within There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @youknowriad Back when I worked on this there were a bunch of inconsistencies between the editors (I don't think there was even a consistent to get the current page id), so I extracted all the common stuff to a new I won't have any bandwidth to work on this till post WordPress 6.5, so feel free to take over. |
||
const { replaceInnerBlocks } = useDispatch( blockEditorStore ); | ||
const [ isClosed, setIsClosed ] = useState( false ); | ||
const { shouldOpenModal, postType, rootClientId } = useSelect( | ||
( select ) => { | ||
const { hasPageContentFocus, getEditedPostContext } = | ||
select( editSiteStore ); | ||
const context = getEditedPostContext(); | ||
const isEditingPage = | ||
context?.postType === 'page' && | ||
context?.postId && | ||
hasPageContentFocus(); | ||
const isWelcomeGuideOpen = select( preferencesStore ).get( | ||
'core/edit-site', | ||
'welcomeGuide' | ||
); | ||
if ( ! isEditingPage || isWelcomeGuideOpen ) { | ||
return { shouldOpenModal: false }; | ||
} | ||
|
||
const { __experimentalGetGlobalBlocksByName, getBlock } = | ||
select( blockEditorStore ); | ||
const [ contentBlockClientId ] = | ||
__experimentalGetGlobalBlocksByName( 'core/post-content' ); | ||
if ( ! contentBlockClientId ) { | ||
return { shouldOpenModal: false }; | ||
} | ||
|
||
const contentBlock = getBlock( contentBlockClientId ); | ||
if ( contentBlock?.innerBlocks?.length ) { | ||
return { shouldOpenModal: false }; | ||
} | ||
|
||
return { | ||
shouldOpenModal: true, | ||
postType: context.postType, | ||
rootClientId: contentBlockClientId, | ||
}; | ||
}, | ||
[] | ||
); | ||
|
||
if ( isClosed || ! shouldOpenModal ) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<StarterPatternsModal | ||
postType={ postType } | ||
rootClientId={ rootClientId } | ||
onChoosePattern={ ( pattern, blocks ) => { | ||
setIsClosed( true ); | ||
const selectInsertedBlocks = true; | ||
replaceInnerBlocks( | ||
rootClientId, | ||
blocks, | ||
selectInsertedBlocks | ||
); | ||
} } | ||
onRequestClose={ () => setIsClosed( true ) } | ||
/> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should maybe make this one private.