diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 56df6c52598374..07ce2b8981f268 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -681,7 +681,7 @@ Display a graphic to represent this site. Update the block, and the changes appl
- **Name:** core/site-logo
- **Category:** theme
- **Supports:** align, color (~~background~~, ~~text~~), ~~alignWide~~, ~~html~~
-- **Attributes:** isLink, linkTarget, width
+- **Attributes:** isLink, linkTarget, shouldSyncIcon, width
## Site Tagline
diff --git a/packages/block-library/src/site-logo/block.json b/packages/block-library/src/site-logo/block.json
index 7dc85b04f3ccd9..1fe60445c5ecf7 100644
--- a/packages/block-library/src/site-logo/block.json
+++ b/packages/block-library/src/site-logo/block.json
@@ -17,12 +17,16 @@
"linkTarget": {
"type": "string",
"default": "_self"
+ },
+ "shouldSyncIcon": {
+ "type": "boolean"
}
},
"example": {
"viewportWidth": 500,
"attributes": {
- "width": 350
+ "width": 350,
+ "className": "block-editor-block-types-list__site-logo-example"
}
},
"supports": {
diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js
index 1cc4399a618c7a..fa482e65a44199 100644
--- a/packages/block-library/src/site-logo/edit.js
+++ b/packages/block-library/src/site-logo/edit.js
@@ -8,7 +8,12 @@ import { includes, pick } from 'lodash';
* WordPress dependencies
*/
import { isBlobURL } from '@wordpress/blob';
-import { useEffect, useState, useRef } from '@wordpress/element';
+import {
+ createInterpolateElement,
+ useEffect,
+ useState,
+ useRef,
+} from '@wordpress/element';
import { __, isRTL } from '@wordpress/i18n';
import {
MenuItem,
@@ -53,7 +58,7 @@ const ACCEPT_MEDIA_STRING = 'image/*';
const SiteLogo = ( {
alt,
- attributes: { align, width, height, isLink, linkTarget },
+ attributes: { align, width, height, isLink, linkTarget, shouldSyncIcon },
containerRef,
isSelected,
setAttributes,
@@ -61,6 +66,9 @@ const SiteLogo = ( {
logoUrl,
siteUrl,
logoId,
+ iconId,
+ setIcon,
+ canUserEdit,
} ) => {
const clientWidth = useClientWidth( containerRef, [ align ] );
const isLargeViewport = useViewportMatch( 'medium' );
@@ -84,6 +92,15 @@ const SiteLogo = ( {
};
}, [] );
+ useEffect( () => {
+ // Turn the `Use as site icon` toggle off if it is on but the logo and icon have
+ // fallen out of sync. This can happen if the toggle is saved in the `on` position,
+ // but changes are later made to the site icon in the Customizer.
+ if ( shouldSyncIcon && logoId !== iconId ) {
+ setAttributes( { shouldSyncIcon: false } );
+ }
+ }, [] );
+
useEffect( () => {
if ( ! isSelected ) {
setIsEditingImage( false );
@@ -250,6 +267,25 @@ const SiteLogo = ( {
);
+ const syncSiteIconHelpText = createInterpolateElement(
+ __(
+ 'Site Icons are what you see in browser tabs, bookmark bars, and within the WordPress mobile apps. To use a custom icon that is different from your site logo, use the Site Icon settings.'
+ ),
+ {
+ a: (
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
+
+ ),
+ }
+ );
+
return (
<>
@@ -286,6 +322,19 @@ const SiteLogo = ( {
/>
>
) }
+ { canUserEdit && (
+ <>
+ {
+ setAttributes( { shouldSyncIcon: value } );
+ setIcon( value ? logoId : undefined );
+ } }
+ checked={ !! shouldSyncIcon }
+ help={ syncSiteIconHelpText }
+ />
+ >
+ ) }
@@ -308,7 +357,7 @@ export default function LogoEdit( {
setAttributes,
isSelected,
} ) {
- const { width } = attributes;
+ const { className: styleClass, width, shouldSyncIcon } = attributes;
const [ logoUrl, setLogoUrl ] = useState();
const ref = useRef();
@@ -316,6 +365,7 @@ export default function LogoEdit( {
siteLogoId,
canUserEdit,
url,
+ siteIconId,
mediaItemData,
isRequestingMediaItem,
} = useSelect( ( select ) => {
@@ -328,6 +378,7 @@ export default function LogoEdit( {
const _readOnlyLogo = siteData?.site_logo;
const _canUserEdit = canUser( 'update', 'settings' );
const _siteLogoId = _canUserEdit ? _siteLogo : _readOnlyLogo;
+ const _siteIconId = siteSettings?.site_icon;
const mediaItem =
_siteLogoId &&
select( coreStore ).getMedia( _siteLogoId, {
@@ -339,6 +390,7 @@ export default function LogoEdit( {
_siteLogoId,
{ context: 'view' },
] );
+
return {
siteLogoId: _siteLogoId,
canUserEdit: _canUserEdit,
@@ -349,14 +401,59 @@ export default function LogoEdit( {
alt: mediaItem.alt_text,
},
isRequestingMediaItem: _isRequestingMediaItem,
+ siteIconId: _siteIconId,
};
}, [] );
+ const { getGlobalBlockCount } = useSelect( blockEditorStore );
const { editEntityRecord } = useDispatch( coreStore );
- const setLogo = ( newValue ) =>
+
+ useEffect( () => {
+ // Cleanup function to discard unsaved changes to the icon and logo when
+ // the block is removed.
+ return () => {
+ // Do nothing if the block is being rendered in the styles preview or the
+ // block inserter.
+ if (
+ styleClass?.includes(
+ 'block-editor-block-types-list__site-logo-example'
+ ) ||
+ styleClass?.includes(
+ 'block-editor-block-styles__block-preview-container'
+ )
+ ) {
+ return;
+ }
+
+ const logoBlockCount = getGlobalBlockCount( 'core/site-logo' );
+
+ // Only discard unsaved changes if we are removing the last Site Logo block
+ // on the page.
+ if ( logoBlockCount === 0 ) {
+ editEntityRecord( 'root', 'site', undefined, {
+ site_logo: undefined,
+ site_icon: undefined,
+ } );
+ }
+ };
+ }, [] );
+
+ const setLogo = ( newValue, shouldForceSync = false ) => {
+ // `shouldForceSync` is used to force syncing when the attribute
+ // may not have updated yet.
+ if ( shouldSyncIcon || shouldForceSync ) {
+ setIcon( newValue );
+ }
+
editEntityRecord( 'root', 'site', undefined, {
site_logo: newValue,
} );
+ };
+
+ const setIcon = ( newValue ) =>
+ editEntityRecord( 'root', 'site', undefined, {
+ site_icon: newValue,
+ } );
let alt = null;
if ( mediaItemData ) {
@@ -365,7 +462,24 @@ export default function LogoEdit( {
setLogoUrl( mediaItemData.url );
}
}
- const onSelectLogo = ( media ) => {
+
+ const onInitialSelectLogo = ( media ) => {
+ // Initialize the syncSiteIcon toggle. If we currently have no Site logo and no
+ // site icon, automatically sync the logo to the icon.
+ if ( shouldSyncIcon === undefined ) {
+ const shouldForceSync = ! siteIconId;
+ setAttributes( { shouldSyncIcon: shouldForceSync } );
+
+ // Because we cannot rely on the `shouldSyncIcon` attribute to have updated by
+ // the time `setLogo` is called, pass an argument to force the syncing.
+ onSelectLogo( media, shouldForceSync );
+ return;
+ }
+
+ onSelectLogo( media );
+ };
+
+ const onSelectLogo = ( media, shouldForceSync = false ) => {
if ( ! media ) {
return;
}
@@ -377,7 +491,7 @@ export default function LogoEdit( {
return;
}
- setLogo( media.id );
+ setLogo( media.id, shouldForceSync );
};
const onRemoveLogo = () => {
@@ -423,6 +537,9 @@ export default function LogoEdit( {
setLogo={ setLogo }
logoId={ mediaItemData?.id || siteLogoId }
siteUrl={ url }
+ setIcon={ setIcon }
+ iconId={ siteIconId }
+ canUserEdit={ canUserEdit }
/>
);
}
@@ -481,7 +598,7 @@ export default function LogoEdit( {
) }
{ ! logoUrl && canUserEdit && (
true,
+ 'type' => 'integer',
+ 'description' => __( 'Site icon.' ),
+ )
+ );
+}
+
+add_action( 'rest_api_init', 'register_block_core_site_icon_setting', 10 );
+
/**
* Registers the `core/site-logo` block on the server.
*/
diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js
index 867628dc9f41d6..2e87daf99e7c7d 100644
--- a/packages/editor/src/components/entities-saved-states/index.js
+++ b/packages/editor/src/components/entities-saved-states/index.js
@@ -24,6 +24,7 @@ const TRANSLATED_SITE_PROPERTIES = {
title: __( 'Title' ),
description: __( 'Tagline' ),
site_logo: __( 'Logo' ),
+ site_icon: __( 'Icon' ),
show_on_front: __( 'Show on front' ),
page_on_front: __( 'Page on front' ),
};