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' ), };