-
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
Create Query Block #20106
Create Query Block #20106
Changes from all commits
fadaa6c
72edd01
1419bd6
361560d
4bbec33
f390c6a
291787c
3823255
699885e
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,45 @@ | ||
{ | ||
"name": "core/query", | ||
"category": "layout", | ||
"attributes": { | ||
"className": { | ||
"type": "string" | ||
}, | ||
"criteria": { | ||
"type": "object", | ||
"default": { | ||
"per_page": 3, | ||
"offset": 0, | ||
"tags": [], | ||
"categories": [], | ||
"author": [], | ||
"specificPosts": [] | ||
} | ||
}, | ||
"blocks": { | ||
"type": "array", | ||
"default": [ | ||
{ | ||
"name": "core/post-title", | ||
"innerBlocks": [], | ||
"isValid": true | ||
}, | ||
{ | ||
"name": "core/post-date", | ||
"innerBlocks": [], | ||
"isValid": true | ||
}, | ||
{ | ||
"name": "core/post-author", | ||
"innerBlocks": [], | ||
"isValid": true | ||
}, | ||
{ | ||
"name": "core/post-excerpt", | ||
"innerBlocks": [], | ||
"isValid": true | ||
} | ||
] | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import QueryPanel from './query-panel'; | ||
|
||
/** | ||
* External dependencies | ||
*/ | ||
import classNames from 'classnames'; | ||
import { debounce, isUndefined, pickBy } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
import { Component, Fragment } from '@wordpress/element'; | ||
import { | ||
BlockList, | ||
BlockEditorProvider, | ||
InspectorControls, | ||
WritingFlow, | ||
} from '@wordpress/block-editor'; | ||
import { cloneBlock } from '@wordpress/blocks'; | ||
import { PanelBody, Placeholder, Spinner } from '@wordpress/components'; | ||
import { compose } from '@wordpress/compose'; | ||
import { EntityProvider } from '@wordpress/core-data'; | ||
import { withSelect } from '@wordpress/data'; | ||
|
||
class Edit extends Component { | ||
youknowriad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
constructor( props ) { | ||
super( props ); | ||
this.state = { | ||
editingPost: null, | ||
blocksTree: {}, | ||
}; | ||
|
||
this.debouncedCreateBlockTree = debounce( | ||
this.createBlockTree.bind( this ), | ||
1000 | ||
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. While testing I noticed that my changes take a lot of time to propagate to the other blocks, Is this one-second delay necessary? 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. It seems to be, because changes to block content / attrs will propagate and interfere with the usability. The easiest way to see the problem is to remove the debounce, add a paragraph block inside the Query, and start typing. When you are typing, the block tree will keep getting updated and the updates will cause your typing to lag, and in some cases not register at all. |
||
); | ||
} | ||
|
||
componentDidMount() { | ||
this.createBlockTree(); | ||
} | ||
|
||
componentDidUpdate( prevProps ) { | ||
const { query } = this.props; | ||
if ( prevProps.query !== query ) { | ||
this.createBlockTree(); | ||
} | ||
} | ||
|
||
createBlockTree() { | ||
const { editingPost, blocksTree } = this.state; | ||
const { attributes, query } = this.props; | ||
const { blocks } = attributes; | ||
const newBlocksTree = ( query || [] ).reduce( | ||
( accumulator, post ) => ( { | ||
...accumulator, | ||
[ post.id ]: | ||
post.id === editingPost | ||
? blocksTree[ post.id ] | ||
: blocks.map( ( block ) => cloneBlock( block ) ), | ||
} ), | ||
{} | ||
); | ||
this.setState( { blocksTree: newBlocksTree } ); | ||
} | ||
|
||
updateBlocks( blocks, postId ) { | ||
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 was expecting this function to just save the "blocks" argument here as the "blocks" attribute? 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 was able to simplify this a bit using |
||
const { setAttributes } = this.props; | ||
const { blocksTree } = this.state; | ||
const cleanBlocks = blocks.map( ( block ) => cloneBlock( block ) ); | ||
this.setState( | ||
{ | ||
blocksTree: { ...( blocksTree || [] ), [ postId ]: blocks }, | ||
editingPost: postId, | ||
}, | ||
() => { | ||
setAttributes( { blocks: cleanBlocks } ); | ||
this.debouncedCreateBlockTree(); | ||
} | ||
); | ||
} | ||
|
||
render() { | ||
const { | ||
attributes, | ||
className, | ||
postList, | ||
query, | ||
setAttributes, | ||
settings, | ||
} = this.props; | ||
|
||
const { criteria } = attributes; | ||
|
||
const { editingPost, blocksTree } = this.state; | ||
const classes = classNames( | ||
className, | ||
editingPost ? 'is-editing' : '' | ||
); | ||
|
||
return ( | ||
<div className={ classes }> | ||
<InspectorControls> | ||
<PanelBody | ||
title={ __( 'Query Settings' ) } | ||
initialOpen={ true } | ||
> | ||
<QueryPanel | ||
criteria={ criteria } | ||
postList={ postList } | ||
onChange={ ( newCriteria ) => | ||
setAttributes( { criteria: newCriteria } ) | ||
} | ||
/> | ||
</PanelBody> | ||
</InspectorControls> | ||
<Fragment> | ||
{ ! query && ( | ||
<Placeholder> | ||
<Spinner /> | ||
</Placeholder> | ||
) } | ||
{ query && ! query.length && ( | ||
<Placeholder> | ||
{ __( 'Sorry, no posts were found.' ) } | ||
</Placeholder> | ||
) } | ||
{ query && | ||
!! query.length && | ||
query.map( ( post ) => { | ||
if ( ! blocksTree[ post.id ] ) return null; | ||
return ( | ||
<article | ||
className={ | ||
post.id === editingPost | ||
? 'is-editing' | ||
: '' | ||
} | ||
key={ post.id } | ||
> | ||
<EntityProvider | ||
kind="postType" | ||
type="post" | ||
id={ post.id } | ||
> | ||
<BlockEditorProvider | ||
value={ blocksTree[ post.id ] } | ||
onChange={ ( blocks ) => | ||
this.updateBlocks( | ||
blocks, | ||
post.id | ||
) | ||
} | ||
settings={ settings } | ||
> | ||
<WritingFlow> | ||
<BlockList /> | ||
</WritingFlow> | ||
</BlockEditorProvider> | ||
</EntityProvider> | ||
</article> | ||
); | ||
} ) } | ||
</Fragment> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
const isSpecificPostModeActive = ( { specificMode, specificPosts } ) => | ||
specificMode && specificPosts && specificPosts.length; | ||
|
||
const queryCriteriaFromAttributes = ( criteria ) => { | ||
const { | ||
per_page: perPage, | ||
author, | ||
categories, | ||
tags, | ||
specificPosts, | ||
} = criteria; | ||
const queryCriteria = pickBy( | ||
isSpecificPostModeActive( criteria ) | ||
? { | ||
include: specificPosts, | ||
orderby: 'include', | ||
per_page: specificPosts.length, | ||
} | ||
: { | ||
per_page: perPage, | ||
categories, | ||
author, | ||
tags, | ||
}, | ||
( value ) => ! isUndefined( value ) | ||
); | ||
return queryCriteria; | ||
}; | ||
|
||
export default compose( | ||
withSelect( ( select, props ) => { | ||
const { attributes } = props; | ||
const { criteria } = attributes; | ||
const queryCriteria = queryCriteriaFromAttributes( criteria ); | ||
|
||
return { | ||
query: select( 'core' ).getEntityRecords( | ||
'postType', | ||
'post', | ||
queryCriteria | ||
), | ||
settings: select( 'core/block-editor' ).getSettings(), | ||
}; | ||
} ) | ||
)( Edit ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.wp-block-query { | ||
.block-editor-writing-flow__click-redirect { | ||
display: none; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Path, SVG } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import metadata from './block.json'; | ||
import edit from './edit'; | ||
|
||
const { name } = metadata; | ||
export { metadata, name }; | ||
|
||
export const title = __( 'Query' ); | ||
|
||
/* From https://material.io/tools/icons */ | ||
export const icon = ( | ||
<SVG | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
> | ||
<Path d="M0 0h24v24H0z" fill="none" /> | ||
<Path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z" /> | ||
</SVG> | ||
); | ||
|
||
export const settings = { | ||
title, | ||
icon, | ||
keywords: [], | ||
description: __( 'A collection of posts.' ), | ||
supports: { | ||
html: false, | ||
align: false, | ||
}, | ||
edit, | ||
}; |
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.
After using this block for a few minutes, I found that there's a lot of work to be done to make sure the block works properly even without criteria/filtering...
I was wondering what do you think if we split this PR into two, one block without filtering where we'd only focus on how the template gets edited/updated and rendered. and the filtering can be easily added on a follow-up PR when the interactions are sorted-out?