Skip to content
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

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function gutenberg_reregister_core_block_types() {
'post-excerpt.php' => 'core/post-excerpt',
'post-featured-image.php' => 'core/post-featured-image',
'post-tags.php' => 'core/post-tags',
'query.php' => 'core/query',
);

$registry = WP_Block_Type_Registry::get_instance();
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
@import "./paragraph/editor.scss";
@import "./post-excerpt/editor.scss";
@import "./pullquote/editor.scss";
@import "./query/editor.scss";
@import "./quote/editor.scss";
@import "./rss/editor.scss";
@import "./search/editor.scss";
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import * as postDate from './post-date';
import * as postExcerpt from './post-excerpt';
import * as postFeaturedImage from './post-featured-image';
import * as postTags from './post-tags';
import * as query from './query';

/**
* Function to register an individual block.
Expand Down Expand Up @@ -207,6 +208,7 @@ export const __experimentalRegisterExperimentalCoreBlocks =
postExcerpt,
postFeaturedImage,
postTags,
query,
]
: [] ),
].forEach( registerBlock );
Expand Down
45 changes: 45 additions & 0 deletions packages/block-library/src/query/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "core/query",
"category": "layout",
"attributes": {
"className": {
"type": "string"
},
"criteria": {
Copy link
Contributor

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?

"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
}
]
}
}
}
218 changes: 218 additions & 0 deletions packages/block-library/src/query/edit.js
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 ) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?
Why do we need all this complex logic?
Why are we forcing the "post" attribute on all blocks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to simplify this a bit using cloneBlock, but maybe needs some more reworking? The method's purpose is to take block changes from on Query result post and apply it to the rest of the results. It also saves the new block list in the attributes for use server-side.

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 );
5 changes: 5 additions & 0 deletions packages/block-library/src/query/editor.scss
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;
}
}
41 changes: 41 additions & 0 deletions packages/block-library/src/query/index.js
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,
};
Loading