Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/block-library/src/query-title/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"type": "search"
}
},
"usesContext": [ "query" ],
"supports": {
"align": [ "wide", "full" ],
"html": false,
Expand Down
59 changes: 58 additions & 1 deletion packages/block-library/src/query-title/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ import { __, _x, sprintf } from '@wordpress/i18n';
* Internal dependencies
*/
import { useArchiveLabel } from './use-archive-label';
import { usePostTypeLabel } from './use-post-type-label';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';

const SUPPORTED_TYPES = [ 'archive', 'search' ];
const SUPPORTED_TYPES = [ 'archive', 'search', 'post-type' ];

export default function QueryTitleEdit( {
attributes: {
Expand All @@ -39,8 +40,10 @@ export default function QueryTitleEdit( {
showSearchTerm,
},
setAttributes,
context: { query },
} ) {
const { archiveTypeLabel, archiveNameLabel } = useArchiveLabel();
const { postTypeLabel } = usePostTypeLabel( query?.postType );
const dropdownMenuProps = useToolsPanelDropdownMenuProps();

const TagName = `h${ level }`;
Expand Down Expand Up @@ -174,6 +177,60 @@ export default function QueryTitleEdit( {
);
}

if ( type === 'post-type' ) {
let title;
if ( postTypeLabel ) {
if ( showPrefix ) {
title = sprintf(
/* translators: %s: Singular post type name of the queried object */
__( 'Post Type: "%s"' ),
postTypeLabel
);
} else {
title = postTypeLabel;
}
} else {
title = showPrefix ? __( 'Post Type: Name' ) : __( 'Name' );
}

titleElement = (
<>
<InspectorControls>
<ToolsPanel
label={ __( 'Settings' ) }
resetAll={ () =>
setAttributes( {
showPrefix: true,
} )
}
dropdownMenuProps={ dropdownMenuProps }
>
<ToolsPanelItem
hasValue={ () => ! showPrefix }
label={ __( 'Show post type label' ) }
onDeselect={ () =>
setAttributes( { showPrefix: true } )
}
isShownByDefault
>
<ToggleControl
__nextHasNoMarginBottom
label={ __( 'Show post type label' ) }
onChange={ () =>
setAttributes( {
showPrefix: ! showPrefix,
} )
}
checked={ showPrefix }
/>
</ToolsPanelItem>
</ToolsPanel>
</InspectorControls>
<TagName { ...blockProps }>{ title }</TagName>
</>
);
}

return (
<>
<BlockControls group="block">
Expand Down
35 changes: 30 additions & 5 deletions packages/block-library/src/query-title/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@

/**
* Renders the `core/query-title` block on the server.
* For now it only supports Archive title,
* For now it supports Archive title, Search title, and Post Type Label,
* using queried object information
*
* @since 5.8.0
*
* @param array $attributes Block attributes.
* @param array $attributes Block attributes.
* @param array $_content Block content.
* @param object $block Block instance.
*
* @return string Returns the query title based on the queried object.
*/
function render_block_core_query_title( $attributes ) {
function render_block_core_query_title( $attributes, $content, $block ) {
$type = isset( $attributes['type'] ) ? $attributes['type'] : null;
$is_archive = is_archive();
$is_search = is_search();
$post_type = isset( $block->context['query']['postType'] ) ? $block->context['query']['postType'] : get_post_type();

if ( ! $type ||
( 'archive' === $type && ! $is_archive ) ||
( 'search' === $type && ! $is_search )
) {
( 'search' === $type && ! $is_search ) ||
( 'post-type' === $type && ! $post_type )
) {
return '';
}
$title = '';
Expand All @@ -48,6 +53,26 @@ function render_block_core_query_title( $attributes ) {
);
}
}
if ( 'post-type' === $type ) {
$post_type_object = get_post_type_object( $post_type );

if ( ! $post_type_object ) {
return '';
}

$post_type_name = $post_type_object->labels->singular_name;
$show_prefix = isset( $attributes['showPrefix'] ) ? $attributes['showPrefix'] : true;

if ( $show_prefix ) {
$title = sprintf(
/* translators: %s is the post type name. */
__( 'Post Type: "%s"' ),
$post_type_name
);
} else {
$title = $post_type_name;
}
}

$tag_name = isset( $attributes['level'] ) ? 'h' . (int) $attributes['level'] : 'h1';
$align_class_name = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}";
Expand Down
34 changes: 34 additions & 0 deletions packages/block-library/src/query-title/use-post-type-label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* WordPress dependencies
*/
import { store as coreStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';

/**
* Hook to fetch the singular label for the current post type.
*
* @param {string} contextPostType Context provided post type.
*/
export function usePostTypeLabel( contextPostType ) {
const currentPostType = useSelect( ( select ) => {
// Access core/editor by string to avoid @wordpress/editor dependency.
// eslint-disable-next-line @wordpress/data-no-store-string-literals
const { getCurrentPostType } = select( 'core/editor' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we import the store name here instead of suppressing the eslint error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the review!

I did this to avoid having @wordpress/editor as a dependency for @wordpress/block-editor. A similar pattern is used in useArchiveLabel:

// @wordpress/block-library should not depend on @wordpress/editor.
// Blocks can be loaded into a *non-post* block editor, so to avoid
// declaring @wordpress/editor as a dependency, we must access its
// store by string.
// The solution here is to split WP specific blocks from generic blocks.
// eslint-disable-next-line @wordpress/data-no-store-string-literals
const { getCurrentPostId, getCurrentPostType, getCurrentTemplateId } =
select( 'core/editor' );

Would it help if I add a comment referencing this?

Copy link
Contributor

@ryanwelcher ryanwelcher Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add the reference, yes. That way there is no confusion for reviewers or other engineers who reference this code. Thanks for the explanation!

return getCurrentPostType();
}, [] );

// Fetch the post type label from the core data store
return useSelect(
( select ) => {
const { getPostType } = select( coreStore );
const postTypeSlug = contextPostType || currentPostType;
const postType = getPostType( postTypeSlug );

// Return the singular name of the post type
return {
postTypeLabel: postType ? postType.labels.singular_name : '',
};
},
[ contextPostType, currentPostType ]
);
}
13 changes: 13 additions & 0 deletions packages/block-library/src/query-title/variations.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ const variations = [
},
scope: [ 'inserter' ],
},
{
isDefault: false,
name: 'post-type-label',
title: __( 'Post Type Label' ),
description: __(
'Display the post type label based on the queried object.'
),
icon: title,
attributes: {
type: 'post-type',
},
scope: [ 'inserter' ],
},
];

/**
Expand Down
Loading