Skip to content
Open
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 includes/class-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static function init() {
if ( wp_is_block_theme() ) {
require_once NEWSPACK_ABSPATH . 'src/blocks/avatar/class-avatar-block.php';
require_once NEWSPACK_ABSPATH . 'src/blocks/byline/class-byline-block.php';
require_once NEWSPACK_ABSPATH . 'src/blocks/featured-image-caption/class-featured-image-caption-block.php';
require_once NEWSPACK_ABSPATH . 'src/blocks/author-profile-social/class-author-profile-social-block.php';
require_once NEWSPACK_ABSPATH . 'src/blocks/author-social-link/class-author-social-link-block.php';
require_once NEWSPACK_ABSPATH . 'src/blocks/copyright-date/class-copyright-date-block.php';
Expand Down
45 changes: 45 additions & 0 deletions src/blocks/featured-image-caption/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "newspack/featured-image-caption",
"title": "Featured Image Caption",
"category": "newspack",
"textdomain": "newspack-plugin",
"attributes": {
"customCaption": {
"type": "string",
"default": ""
}
},
"usesContext": [ "postId", "postType" ],
"supports": {
"html": false,
"color": {
"gradients": true,
"__experimentalDefaultControls": {
"background": true,
"text": true,
"link": true
}
},
"spacing": {
"margin": true,
"padding": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"textAlign": true,
"__experimentalFontFamily": true,
"__experimentalFontWeight": true,
"__experimentalFontStyle": true,
"__experimentalTextTransform": true,
"__experimentalTextDecoration": true,
"__experimentalLetterSpacing": true,
"__experimentalDefaultControls": {
"fontSize": true,
"fontAppearance": true
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
/**
* Featured Image Caption Block.
*
* @package Newspack
*/

namespace Newspack\Blocks\FeaturedImageCaption;

defined( 'ABSPATH' ) || exit;

/**
* Featured Image Caption Block class.
*/
final class Featured_Image_Caption_Block {

/**
* Initializer.
*/
public static function init() {
\add_action( 'init', [ __CLASS__, 'register_block' ] );
}

/**
* Register the block.
*/
public static function register_block() {
register_block_type_from_metadata(
__DIR__ . '/block.json',
[
'render_callback' => [ __CLASS__, 'render_block' ],
]
);
}

/**
* Block render callback.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param \WP_Block $block Block instance.
* @return string Rendered block.
*/
public static function render_block( $attributes, $content, $block ) {
$post_id = ! empty( $block->context['postId'] ) ? $block->context['postId'] : get_the_ID();
if ( ! $post_id ) {
return '';
}

$featured_image_id = get_post_thumbnail_id( $post_id );
if ( ! $featured_image_id ) {
return '';
}

$custom_caption = $attributes['customCaption'] ?? '';

if ( $custom_caption ) {
$output = wp_kses_post( $custom_caption );
} else {
$caption = wp_kses_post( wp_get_attachment_caption( $featured_image_id ) );
$credit = '';

if ( class_exists( '\Newspack\Newspack_Image_Credits' ) ) {
$credit = \Newspack\Newspack_Image_Credits::get_media_credit_string( $featured_image_id );
}

$output = trim( $caption );
if ( $output && $credit ) {
$output .= ' ' . $credit;
} elseif ( $credit ) {
$output = $credit;
}
}

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

$wrapper_attributes = get_block_wrapper_attributes();
return sprintf( '<figcaption %1$s>%2$s</figcaption>', $wrapper_attributes, $output );
}
}
Featured_Image_Caption_Block::init();
73 changes: 73 additions & 0 deletions src/blocks/featured-image-caption/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useBlockProps, RichText } from '@wordpress/block-editor';
import { useEntityProp } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { __, sprintf } from '@wordpress/i18n';

/**
* Generates the credit text for the media credit and organization.
*
* @param {string} mediaCredit The media credit.
* @param {string} organization The organization associated with the media credit. Optional.
*
* @return {string} The formatted credit text.
*/
const generateCreditText = ( mediaCredit, organization ) => {
if ( mediaCredit && organization ) {
return sprintf(
/* translators: 1: media credit, 2: organization */
__( 'Credit: %1$s / %2$s', 'newspack-plugin' ),
mediaCredit,
organization
);
}

return sprintf(
/* translators: %s: media credit */
__( 'Credit: %s', 'newspack-plugin' ),
mediaCredit
);
};

export const Edit = ( { attributes, setAttributes, context: { postType, postId } } ) => {
const blockProps = useBlockProps();

const [ featuredImage ] = useEntityProp( 'postType', postType, 'featured_media', postId );

const { caption, credit } = useSelect(
select => {
if ( ! featuredImage ) {
return {};
}
Comment on lines +36 to +40
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

This new block adds logic to derive caption/credit from the featured image entity (and format credits) but there are no accompanying unit tests. Similar blocks in this repo have Jest/Testing Library coverage; consider adding tests for “no featured image”, “caption only”, “credit only”, and “caption + org credit” to prevent regressions.

Copilot uses AI. Check for mistakes.
const media = select( 'core' ).getMedia( featuredImage );
if ( ! media ) {
return {};
}
return {
caption: media.caption?.raw || '',
credit: media.meta?._media_credit ? generateCreditText( media.meta._media_credit, media.meta?._navis_media_credit_org ) : '',
};
},
[ featuredImage ]
);

const defaultText = [ caption, credit ].filter( Boolean ).join( ' ' );

if ( ! featuredImage ) {
return (
<figcaption { ...blockProps }>
<span className="featured-image-caption-placeholder">{ __( 'Featured image caption.', 'newspack-plugin' ) }</span>
</figcaption>
Comment on lines +57 to +59
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The placeholder shown when no featured image is set does not match the PR testing instructions (it doesn’t prompt the user to add a featured image). Consider updating this placeholder copy to explicitly instruct the user to add/set a featured image so the editor state is self-explanatory.

Copilot uses AI. Check for mistakes.
);
}

return (
<RichText
{ ...blockProps }
tagName="figcaption"
value={ attributes.customCaption }
onChange={ val => setAttributes( { customCaption: val } ) }
placeholder={ defaultText || __( 'Write caption…', 'newspack-plugin' ) }
allowedFormats={ [ 'core/bold', 'core/italic', 'core/link' ] }
/>
);
};
35 changes: 35 additions & 0 deletions src/blocks/featured-image-caption/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { caption as icon } from '@wordpress/icons';

/**
* Internal dependencies
*/
import metadata from './block.json';
import { Edit } from './edit';
import colors from '../../../packages/colors/colors.module.scss';
import './style.scss';

export const title = __( 'Featured Image Caption', 'newspack-plugin' );

const { name } = metadata;

export { metadata, name };

export const settings = {
title,
icon: {
src: icon,
foreground: colors[ 'primary-400' ],
},
keywords: [
__( 'caption', 'newspack-plugin' ),
__( 'featured image', 'newspack-plugin' ),
__( 'credit', 'newspack-plugin' ),
__( 'newspack', 'newspack-plugin' ),
],
description: __( 'Display the featured image caption and credit.', 'newspack-plugin' ),
edit: Edit,
};
5 changes: 5 additions & 0 deletions src/blocks/featured-image-caption/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.wp-block-newspack-featured-image-caption {
.featured-image-caption-placeholder {
opacity: 0.6;
}
}
3 changes: 3 additions & 0 deletions src/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as correctionBox from './correction-box';
import * as correctionItem from './correction-item';
import * as avatar from './avatar';
import * as byline from './byline';
import * as featuredImageCaption from './featured-image-caption';
import * as authorProfileSocial from './author-profile-social';
import * as authorSocialLink from './author-social-link';
import * as collections from './collections';
Expand All @@ -33,6 +34,7 @@ export const blocks = [
correctionItem,
avatar,
byline,
featuredImageCaption,
authorProfileSocial,
authorSocialLink,
collections,
Expand All @@ -51,6 +53,7 @@ const blockThemeBlocks = [
'newspack/avatar',
'newspack/byline',
'newspack/copyright-date',
'newspack/featured-image-caption',
'newspack/my-account-button',
];

Expand Down
Loading