Skip to content

Instantly share code, notes, and snippets.

@andreilupu
Last active February 3, 2024 14:38
Show Gist options
  • Save andreilupu/be97e8e38e6caa29e1e9348b36125084 to your computer and use it in GitHub Desktop.
Save andreilupu/be97e8e38e6caa29e1e9348b36125084 to your computer and use it in GitHub Desktop.
WordPress Post Title variation which prints a <li> instead of <h{n}>
// register a new post title variation.
// In a production environment I would register a new attribute called "isLiPostTitle" and use that instead of checking the className.
window.wp.blocks.registerBlockVariation(
'core/post-title',
{
name: 'core/post-title-li',
title: 'Post title (li)',
attributes: {
className: 'is-li-title'
},
}
);
<?php
/**
* A function that imitates the render_callback of the Post Title block but with the output of <li> tag instead of <h{N}>
* Added to `render_block` that's why parameters are not the same.
* https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/post-title/index.php#L19-L59
*
* @param $content
* @param $attrs
* @param $block
* @return string
*/
function _custom_render_block_core_post_title( $content, $attrs, $block ) {
$attributes = $block->attributes;
if ( ! isset( $block->context['postId'] ) ) {
return '';
}
/**
* The `$post` argument is intentionally omitted so that changes are reflected when previewing a post.
* See: https://github.com/WordPress/gutenberg/pull/37622#issuecomment-1000932816.
*/
$title = get_the_title();
if ( ! $title ) {
return '';
}
$tag_name = 'h2';
if ( isset( $attributes['level'] ) ) {
$tag_name = 'h' . $attributes['level'];
}
// @THIS! This is different from the original https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/post-title/index.php#L19-L59
if ( isset( $attributes['className'] ) && $attributes['className'] === 'is-li-title' ) {
$tag_name = 'li';
} else {
return $content;
}
if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) {
$rel = ! empty( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : '';
$title = sprintf( '<a href="%1$s" target="%2$s" %3$s>%4$s</a>', esc_url( get_the_permalink( $block->context['postId'] ) ), esc_attr( $attributes['linkTarget'] ), $rel, $title );
}
$classes = array();
if ( isset( $attributes['textAlign'] ) ) {
$classes[] = 'has-text-align-' . $attributes['textAlign'];
}
if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
$classes[] = 'has-link-color';
}
return sprintf(
'<%1$s %2$s>%3$s</%1$s>',
$tag_name,
implode( ' ', $classes ),
$title
);
}
add_filter('render_block', '_custom_render_block_core_post_title', 10, 3);
import customPostTitleEdit from './4-PostTitleEdit.js';
const { createHigherOrderComponent } = wp.compose;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;
const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
const isLI = props.attributes['className'] === 'is-li-title';
return (
<>
{isLI && <PostTitleEdit { ...props } />}
{!isLI && <BlockEdit key="edit" { ...props } />}
<InspectorControls>
<PanelBody>My custom control</PanelBody>
</InspectorControls>
</>
);
};
}, 'withMyPluginControls' );
wp.hooks.addFilter(
'editor.BlockEdit',
'my-plugin/with-inspector-controls',
withMyPluginControls
);
// A copy of Gutenberg's PostTitle Edit component https://github.com/WordPress/gutenberg/blob/408c1186af35e19ed040b8fa56765b1ac731969d/packages/block-library/src/post-title/edit.js#L28
// This file would be the only one who needs webpack handling with @wordpress-scripts. (I could convert imports to window.wp but is out of scope for this excercise).
// At this point this is already a custom block just to replace a <h{n}> with a <li>.
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import {
AlignmentControl,
BlockControls,
InspectorControls,
useBlockProps,
PlainText,
HeadingLevelDropdown,
useBlockEditingMode,
} from '@wordpress/block-editor';
import { ToggleControl, TextControl, PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
import { useEntityProp } from '@wordpress/core-data';
/**
* Returns whether the current user can edit the given entity.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {string} recordId Record's id.
*/
export function useCanEditEntity( kind, name, recordId ) {
return useSelect(
( select ) =>
select( coreStore ).canUserEditEntityRecord( kind, name, recordId ),
[ kind, name, recordId ]
);
}
export default function customPostTitleEdit( {
attributes: { level, textAlign, isLink, rel, linkTarget },
setAttributes,
context: { postType, postId, queryId },
insertBlocksAfter,
} ) {
const TagName = 'li';
const isDescendentOfQueryLoop = Number.isFinite( queryId );
/**
* Hack: useCanEditEntity may trigger an OPTIONS request to the REST API via the canUser resolver.
* However, when the Post Title is a descendant of a Query Loop block, the title cannot be edited.
* In order to avoid these unnecessary requests, we call the hook without
* the proper data, resulting in returning early without making them.
*/
const userCanEdit = useCanEditEntity(
'postType',
! isDescendentOfQueryLoop && postType,
postId
);
const [ rawTitle = '', setTitle, fullTitle ] = useEntityProp(
'postType',
postType,
'title',
postId
);
const [ link ] = useEntityProp( 'postType', postType, 'link', postId );
const onSplitAtEnd = () => {
insertBlocksAfter( createBlock( getDefaultBlockName() ) );
};
const blockProps = useBlockProps( {
className: classnames( {
[ `has-text-align-${ textAlign }` ]: textAlign,
} ),
} );
const blockEditingMode = useBlockEditingMode();
let titleElement = <TagName { ...blockProps }>{ __( 'Title' ) }</TagName>;
if ( postType && postId ) {
titleElement = userCanEdit ? (
<PlainText
tagName={ TagName }
placeholder={ __( 'No Title' ) }
value={ rawTitle }
onChange={ setTitle }
__experimentalVersion={ 2 }
__unstableOnSplitAtEnd={ onSplitAtEnd }
{ ...blockProps }
/>
) : (
<TagName
{ ...blockProps }
dangerouslySetInnerHTML={ { __html: fullTitle?.rendered } }
/>
);
}
if ( isLink && postType && postId ) {
titleElement = userCanEdit ? (
<TagName { ...blockProps }>
<PlainText
tagName="a"
href={ link }
target={ linkTarget }
rel={ rel }
placeholder={ ! rawTitle.length ? __( 'No Title' ) : null }
value={ rawTitle }
onChange={ setTitle }
__experimentalVersion={ 2 }
__unstableOnSplitAtEnd={ onSplitAtEnd }
/>
</TagName>
) : (
<TagName { ...blockProps }>
<a
href={ link }
target={ linkTarget }
rel={ rel }
onClick={ ( event ) => event.preventDefault() }
dangerouslySetInnerHTML={ {
__html: fullTitle?.rendered,
} }
/>
</TagName>
);
}
return (
<>
{ blockEditingMode === 'default' && (
<BlockControls group="block">
<HeadingLevelDropdown
value={ level }
onChange={ ( newLevel ) =>
setAttributes( { level: newLevel } )
}
/>
<AlignmentControl
value={ textAlign }
onChange={ ( nextAlign ) => {
setAttributes( { textAlign: nextAlign } );
} }
/>
</BlockControls>
) }
<InspectorControls>
<PanelBody title={ __( 'Settings' ) }>
<ToggleControl
__nextHasNoMarginBottom
label={ __( 'Make title a link' ) }
onChange={ () => setAttributes( { isLink: ! isLink } ) }
checked={ isLink }
/>
{ isLink && (
<>
<ToggleControl
__nextHasNoMarginBottom
label={ __( 'Open in new tab' ) }
onChange={ ( value ) =>
setAttributes( {
linkTarget: value ? '_blank' : '_self',
} )
}
checked={ linkTarget === '_blank' }
/>
<TextControl
__nextHasNoMarginBottom
label={ __( 'Link rel' ) }
value={ rel }
onChange={ ( newRel ) =>
setAttributes( { rel: newRel } )
}
/>
</>
) }
</PanelBody>
</InspectorControls>
{ titleElement }
</>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment