|
/** |
|
* 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 { |
|
InnerBlocks, |
|
useBlockProps, |
|
InspectorControls, |
|
} from '@wordpress/block-editor'; |
|
|
|
/** |
|
* Other things needed |
|
* |
|
*/ |
|
|
|
import { SelectControl, TextControl } from '@wordpress/components'; |
|
import { Component } from '@wordpress/element'; |
|
import { useDispatch, useSelect } from '@wordpress/data'; |
|
|
|
/** |
|
* 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'; |
|
|
|
function mySelectPosts({clientId, attributes, setAttributes }) { |
|
|
|
// Used to reset inner blocks if we've changed the selected exercise |
|
const { replaceInnerBlocks } = useDispatch("core/block-editor"); |
|
const { inner_blocks } = useSelect(select => ({ |
|
inner_blocks: select("core/block-editor").getBlocks(clientId) |
|
})); |
|
|
|
/** |
|
* Called when the inspector controls select box is changed |
|
* Saves the value of the selected post to the selectedPost attribute |
|
* and resets the innerblock |
|
**/ |
|
|
|
const onSelectPost = ( post ) => { |
|
|
|
// reset the inner |
|
let inner_blocks_new = []; |
|
|
|
replaceInnerBlocks(clientId, inner_blocks_new, false); |
|
|
|
setAttributes( { |
|
selectedPost: parseInt(post), |
|
} ); |
|
}; |
|
|
|
const posts = useSelect( 'core' ).getEntityRecords( 'postType', 'exercise', { //1 |
|
// *no _embed here |
|
|
|
// This should always be set to avoid duplicates in the post selection dropdown. |
|
// But of course, you can change the value, e.g. to 20 or, the max value - 100. |
|
per_page: 10, |
|
} ); |
|
|
|
const selectedPost = useSelect( select => { //2 |
|
const { getEntityRecord } = select( 'core' ); |
|
|
|
const post_id = attributes.selectedPost; |
|
|
|
// Fetch the post from the REST API, if we have a valid post ID. |
|
return post_id && getEntityRecord( 'postType', 'exercise', post_id, { |
|
// Request featured media along with the standard post data. |
|
_embed: 'wp:featuredmedia', |
|
|
|
// *no per_page here |
|
} ); |
|
// This useSelect callback has one dependency - the selected post ID. |
|
}, [ attributes.selectedPost ] ); |
|
|
|
/* Set of functions for saving out attributes |
|
* reps, sets, notes |
|
*/ |
|
|
|
const onChangeReps = ( reps ) => { |
|
setAttributes( { |
|
reps: Number( reps ), //3 |
|
} ); |
|
} |
|
|
|
const onChangeSets = ( sets ) => { |
|
setAttributes( { |
|
sets: Number( sets ), //4 |
|
} ); |
|
} |
|
|
|
const onChangeNotes = ( notes ) => { |
|
setAttributes( { |
|
notes: notes, |
|
} ); |
|
} |
|
|
|
// will contain the text output for the edit. |
|
let output = ""; |
|
|
|
const blockProps = useBlockProps( { |
|
className: "sandcexercise", |
|
} ); |
|
|
|
// we've selected a post, so grab the bits from that post to put into the html |
|
if (selectedPost) { //5 |
|
|
|
// grab the things we are putting in the innerblock from the post |
|
let mediaID = selectedPost?.featured_media || 0; //6 |
|
let mediaURL = selectedPost?._embedded?.['wp:featuredmedia'][0]?.source_url || ''; //7 |
|
let exerciseLink = selectedPost.link; |
|
|
|
// remove any html content from the excerpt as it explodes the inner block :/ |
|
let strippedContent = selectedPost.excerpt.rendered.replace(/(<([^>]+)>)/gi, ""); |
|
|
|
// create a linked heading |
|
let heading = '<a href="' + exerciseLink + '">' + selectedPost.title.rendered + '</a>'; |
|
|
|
// build the innerblocks template |
|
const MY_TEMPLATE = [ |
|
[ 'core/heading', { content: heading } ], |
|
[ 'core/image', { id: mediaID, url: mediaURL, href: exerciseLink, align: "right", sizeSlug: "medium", caption: "Click for instructions", width: 300 } ], //* |
|
[ 'core/paragraph', { content: strippedContent } ] |
|
]; |
|
output = <InnerBlocks |
|
template={ MY_TEMPLATE } |
|
templateLock="" |
|
/> |
|
|
|
} else { |
|
output = <p>Select an exercise from the right</p> |
|
} |
|
|
|
//8 I changed the SelectControl's options - a "loading" message is shown if "posts" |
|
// is yet filled. |
|
|
|
// return the edit html. |
|
return [ |
|
<div { ...blockProps } key="sandcexercise"> |
|
<InspectorControls> |
|
<div id="sandcexercise-controls"> |
|
<SelectControl |
|
onChange={ onSelectPost } |
|
value={ attributes.selectedPost } |
|
label={ __( 'Select a Post' ) } |
|
options={posts ? [ |
|
...[ { value: 0, label: __( 'Select an exercise' ) } ], |
|
...posts.map( post => ( { |
|
value: post.id, |
|
label: post.title.rendered, |
|
})), |
|
] : [ |
|
{ value: 0, label: __( 'Loading the posts list..' ) }, |
|
]} |
|
/> |
|
</div> |
|
</InspectorControls> |
|
{output} |
|
<TextControl |
|
{ ...blockProps } |
|
label="Reps:" |
|
type="number" |
|
className="thereps" |
|
onChange={ onChangeReps } |
|
value={ attributes.reps } |
|
/> |
|
<TextControl |
|
{ ...blockProps } |
|
label="Sets:" |
|
type="number" |
|
className="thesets" |
|
onChange={ onChangeSets } |
|
value={ attributes.sets } |
|
/> |
|
<TextControl |
|
{ ...blockProps } |
|
label="Notes:" |
|
type="text" |
|
className="thenotes" |
|
onChange={ onChangeNotes } |
|
value={ attributes.notes } |
|
/> |
|
</div> |
|
] |
|
|
|
} |
|
|
|
export default mySelectPosts; |
This code commits the cardinal sin of putting InnerBlocks alongside other components. This is the best way to get your block to fail validation.
Blocks that contain other blocks cannot put additional elements alongside. It needs to be on its own inside an element
Good:
Bad:
Inner blocks is always an only child! It never ever shares its container with other elements or containers. No exceptions.