Skip to content

Instantly share code, notes, and snippets.

@carlodaniele
Last active February 17, 2024 21:39
Show Gist options
  • Save carlodaniele/27e292fbbe4b897ca3bda4539dfd74df to your computer and use it in GitHub Desktop.
Save carlodaniele/27e292fbbe4b897ca3bda4539dfd74df to your computer and use it in GitHub Desktop.
An example Gutenberg dynamic block
<?php
/**
* Plugin Name: An example block for Kinsta readers
* Description: Businessperson
* Requires at least: 5.8
* Requires PHP: 7.0
* Version: 0.1.0
* Author: Carlo
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: author-plugin
*
* @package author-box
*/
function author_box_author_plugin_render_author_content( $attr ) {
$args = array(
'numberposts' => $attr['numberOfItems']
);
$my_posts = get_posts( $args );
if( ! empty( $my_posts ) ){
$output = '<div ' . get_block_wrapper_attributes() . '>';
if( $attr['displayAuthorInfo'] ){
$output .= '<div class="wp-block-author-box-author-plugin__author">';
if( $attr['showAvatar'] ){
$output .= '<div class="wp-block-author-box-author-plugin__avatar">'
. get_avatar( get_the_author_meta( 'ID' ), $attr['avatarSize'] )
. '</div>';
}
$output .= '<div class="wp-block-author-box-author-plugin__author-content">';
$output .= '<div class="wp-block-author-box-author-plugin__name">'
. get_the_author_meta( 'display_name' )
. '</div>';
if( $attr['showBio'] ){
$output .= '<div class="wp-block-author-box-author-plugin__description">'
. get_the_author_meta( 'description' )
. '</div>';
}
$output .= '</div>';
$output .= '</div>';
}
$num_cols = $attr['columns'] > 1 ? strval( $attr['columns'] ) : '1';
$output .= '<ul class="wp-block-author-box-author-plugin__post-items columns-' . $num_cols . '">';
foreach ( $my_posts as $p ){
$title = $p->post_title ? $p->post_title : 'Default title';
$url = esc_url( get_permalink( $p->ID ) );
$thumbnail = has_post_thumbnail( $p->ID ) ? get_the_post_thumbnail( $p->ID, 'large', array( 'class' => 'wp-block-author-box-author-plugin__post-thumbnail' ) ) : '';
$output .= '<li>';
if( ! empty( $thumbnail ) && $attr['displayThumbnail'] ){
$output .= $thumbnail;
}
$output .= '<h5 class="wp-block-author-box-author-plugin__post-title"><a href="' . $url . '">' . $title . '</a></h5>';
if( $attr['displayDate'] ){
$output .= '<time datetime="' . esc_attr( get_the_date( 'c', $p ) ) . '" class="wp-block-author-box-author-plugin__post-date">' . esc_html( get_the_date( '', $p ) ) . '</time>';
}
if( get_the_excerpt( $p ) && $attr['displayExcerpt'] ){
$output .= '<div class="wp-block-author-box-author-plugin__post-excerpt">' . get_the_excerpt( $p ) . '</div>';
}
$output .= '</li>';
}
$output .= '</ul>';
$output .= '</div>';
}
return $output ?? '<strong>Sorry. No posts matching your criteria!</strong>';
}
/**
* Registers the block using the metadata loaded from the `block.json` file.
* Behind the scenes, it registers also all assets so they can be enqueued
* through the block editor in the corresponding context.
*
* @see https://developer.wordpress.org/reference/functions/register_block_type/
*/
function author_box_author_plugin_block_init() {
register_block_type( __DIR__ . '/build', array(
'render_callback' => 'author_box_author_plugin_render_author_content'
) );
}
add_action( 'init', 'author_box_author_plugin_block_init' );
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "author-box/author-plugin",
"version": "0.1.0",
"title": "Author box",
"category": "widgets",
"icon": "businessperson",
"description": "An example block for Kinsta readers",
"supports": {
"html": false
},
"textdomain": "author-plugin",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"attributes": {
"numberOfItems": {
"type": "number",
"default": 3
},
"columns": {
"type": "number",
"default": 1
},
"displayDate": {
"type": "boolean",
"default": true
},
"displayExcerpt": {
"type": "boolean",
"default": true
},
"displayThumbnail": {
"type": "boolean",
"default": true
},
"displayAuthorInfo": {
"type": "boolean",
"default": true
},
"showAvatar": {
"type": "boolean",
"default": true
},
"avatarSize": {
"type": "number",
"default": 48
},
"showBio": {
"type": "boolean",
"default": true
}
}
}
/**
* External dependencies
*/
import { forEach } from 'lodash';
/**
* Retrieves the translation of text.
*
* @see https://developer.wordpress.org/block-editor/packages/packages-i18n/
*/
import { __ } from '@wordpress/i18n';
/**
* React hook that is used to mark the block wrapper element.
* It provides all the necessary props like the class name.
*
* @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps
*/
import {
useBlockProps,
InspectorControls
} from '@wordpress/block-editor';
import {
PanelBody,
PanelRow,
QueryControls,
SelectControl,
ToggleControl,
RangeControl
} from '@wordpress/components';
/**
* Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
* Those files can contain any CSS code that gets applied to the editor.
*
* @see https://www.npmjs.com/package/@wordpress/scripts#using-css
*/
import './editor.scss';
import { useSelect } from '@wordpress/data';
import { RawHTML } from '@wordpress/element';
import { dateI18n, format, __experimentalGetSettings } from '@wordpress/date';
/**
* The edit function describes the structure of your block in the context of the
* editor. This represents what the editor will render when the block is used.
*
* @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit
*
* @return {WPElement} Element to render.
*/
export default function Edit( { attributes, setAttributes } ) {
const {
numberOfItems,
columns,
displayExcerpt,
displayDate,
displayThumbnail,
displayAuthorInfo,
showAvatar,
avatarSize,
showBio
} = attributes;
const { authorDetails, posts } = useSelect(
( select ) => {
const _authorId = select( 'core/editor' ).getCurrentPostAttribute( 'author' );
const authorDetails = _authorId ? select( 'core' ).getUser( _authorId ) : null;
const posts = select( 'core' ).getEntityRecords( 'postType', 'post', {
'author': _authorId,
'per_page': numberOfItems,
'_embed': true
});
return {
authorDetails: authorDetails,
posts: posts
};
},
[ numberOfItems ]
);
const avatarSizes = [];
if ( authorDetails ) {
forEach( authorDetails.avatar_urls, ( url, size ) => {
avatarSizes.push( {
value: size,
label: `${ size } x ${ size }`,
} );
} );
}
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Author Info', 'author-plugin' ) }>
<PanelRow>
<ToggleControl
label={ __( 'Display Author Info', 'author-plugin' ) }
checked={ displayAuthorInfo }
onChange={ () =>
setAttributes( { displayAuthorInfo: ! displayAuthorInfo } )
}
/>
</PanelRow>
{ displayAuthorInfo && (
<>
<PanelRow>
<ToggleControl
label={ __( 'Show avatar' ) }
checked={ showAvatar }
onChange={ () =>
setAttributes( { showAvatar: ! showAvatar } )
}
/>
{ showAvatar && (
<SelectControl
label={ __( 'Avatar size' ) }
value={ avatarSize }
options={ avatarSizes }
onChange={ ( size ) => {
setAttributes( {
avatarSize: Number( size ),
} );
} }
/>
) }
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Show Bio', 'author-plugin' ) }
checked={ showBio }
onChange={ () =>
setAttributes( { showBio: ! showBio } )
}
/>
</PanelRow>
</>
) }
</PanelBody>
<PanelBody title={ __( 'Content Settings', 'author-plugin' ) }>
<PanelRow>
<QueryControls
numberOfItems={ numberOfItems }
onNumberOfItemsChange={ ( value ) =>
setAttributes( { numberOfItems: value } )
}
minItems={ 1 }
maxItems={ 10 }
/>
</PanelRow>
<PanelRow>
<RangeControl
label={ __( 'Number of Columns', 'author-plugin' ) }
value={ columns }
onChange={ ( value ) =>
setAttributes( { columns: value } )
}
min={ 1 }
max={ 4 }
required
/>
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Show Featured Image', 'author-plugin' ) }
checked={ displayThumbnail }
onChange={ () =>
setAttributes( { displayThumbnail: ! displayThumbnail } )
}
/>
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Show Date', 'author-plugin' ) }
checked={ displayDate }
onChange={ () =>
setAttributes( { displayDate: ! displayDate } )
}
/>
</PanelRow>
<PanelRow>
<ToggleControl
label={ __( 'Display Excerpt', 'author-plugin' ) }
checked={ displayExcerpt }
onChange={ () =>
setAttributes( { displayExcerpt: ! displayExcerpt } )
}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
<div { ...useBlockProps() }>
{ displayAuthorInfo && authorDetails && (
<div className="wp-block-author-box-author-plugin__author">
{ showAvatar && (
<div className="wp-block-author-box-author-plugin__avatar">
<img
width={ avatarSize }
src={
authorDetails.avatar_urls[
avatarSize
]
}
alt={ authorDetails.name }
/>
</div>
) }
<div className='wp-block-author-box-author-plugin__author-content'>
<div className='wp-block-author-box-author-plugin__name'>
{ authorDetails.name }
</div>
{ showBio &&
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
authorDetails?.description &&
authorDetails.description.length > 0 && (
<p className='wp-block-author-box-author-plugin__description'>{ authorDetails.description }</p>
) }
</div>
</div>
)}
{/* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals */}
<ul className={ `wp-block-author-box-author-plugin__post-items columns-${ columns }` }>
{ posts && posts.map( ( post ) => {
return (
<li key={ post.id }>
{
displayThumbnail &&
post._embedded &&
post._embedded['wp:featuredmedia'] &&
post._embedded['wp:featuredmedia'][0] &&
<img
className='wp-block-author-box-author-plugin__post-thumbnail'
src={ post._embedded['wp:featuredmedia'][0].media_details.sizes.large.source_url }
alt={ post._embedded['wp:featuredmedia'][0].alt_text }
/>
}
<h5
className='wp-block-author-box-author-plugin__post-title'
>
<a href={ post.link }>
{ post.title.rendered ? (
<RawHTML>
{ post.title.rendered }
</RawHTML>
) : (
__( 'Default title', 'author-plugin' )
)}
</a>
</h5>
{
displayDate && (
<time
className='wp-block-author-box-author-plugin__post-date'
dateTime={ format( 'c', post.date_gmt ) }
>
{ dateI18n(
__experimentalGetSettings().formats.date,
post.date_gmt
)}
</time>
)
}
{
displayExcerpt &&
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
post.excerpt?.rendered && (
<div className='wp-block-author-box-author-plugin__post-excerpt'>
<RawHTML>
{ post.excerpt.rendered }
</RawHTML>
</div>
)
}
</li>
)
})}
</ul>
</div>
</>
);
}
/**
* Registers a new block provided a unique name and an object defining its behavior.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
* All files containing `style` keyword are bundled together. The code used
* gets applied both to the front of your site and to the editor.
*
* @see https://www.npmjs.com/package/@wordpress/scripts#using-css
*/
import './style.scss';
/**
* Internal dependencies
*/
import Edit from './edit';
import save from './save';
/**
* Every block starts by registering a new block type definition.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
*/
registerBlockType('author-box/author-plugin', {
/**
* @see ./edit.js
*/
edit: Edit,
/**
* @see ./save.js
*/
save,
});
export default function save() {
return null;
}
/**
* The following styles get applied both on the front of your site
* and in the editor.
*
* Replace them with your own styles or remove the file completely.
*/
.wp-block-author-box-author-plugin {
background-color: #21759b;
color: #fff;
padding: .6em;
ul {
padding: 0;
list-style-type: none;
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout
// https://learncssgrid.com/
display: grid;
gap: .5em;
// https://sass-lang.com/documentation/at-rules/control/for
@for $i from 2 through 4 {
&.columns-#{ $i } {
grid-template-columns: repeat(#{ $i }, 1fr);
}
}
li {
list-style: none;
img {
height: auto;
max-width: 100%;
}
}
}
}
.wp-block-author-box-author-plugin__author {
display: flex;
flex-wrap: wrap;
}
.wp-block-author-box-author-plugin__avatar {
margin-right: 1em;
}
.wp-block-author-box-author-plugin__author-content {
flex-basis: 0;
flex-grow: 1;
}
@eigenstil
Copy link

eigenstil commented Mar 23, 2023

@carlodaniele thanks, seems very nice, but I can't seem to make it run.

I downloaded this whole gist as zip, uploaded it to my site and activated the plugin.

But in the Gutenberg I can't find the block in the "widget" section (or I don't know what to search for). Maybe this is because of line 86 in author-plugin.php? Why is there the string "/build" added to the folder path (maybe a leftover)?

Maybe I'm getting something wrong!?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment