-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The Query Block allows users to create a search on the site and choose blocks to display content from the matching posts. This allows users to highlight specific content or areas of their sites.
- Loading branch information
George Hotelling
committed
Feb 7, 2020
1 parent
525dd41
commit 0b95943
Showing
10 changed files
with
851 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "core/query", | ||
"category": "layout" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import QueryPanel from './query-panel'; | ||
import { STORE_NAMESPACE } from './store'; | ||
|
||
/** | ||
* 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, createBlock } from '@wordpress/blocks'; | ||
import { PanelBody, Placeholder, Spinner } from '@wordpress/components'; | ||
import { compose } from '@wordpress/compose'; | ||
import { EntityProvider } from '@wordpress/core-data'; | ||
import { withSelect, withDispatch } from '@wordpress/data'; | ||
|
||
const defaultFields = [ | ||
'core/post-title', | ||
'core/post-date', | ||
'core/post-author', | ||
'core/post-excerpt', | ||
]; | ||
|
||
class Edit extends Component { | ||
constructor( props ) { | ||
super( props ); | ||
this.state = { | ||
editingPost: null, | ||
blocksTree: {}, | ||
}; | ||
|
||
this.debouncedCreateBlockTree = debounce( | ||
this.createBlockTree.bind( this ), | ||
1000 | ||
); | ||
} | ||
|
||
componentDidMount() { | ||
this.createBlockTree(); | ||
this.updateBlocks( defaultFields.map( ( f ) => createBlock( f ) ) ); | ||
} | ||
|
||
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, { post } ) | ||
), | ||
} ), | ||
{} | ||
); | ||
this.setState( { blocksTree: newBlocksTree } ); | ||
} | ||
|
||
cleanBlock( block ) { | ||
const { name, isValid, attributes, innerBlocks } = block; | ||
return { | ||
name, | ||
attributes: { ...attributes, post: {} }, | ||
innerBlocks: innerBlocks.map( ( b ) => this.cleanBlock( b ) ), | ||
isValid, | ||
}; | ||
} | ||
|
||
updateBlocks( blocks, postId ) { | ||
const { setAttributes } = this.props; | ||
const { blocksTree } = this.state; | ||
const cleanBlocks = blocks.map( this.cleanBlock ); | ||
this.setState( | ||
{ | ||
blocksTree: { ...( blocksTree || [] ), [ postId ]: blocks }, | ||
editingPost: postId, | ||
}, | ||
() => { | ||
setAttributes( { blocks: cleanBlocks } ); | ||
this.debouncedCreateBlockTree(); | ||
} | ||
); | ||
} | ||
|
||
render() { | ||
const { | ||
attributes, | ||
className, | ||
query, | ||
setAttributes, | ||
clientId, | ||
postList, | ||
markPostsAsDisplayed, | ||
} = this.props; | ||
|
||
const { criteria } = attributes; | ||
|
||
const { editingPost, blocksTree } = this.state; | ||
const settings = {}; | ||
const classes = classNames( | ||
className, | ||
editingPost ? 'is-editing' : '' | ||
); | ||
markPostsAsDisplayed( clientId, query ); | ||
|
||
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.', | ||
'newspack-blocks' | ||
) } | ||
</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, | ||
authors, | ||
categories, | ||
tags, | ||
specificPosts, | ||
} = criteria; | ||
const queryCriteria = pickBy( | ||
isSpecificPostModeActive( criteria ) | ||
? { | ||
include: specificPosts, | ||
orderby: 'include', | ||
per_page: specificPosts.length, | ||
} | ||
: { | ||
per_page: perPage, | ||
categories, | ||
author: authors, | ||
tags, | ||
}, | ||
( value ) => ! isUndefined( value ) | ||
); | ||
return queryCriteria; | ||
}; | ||
|
||
export default compose( | ||
withSelect( ( select, props ) => { | ||
const { attributes, clientId } = props; | ||
const { criteria } = attributes; | ||
const queryCriteria = queryCriteriaFromAttributes( criteria ); | ||
|
||
if ( ! isSpecificPostModeActive( criteria ) ) { | ||
const postIdsToExclude = select( STORE_NAMESPACE ).previousPostIds( | ||
clientId | ||
); | ||
queryCriteria.exclude = postIdsToExclude.join( ',' ); | ||
} | ||
|
||
return { | ||
query: select( 'core' ).getEntityRecords( | ||
'postType', | ||
'post', | ||
queryCriteria | ||
), | ||
}; | ||
} ), | ||
withDispatch( ( dispatch, props ) => { | ||
const { attributes } = props; | ||
const { criteria } = attributes; | ||
const markPostsAsDisplayed = isSpecificPostModeActive( criteria ) | ||
? dispatch( STORE_NAMESPACE ).markSpecificPostsAsDisplayed | ||
: dispatch( STORE_NAMESPACE ).markPostsAsDisplayed; | ||
|
||
return { | ||
markPostsAsDisplayed, | ||
}; | ||
} ) | ||
)( Edit ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Path, SVG } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { InnerBlocks } from '@wordpress/block-editor'; | ||
|
||
/** | ||
* 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, | ||
category: 'layout', | ||
keywords: [], | ||
description: __( 'A collection of posts.' ), | ||
attributes: { | ||
className: { | ||
type: 'string', | ||
}, | ||
criteria: { | ||
type: 'object', | ||
default: { | ||
per_page: 3, | ||
offset: 0, | ||
tags: [], | ||
categories: [], | ||
author: [], | ||
specificPosts: [], | ||
}, | ||
}, | ||
blocks: { | ||
type: 'array', | ||
default: [ | ||
{ | ||
isValid: true, | ||
clientId: null, | ||
name: 'post-title', | ||
attributes: {}, | ||
innerBlocks: [], | ||
}, | ||
], | ||
}, | ||
}, | ||
supports: { | ||
html: false, | ||
align: false, | ||
}, | ||
edit, | ||
save: () => <InnerBlocks.Content />, | ||
}; |
Oops, something went wrong.