Skip to content

Instantly share code, notes, and snippets.

@dwtompkins
Created May 13, 2021 20:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dwtompkins/8f4bd679debef3ea64e35e5b4604756b to your computer and use it in GitHub Desktop.
Save dwtompkins/8f4bd679debef3ea64e35e5b4604756b to your computer and use it in GitHub Desktop.
First Gutenberg Block. Need a hand
/**
* BLOCK: Card
*
* Registering a basic block with Gutenberg.
* Simple block, renders and saves the same content without any interactivity.
*/
// Import CSS.
import './editor.scss';
import './style.scss';
// Custom MultiSelect Component
import TokenMultiSelectControl from './token-multiselect-control';
// Get the apiFetch library to pull in Post data
import apiFetch from '@wordpress/api-fetch';
// Setup a function wrapper to localize all the react libraries
( function( i18n, data, plugins, editPost, blocks, element, blockEditor, components, compose ) {
// Alias the vanilla createElement method to 'el'
const el = element.createElement;
const { __ } = i18n; // Import __() from wp.i18n
const { registerBlockType } = blocks; // Import registerBlockType() from wp.blocks
const { Fragment } = element; // Import for Sidebar
const { InspectorControls } = blockEditor; // Used to configure block attributes
const { FormTokenField, PanelBody, PanelRow, RadioControl, SelectControl, CheckboxControl, TextControl, TextareaControl, ToggleControl } = components; // Import components
const { withState } = compose;
// Build a custom svg icon for the blocks
const iretaIcon = el( 'svg',
{
width: 20,
height: 20,
},
el( 'path', {
d: 'M10 0.4c-5.302 0-9.6 4.298-9.6 9.6s4.298 9.6 9.6 9.6c5.301 0 9.6-4.298 9.6-9.601 0-5.301-4.299-9.599-9.6-9.599zM10 17.599c-4.197 0-7.6-3.402-7.6-7.6s3.402-7.599 7.6-7.599c4.197 0 7.601 3.402 7.601 7.6s-3.404 7.599-7.601 7.599zM7.501 9.75c0.828 0 1.499-0.783 1.499-1.75s-0.672-1.75-1.5-1.75-1.5 0.783-1.5 1.75 0.672 1.75 1.501 1.75zM12.5 9.75c0.829 0 1.5-0.783 1.5-1.75s-0.672-1.75-1.5-1.75-1.5 0.784-1.5 1.75 0.672 1.75 1.5 1.75zM14.341 11.336c-0.363-0.186-0.815-0.043-1.008 0.32-0.034 0.066-0.869 1.593-3.332 1.593-2.451 0-3.291-1.513-3.333-1.592-0.188-0.365-0.632-0.514-1.004-0.329-0.37 0.186-0.52 0.636-0.335 1.007 0.050 0.099 1.248 2.414 4.672 2.414 3.425 0 4.621-2.316 4.67-2.415 0.184-0.367 0.036-0.81-0.33-0.998z',
} ),
);
/**
* Register: IRETA Card - Gutenberg Block.
*
* Registers a new block provided a unique name and an object defining its
* behavior. Once registered, the block is made editor as an option to any
* editor interface where blocks are implemented.
*
* @link https://wordpress.org/gutenberg/handbook/block-api/
* @param {string} name Block name.
* @param {Object} settings Block settings.
* @return {?WPBlock} The block, if it has been successfully
* registered; otherwise `undefined`.
*/
registerBlockType( 'ireta/card', { // Block name. Block names must be string that contains a namespace prefix. Example: my-plugin/my-custom-block.
title: __( 'Card' ), // Block title.
icon: iretaIcon, // Block icon from Dashicons → https:// developer.wordpress.org/resource/dashicons/.
category: 'common', // Block category — Group blocks together based on common traits E.g. common, formatting, layout widgets, embed.
// Keywords to search for Block
keywords: [
__( 'Card Block' ),
__( 'Card' ),
__( 'ireta card' ),
],
// Attributes are:
// 1. archiveType (string)
// 2. limit (int)
// 3. filter (bool)
// 4. selectedPosts (array)
attributes: {
archiveType: {
type: 'string',
default: 'posts',
},
limit: {
type: 'integer',
default: 12,
},
filter: {
type: 'boolean',
default: false,
},
selectedPosts: {
type: 'array',
default: [],
},
availablePosts: {
type: 'array',
default: [],
},
},
/**
* 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.
*
* The "edit" property must be a valid function.
*
* @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
*
* @param {Object} props Props.
* @returns {Mixed} JSX Component.
*/
edit: ( props ) => {
function joinDate( t, a, s ) {
function format( m ) {
const f = new Intl.DateTimeFormat( 'en', m );
return f.format( t );
}
return a.map( format ).join( s );
}
function getOptions( type ) {
const location = '/wp/v2/' + type; // WordPress API, uses json request with post type
const availablePosts = []; // Empty array to hold available posts
console.log( 'Location: ' );
console.log( location );
apiFetch( { path: location } ).then( ( posts ) => { // Query
if ( posts ) { // If query retrieved posts
console.log( 'All Posts: ' );
console.log( posts );
posts.forEach( ( post ) => { // Loop through posts, retrieving post info
console.log( 'Post Title: ' );
console.log( post.title.rendered );
availablePosts.push( {
value: {
author: post.author,
categories: post.categories,
content: post.content.rendered,
date: joinDate( Date.parse( post.date ), a, '/' ),
excerpt: post.excerpt.rendered,
featured_image_src: post.featured_image_src,
id: parseInt( post.id ),
link: post.link,
modified: post.modified,
slug: post.slug,
tags: post.tags,
template: post.template,
title: post.title.rendered,
type: post.type,
},
label: post.title.rendered,
} );
} );
} else {
availablePosts.push( {
value: 0,
label: 'Loading...',
} );
}
} );
return availablePosts;
}
console.log( 'Type: ' );
console.log( props.attributes.archiveType );
console.log( 'Options: ');
console.log( getOptions( props.attributes.archiveType ) );
/*
const SelectPosts = withState( {
tokens: props.attributes.selectedPosts,
suggestions: getOptions( props.attributes.archiveType ),
} )( ( { tokens, suggestions, setState } ) => (
<FormTokenField
label={ __( 'Select specific posts:', 'ireta' ) }
help={ __( 'Select a specific post or posts from the available list', 'ireta' ) }
value={ tokens }
suggestions={ suggestions }
onChange={ ( selected ) => {
setState( { selected } );
props.setAttributes( { selectedPosts: selected } );
} }
/>
) );
*/
// Map for building the date
const a = [ { day: 'numeric' }, { month: 'short' }, { year: 'numeric' } ];
const SelectPosts = withState( {
value: props.attributes.selectedPosts,
options: getOptions( props.attributes.archiveType ),
} )( ( { value, options, setState } ) => (
<TokenMultiSelectControl
label={ __( 'Select specific posts:', 'ireta' ) }
help={ __( 'Select a specific post or posts from the available list', 'ireta' ) }
value={ value }
options={ options }
onChange={ ( value ) => {
setState( { value } );
props.setAttributes( { selectedPosts: value } );
} }
/>
) );
return (
el( Fragment, {}, // Wrapper
el( InspectorControls, {}, // Controller
el( PanelBody, // Block Wrapper
{
title: __( 'Card Settings', 'ireta' ),
initialOpen: true,
},
el( PanelRow, {},
// Select the Post Type for Card Block
el( RadioControl, {
label: __( 'Select the post type:', 'ireta' ),
help: __( 'Controls the Card\'s post type selection', 'ireta' ),
options: [
{ label: 'Standard Post', value: 'posts' },
{ label: 'Case Study', value: 'case_studies' },
{ label: 'Resource', value: 'resources' },
],
selected: props.attributes.archiveType,
onChange: ( value ) => {
props.setAttributes( { archiveType: value } );
props.setAttributes( { availablePosts: getOptions( value ) } );
},
} )
),
el( PanelRow, {},
// Numeric input for setting number of cards
el( TextControl, {
label: __( 'Set the card limit:', 'ireta' ),
type: 'number',
value: props.attributes.limit,
onChange: ( value ) => {
props.setAttributes( { limit: value } );
},
} )
),
el( PanelRow, {},
// Boolean to toggle filter dropdown
el( ToggleControl, {
label: __( 'Enable filter?', 'ireta' ),
help: __( 'Enable a dropdown select to filter posts by category, when available.', 'ireta' ),
checked: props.attributes.filter,
onChange: ( value ) => {
props.setAttributes( { filter: value } );
},
} )
),
el( PanelRow,
{
className: 'select-multiple',
},
el( SelectPosts, {}, ),
// Select specific posts
/*
el( FormTokenField, {
label: __( 'Select specific posts:', 'ireta' ),
help: __( 'Select a specific post or posts from the available list', 'ireta' ),
suggestions: getOptions( props.attributes.archiveType ),
value: props.attributes.selectedPosts,
onChange: ( value ) => {
alert( value );
props.setAttributes( { selectedPosts: value } );
},
} )
*/
),
)
)
)
);
},
/**
* The save function defines the way in which the different attributes should be combined
* into the final markup, which is then serialized by Gutenberg into post_content.
*
* The "save" property must be specified and must be a valid function.
*
* @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
*
* @param {Object} props Props.
* @returns {Mixed} JSX Frontend HTML.
*/
save: ( props ) => {
const posts = JSON.parse( props.attributes.selectedPosts );
const { archiveType, limit, filter, selectedPosts, availablePosts } = props;
console.log( 'Properties: ' );
console.log( props );
console.log( 'Selected Posts: ' );
console.log( posts );
return (
/* apiFetch( { path: 'wp/v2/' + archiveType + '' } ).then( ( posts ) => { // Query
if ( posts ) { // If query retrieved posts
console.log( 'All Posts: ' );
console.log( posts );
// Map for building the date
const a = [ { day: 'numeric' }, { month: 'short' }, { year: 'numeric' } ];
author: post.author,
categories: post.categories,
content: post.content.rendered,
date: joinDate( Date.parse( post.date ), a, '/' ),
excerpt: post.excerpt.rendered,
featured_image_src: post.featured_image_src,
id: parseInt( post.id ),
link: post.link,
modified: post.modified,
slug: post.slug,
tags: post.tags,
template: post.template,
title: post.title.rendered,
type: post.type,
availablePosts.push( { // Populate a placeholder in the select dropdown
value: parseInt( 0 ),
label: 'Select a post...',
} );
posts.forEach( ( post ) => { // Loop through posts, retrieving post info
availablePosts.push(
{ label: post.title.rendered, value: post.id },
);
} );
} else {
availablePosts.push( {
value: parseInt( 0 ),
label: 'Loading...',
} );
}
} ); */
/*
posts.forEach( ( ( post ) => {
console.log( 'Post: ' );
console.log( post );
return(
el( 'a',
{
className: 'card__link-wrapper ' + post.type,
href: post.link,
title: post.title,
},
el( 'li',
{
className: 'card__list-item',
},
el( 'div',
{
className: 'card__head',
},
el( 'h4', {
className: 'card__tags-wrapper',
dangerouslySetInnerHTML: { __html: post.categories.join( ' ' ) },
} )
),
el( 'div',
{
className: 'card__featured-image',
},
el( 'img', {
className: 'size-thumbnail card__thumbnail lazyload',
src: post.featured_image_src,
} )
),
el( 'div',
{
className: 'card__body',
},
el( 'div', {
className: 'card__title',
dangerouslySetInnerHTML: { __html: post.title.rendered },
} ),
el( 'p', {
className: 'card__read-more',
dangerouslySetInnerHTML: { __html: __( 'Read More', 'ireta' ) },
} ),
el( 'span', {
className: 'card__date',
dangerouslySetInnerHTML: { __html: post.date },
} )
)
)
)
);
} )() )
*/
null
);
},
} );
} )(
window.wp.i18n,
window.wp.data,
window.wp.plugins,
window.wp.editPost,
window.wp.blocks,
window.wp.element,
window.wp.blockEditor,
window.wp.components,
window.wp.compose
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment