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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Editor: Let writers know when the featured image is also in the post, and hide it on the post where the theme supports that.
1 change: 1 addition & 0 deletions projects/plugins/jetpack/extensions/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"contact-info",
"donations",
"eventbrite",
"featured-image-duplicate",
"field-file",
"gif",
"google-calendar",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { CheckboxControl, Notice } from '@wordpress/components';
import { createHigherOrderComponent } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { PostPreviewButton, store as editorStore } from '@wordpress/editor';
import { useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { HIDE_META_KEY, useFeaturedImageDuplicate } from '.';
import type { ComponentType } from 'react';
import './editor.scss';

/**
* Notice and checkbox shown under the featured image.
*
* @return Content, or null when there's nothing to show.
*/
function FeaturedImageDuplicate() {
const { postType, isHidden, showNotice, showCheckbox, dismiss, setHidden } =
useFeaturedImageDuplicate();

if ( ! showNotice && ! showCheckbox ) {
return null;
}

// Separate objects, so minification can't merge the __() calls and break string extraction.
const copy =
postType === 'page'
? {
notice: __( 'This image is also in your page, so it may appear twice.', 'jetpack' ),
label: __( 'Hide featured image on this page', 'jetpack' ),
help: __( 'It still shows as the thumbnail and when the page is shared.', 'jetpack' ),
}
: {
notice: __( 'This image is also in your post, so it may appear twice.', 'jetpack' ),
label: __( 'Hide featured image on this post', 'jetpack' ),
help: __( 'It still shows as the thumbnail and when the post is shared.', 'jetpack' ),
};

return (
<div className="jetpack-featured-image-duplicate">
{ showNotice && (
<Notice
status="info"
onRemove={ dismiss }
className="jetpack-featured-image-duplicate__notice"
>
<p>{ copy.notice }</p>
{ /* @ts-expect-error -- Its JSDoc types mark the optional props as required. */ }
<PostPreviewButton
className="components-button is-link"
textContent={ __( 'Preview', 'jetpack' ) }
/>
</Notice>
) }
{ showCheckbox && (
<CheckboxControl
__nextHasNoMarginBottom
label={ copy.label }
help={ copy.help }
checked={ isHidden }
onChange={ setHidden }
/>
) }
</div>
);
}

/**
* Adds the notice under core's featured image control.
*/
export const withDuplicateNotice = createHigherOrderComponent(
OriginalComponent => props => (
<>
<OriginalComponent { ...props } />
<FeaturedImageDuplicate />
</>
),
'withDuplicateNotice' // Name for React DevTools.
);

/**
* Stops hiding the featured image once it's no longer in the content.
*
* Registered as an editor plugin so it runs even when the sidebar is closed.
*
* @return Nothing.
*/
export function HiddenSync() {
const { isHidden, isDuplicate, isResolved, setHidden } = useFeaturedImageDuplicate();

useEffect( () => {
if ( isHidden && isResolved && ! isDuplicate ) {
setHidden( false );
}
}, [ isHidden, isResolved, isDuplicate, setHidden ] );

return null;
}

type FeaturedImageBlockProps = {
context?: { postId?: number; queryId?: number };
};

/**
* The template's Featured Image block, hidden when this post hides its featured image.
*
* @param props - Props.
* @param props.BlockEdit - Original block edit component.
* @return The block, or null when hidden.
*/
function FeaturedImageBlock( {
BlockEdit,
...props
}: FeaturedImageBlockProps & { BlockEdit: ComponentType< FeaturedImageBlockProps > } ) {
const { postId, queryId } = props.context ?? {};
const isHidden = useSelect(
select => {
// Inside a Query Loop it's a list of posts, not this post's header.
if ( queryId !== undefined ) {
return false;
}
const editor = select( editorStore );
const meta = ( editor.getEditedPostAttribute( 'meta' ) ?? {} ) as Record< string, unknown >;
return postId === editor.getCurrentPostId() && !! meta[ HIDE_META_KEY ];
},
[ postId, queryId ]
);

return isHidden ? null : <BlockEdit { ...props } />;
}

/**
* Hides the template's Featured Image block in the editor too, matching the site.
*/
export const withHiddenFeaturedImageBlock = createHigherOrderComponent(
BlockEdit => props =>
props.name === 'core/post-featured-image' ? (
<FeaturedImageBlock BlockEdit={ BlockEdit } { ...props } />
) : (
<BlockEdit { ...props } />
),
'withHiddenFeaturedImageBlock'
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
getJetpackExtensionAvailability,
registerJetpackPlugin,
} from '@automattic/jetpack-shared-extension-utils';
import { addFilter } from '@wordpress/hooks';
import { HiddenSync, withDuplicateNotice, withHiddenFeaturedImageBlock } from './components';
import { FEATURE_NAME } from '.';

if ( getJetpackExtensionAvailability( FEATURE_NAME ).available ) {
addFilter( 'editor.PostFeaturedImage', `jetpack/${ FEATURE_NAME }`, withDuplicateNotice ); // Notice and checkbox under the featured image.
addFilter( 'editor.BlockEdit', `jetpack/${ FEATURE_NAME }`, withHiddenFeaturedImageBlock ); // Hide the template's Featured Image block when ticked.
}

registerJetpackPlugin( FEATURE_NAME, { render: HiddenSync } ); // Untick checkbox once the duplicate is gone.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.jetpack-featured-image-duplicate {
display: flex;
flex-direction: column;
gap: 12px;
}

.jetpack-featured-image-duplicate__notice {
margin: 0;

p {
margin-block: 0 4px;
margin-inline: 0;
}

// Core greys out the Preview link while it autosaves; keep it blue.
.components-button.is-link:is(:active, :disabled, [aria-disabled="true"]) {
color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
}
}
Loading
Loading