Skip to content

Instantly share code, notes, and snippets.

@philipjohn
Created May 19, 2025 09:47
Show Gist options
  • Save philipjohn/3eed21d8920fe0e30a8662f9cb65d409 to your computer and use it in GitHub Desktop.
Save philipjohn/3eed21d8920fe0e30a8662f9cb65d409 to your computer and use it in GitHub Desktop.
Attempting to override the core/query and core/post-template WordPress blocks
/**
* WordPress dependencies
*/
import { unregisterBlockVariation, registerBlockVariation } from '@wordpress/blocks'
import { addFilter } from '@wordpress/hooks';
import { registerPlugin } from '@wordpress/plugins';
import { select, subscribe } from '@wordpress/data';
/**
* Internal dependencies
*/
import { isValidTemplate, getPostItems, getPostItemsForTemplate } from '../templates';
import { memo } from '@wordpress/element';
const VARIATION_NAME = 'my/post-template';
registerBlockVariation( 'core/post-template', {
name: 'my/post-template',
title: 'Post Template',
description: 'Displays a single post.',
icon: 'post',
isActive: ( blockAttributes ) => {
return isValidTemplate( blockAttributes.layout )
},
isDefault: true,
} );
const alterCorePostTemplateInnerBlocks = () => {
subscribe( () => {
// Make sure we have blocks to modify.
const currentBlocks = select('core/block-editor').getBlocks();
if ( currentBlocks.length === 0 ) {
return;
}
// Find any blocks that we want to modify. Bail if there aren't any.
const queryBlocks = currentBlocks.filter( block => (
'core/query' === block.name &&
block.attributes?.namespace?.startsWith('my') &&
block.innerBlocks?.length > 0 &&
block.innerBlocks.find( iB => iB.name === 'core/post-template' )
) );
if ( queryBlocks.length === 0 ) {
return;
}
// Loop through the blocks and modify child post-template blocks.
queryBlocks.forEach( ( queryBlock ) => {
const slabTemplateName = queryBlock.attributes.namespace.replace( 'my/slab-', '' );
const postItemBlocks = getPostItemsForTemplate( slabTemplateName ).map( postItem => {
return [ { name: 'my/post', attributes: { template: postItem } } ];
} );
queryBlock.innerBlocks.map( ( postTemplateBlock, index ) => {
// Don't replace existing my/post blocks.
if ( postTemplateBlock?.innerBlocks?.[0]?.name === 'my/post' ) {
return;
}
// postTemplateBlock.innerBlocks = postItemBlocks[ index ];
postTemplateBlock.innerBlocks = [ { name: 'my/post' } ];
} );
} );
return '';
} );
}
registerPlugin( 'my-core-post-template-innerblocks', { render: alterCorePostTemplateInnerBlocks } );
/**
* WordPress dependencies
*/
import { unregisterBlockVariation, registerBlockVariation } from '@wordpress/blocks'
import { addFilter } from '@wordpress/hooks';
import { registerPlugin } from '@wordpress/plugins';
import { select, subscribe } from '@wordpress/data';
/**
* Internal dependencies
*/
import { getInnerBlocksForTemplate, getPostsPerPageForTemplate, getTemplates, getTotalPostsForTemplate } from '../templates';
// Remove the default variations of the core/query block.
unregisterBlockVariation('core/query', 'title-date');
unregisterBlockVariation('core/query', 'title-excerpt');
unregisterBlockVariation('core/query', 'title-date-excerpt');
unregisterBlockVariation('core/query', 'image-date-title');
const VARIATION_PREFIX = 'my/slab-';
const MY_SLAB_DEFAULT_QUERY = {
perPage: 15, // The most in any of our templates.
pages: 1,
offset: 0,
postType: "post",
order: "desc",
orderBy: "date",
author: "",
search: "",
exclude: [],
sticky: "",
inherit: true,
taxQuery: null,
parents: [],
format: [],
};
const MY_DEFAULT_TEMPLATE = '4xTeaser';
getTemplates().forEach( template => {
const variation = VARIATION_PREFIX + template.toLowerCase();
registerBlockVariation( 'core/query', {
name: variation,
title: template,
description: 'Display a slab of posts.',
icon: 'welcome-widgets-menus',
isActive: ({namespace}) => namespace === variation,
isDefault: template === '4xTeaser',
attributes: {
query: {
...MY_SLAB_DEFAULT_QUERY,
perPage: getPostsPerPageForTemplate( template ),
},
namespace: variation,
tagName: 'div',
enhancedPagination: false,
},
scope: ['inserter', 'transform'],
allowedControls: [ 'inherit', 'taxQuery', 'author' ],
innerBlocks: [ [ 'core/post-template', { layout: template }, [ [ 'my/post' ] ] ] ],
} );
} );
const alterCoreQueryExample = ( settings, name ) => {
if ( 'core/query' !== name ) {
return settings;
}
return {
...settings,
example: {
attributes: {
namespace: VARIATION_PREFIX + MY_DEFAULT_TEMPLATE.toLowerCase(),
query: {
...MY_SLAB_DEFAULT_QUERY,
perPage: getPostsPerPageForTemplate( MY_DEFAULT_TEMPLATE ),
},
},
innerBlock: getInnerBlocksForTemplate( MY_DEFAULT_TEMPLATE ),
},
}
}
addFilter( 'blocks.registerBlockType', 'my/core-query/example', alterCoreQueryExample );
const alterCoreQueryPerPage = () => {
subscribe( () => {
// Make sure we have blocks to modify.
const currentBlocks = select('core/block-editor').getBlocks();
if ( currentBlocks.length === 0 ) {
return;
}
// Find any blocks that we want to modify. Bail if there aren't any.
const blocksToModify = currentBlocks.filter( block => 'core/query' === block.name );
if ( blocksToModify.length === 0 ) {
return;
}
// Loop through the blocks and modify.
blocksToModify.forEach( ( block ) => {
// Get the block namespace and find the corresponding template name.
const namespace = block.attributes.namespace;
const templateName = getTemplates().filter( template => namespace === VARIATION_PREFIX + template.toLowerCase() );
if ( templateName.length === 0 ) {
return;
}
block.attributes.query.perPage = getPostsPerPageForTemplate( templateName[0] );
} );
return '';
} );
}
registerPlugin( 'inews-core-query-perpage', { render: alterCoreQueryPerPage } );
/**
* Retrieves the list of templates from block.json.
*
* A simple array of template names, e.g. "1xHero2xTeaser2xJot"
*
* @returns {Array}
*/
export const getTemplates = () => {
return [
"1xBanner4xJot",
"1xBanner_Black4xJot",
"2xHero4xTeaser",
"4xTeaser",
"1xTeaser1xJot1xHero1xTeaser1xJot",
"1xSuperhero2xJot1xTeaser4xJot",
"1xJot1xHero2xJot2xTeaser1xPuff2xJot1xPuff5xJot",
"1xHero2xTeaser2xJot",
"2xTeaser2xJot1xHero",
"1xPortrait3xJot",
"1xHeroBox4xTeaser4xJot",
"1xHeroJot2xJot1xTeaser4xJot1xHero"
];
}
export const getPostItems = () => {
return [
"banner",
"hero",
"herobox",
"herojot",
"jot",
"portrait",
"puff",
"superhero",
"teaser",
];
}
/**
* Checks that a given template string is in our list of templates.
*
* @param {string} template The template name
*
* @returns {bool} True if valid, false otherwise.
*/
export const isValidTemplate = ( template ) => {
return getTemplates().find( el => el === template ) !== undefined;
}
/**
* Provides a list of post items and how many are included in the given template
*
* @param {string} template The template name. E.g. "1xHero2xTeaser2xJot"
*
* @returns {array} An array of objects. E.g.: [
* { name: 'hero', count: 1 },
* { name: 'teaser', count: 2 },
* { name: 'jot', count: 2 },
* ]
*/
const getPostItemsWithCountsForTemplate = ( template ) => {
const items = [];
template.matchAll(/([0-9]+x[a-zA-Z_]+)/g).forEach( ( [ item ] ) => {
// item will be something like 4xJot
// then we split it to get count (4) and name (Jot)
const count = parseInt( item.slice( 0, item.indexOf('x') ) );
const name = item.slice( item.indexOf('x') + 1 ).toLowerCase();
items.push( { name, count } );
} );
return items;
}
/**
* Provides a simple array of post item names for the given template.
*
* @param {string} template The template name. E.g. "1xHero2xTeaser2xJot"
*
* @returns {array} The list of post items. E.g. [
* 'hero', 'teaser', 'teaser', 'jot', 'jot'
* ]
*/
export const getPostItemsForTemplate = ( template ) => {
return getPostItemsWithCountsForTemplate( template ).map( item => item.name );
}
/**
* Provides a simple number of posts that are included in a template.
*
* @param {string} template The template name. E.g. "1xHero2xTeaser2xJot"
*
* @returns {int} Number of posts. E.g. 5
*/
export const getTotalPostsForTemplate = ( template ) => {
return getPostItemsWithCountsForTemplate( template )
.map( item => item.count )
.reduce( ( acc, cur ) => acc + cur, 0 );
}
/**
* Provides the block array for the given post item.
*
* @param {string} postItem The post item. E.g. 'teaser'.
*
* @returns {array} The block array. E.g. [ "my/post", { "template": "teaser" } ]
*/
export const getPostItemBlock = ( postItem ) => {
return [ "my/post", { "template": postItem } ];
}
/**
* Retrieves the posts_per_page parameter to use in a query for a particular template.
*
* @param {string} template The template name. E.g. "1xHero2xTeaser2xJot"
*
* @returns {int} The number of posts. E.g. 5
*/
export const getPostsPerPageForTemplate = ( template ) => {
return getTotalPostsForTemplate( template );
}
/**
* Provides a block template for the given template.
*
* @param {string} template The template name. E.g. "1xHero2xTeaser2xJot"
*
* @returns {array} The block template array. E.g.: [
* [ "core/post-template", { layout: "1xHero2xTeaser2xJot" }, [ "my/post", { "template": "hero" } ] ],
* [ "core/post-template", { layout: "1xHero2xTeaser2xJot" }, [ "my/post", { "template": "teaser" } ] ],
* [ "core/post-template", { layout: "1xHero2xTeaser2xJot" }, [ "my/post", { "template": "teaser" } ] ],
* [ "core/post-template", { layout: "1xHero2xTeaser2xJot" }, [ "my/post", { "template": "jot" } ] ],
* [ "core/post-template", { layout: "1xHero2xTeaser2xJot" }, [ "my/post", { "template": "jot" } ] ],
* ]
*/
export const getInnerBlocksForTemplate = ( template ) => {
if ( ! isValidTemplate( template ) ) {
return [];
}
let innerBlocks = [];
getPostItemsWithCountsForTemplate( template ).map( ( { name, count } ) => {
innerBlocks = [...innerBlocks, ...Array( parseInt( count ) ).fill(
[ 'core/post-template', { layout: template }, [ getPostItemBlock( name ) ] ]
) ];
} );
return innerBlocks;
}
/**
* Provides the block array for the given position in the given template.
*
* @param {string} template The template name. E.g. "1xHero2xTeaser2xJot"
* @param {int} position The position. Note, the first position is `0`. E.g. 3.
*
* @returns {array} The block array. E.g. [ "my/post", { "template": "jot" } ]
*/
export const getBlockForTemplatePosition = ( template, position ) => {
return getInnerBlocksForTemplate( template )[ position ];
}
/**
* Provides the post item name for the given position in the given template.
*
* @param {string} template The template name. E.g. "1xHero2xTeaser2xJot"
* @param {int} position The position. Note, the first position is `0`. E.g. 3.
*
* @returns {string} The post item name. E.g. "jot"
*/
export const getPostItemForTemplatePosition = ( template, position ) => {
return getBlockForTemplatePosition( template, position )[1].template;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment