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
35 changes: 24 additions & 11 deletions lib/compat/wordpress-7.1/admin-bar.php

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Have worked on this here, Not sure is it the correct thing to change. As it's punted to 7.2 not sure if we are creating a new directory in compat and add the changes there?

Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,43 @@ function gutenberg_admin_bar_site_icon( WP_Admin_Bar $wp_admin_bar ): void {
return;
}

if ( isset( $node->meta['class'] ) && false !== strpos( $node->meta['class'], 'has-site-icon' ) ) {
// Bail if this has already run, or if core or another plugin has added an
// icon. Matches `supports-site-icon` as well as `has-site-icon`.
if ( isset( $node->meta['class'] ) && false !== strpos( $node->meta['class'], 'site-icon' ) ) {
return;
}

/** This filter is documented in wp-includes/admin-bar.php */
$show_site_icons = apply_filters( 'wp_admin_bar_show_site_icons', true );
if ( true !== $show_site_icons || ! has_site_icon() ) {
if ( true !== $show_site_icons ) {
return;
}

$site_icon_url = get_site_icon_url( 32 );
$site_icon_url_2x = get_site_icon_url( 64 );
$srcset = ( $site_icon_url_2x && $site_icon_url !== $site_icon_url_2x ) ? sprintf( ' srcset="%s 2x"', esc_url( $site_icon_url_2x ) ) : '';
$site_icon = sprintf(
'<img class="site-icon" src="%s"%s alt="" width="20" height="20" />',
esc_url( $site_icon_url ),
$srcset
);
/*
* `supports-site-icon` is added whether or not an icon is set, so that the
* editor can tell an unset icon apart from icons being filtered off and
* knows whether it may render one after the Site Icon is saved.
*/
$classes = array( 'supports-site-icon' );
$site_icon = '';

if ( has_site_icon() ) {
$site_icon_url = get_site_icon_url( 32 );
$site_icon_url_2x = get_site_icon_url( 64 );
$srcset = ( $site_icon_url_2x && $site_icon_url !== $site_icon_url_2x ) ? sprintf( ' srcset="%s 2x"', esc_url( $site_icon_url_2x ) ) : '';
$site_icon = sprintf(
'<img class="site-icon" src="%s"%s alt="" width="20" height="20" />',
esc_url( $site_icon_url ),
$srcset
);
$classes[] = 'has-site-icon';
}

$wp_admin_bar->add_node(
array(
'id' => 'site-name',
'title' => $site_icon . $node->title,
'meta' => array( 'class' => 'has-site-icon' ),
'meta' => array( 'class' => implode( ' ', $classes ) ),
)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { privateApis as editorPrivateApis } from '@wordpress/editor';
import { useUpdatePostLinkListener } from './listener-hooks';
import { unlock } from '../../lock-unlock';

const { useSyncAdminBarSiteIcon } = unlock( editorPrivateApis );

/**
* Data component used for initializing the editor and re-initializes
Expand All @@ -8,5 +12,6 @@ import { useUpdatePostLinkListener } from './listener-hooks';
*/
export default function EditorInitialization() {
useUpdatePostLinkListener();
useSyncAdminBarSiteIcon();
return null;
}
5 changes: 4 additions & 1 deletion packages/edit-site/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ import SaveHub from '../save-hub';
import SavePanel from '../save-panel';

const { useLocation } = unlock( routerPrivateApis );
const { useStyle, UploadProgressSnackbar } = unlock( editorPrivateApis );
const { useStyle, UploadProgressSnackbar, useSyncAdminBarSiteIcon } =
unlock( editorPrivateApis );

const ANIMATION_DURATION = 0.3;
const CONTENT_COLOR = { background: '#ffffff' };

function Layout() {
useSyncAdminBarSiteIcon();

const { query, name: routeKey, areas, widths } = useLocation();
// Force canvas to 'view' on notfound route to show the error message and allow navigation.
const canvas = routeKey === 'notfound' ? 'view' : query?.canvas ?? 'view';
Expand Down
1 change: 1 addition & 0 deletions packages/editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

- Device Preview: Keep tablet and mobile iframe widths inside their responsive breakpoints so media queries remain accurate at browser zoom levels.
- Document tools: Fix icon button focus styles to use the design system `outset-ring__focus` mixin ([#81115](https://github.com/WordPress/gutenberg/pull/81115)).
- Update the admin bar's site icon when the Site Icon is saved, instead of showing the previous icon until the page is reloaded. Saving the icon also refetches the site's base data, which holds the icon URL derived from it ([#81483](https://github.com/WordPress/gutenberg/issues/81483)).

## 14.52.0 (2026-07-29)

Expand Down
110 changes: 110 additions & 0 deletions packages/editor/src/hooks/use-sync-admin-bar-site-icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect, useRef } from '@wordpress/element';
import { store as coreStore } from '@wordpress/core-data';

const SITE_NAME_SELECTOR = '#wp-admin-bar-site-name';
const SITE_ICON_SELECTOR = ':scope > img.site-icon';
const SUPPORTS_SITE_ICON_CLASS = 'supports-site-icon';
const HAS_SITE_ICON_CLASS = 'has-site-icon';
const SITE_ICON_SIZE = 20;

/**
* Keeps the site icon in the admin bar in sync with the saved Site Icon.
*
* The admin bar is rendered by PHP before the editor mounts, so its icon would
* otherwise keep showing the previous icon until the page is reloaded. The
* markup mirrored here is built by `gutenberg_admin_bar_site_icon()` in
* lib/compat/wordpress-7.1/admin-bar.php, which also marks the node with
* `supports-site-icon` to say whether an icon may be shown at all.
*/
export default function useSyncAdminBarSiteIcon() {
const { savedIconId, iconUrl } = useSelect( ( select ) => {
const { getEntityRecord } = select( coreStore );
return {
// The persisted icon, which only changes once a save completes.
savedIconId: getEntityRecord( 'root', 'site' )?.site_icon,
// The icon's URL is derived server-side, so it lives on the base
// entity rather than alongside the ID in the site settings.
iconUrl: getEntityRecord( 'root', '__unstableBase' )?.site_icon_url,
};
}, [] );
const { invalidateResolution } = useDispatch( coreStore );

// Saving the icon leaves `site_icon_url` stale, because it is derived from
// `site_icon` but belongs to a different entity that nothing refetches.
const savedIconIdRef = useRef();
useEffect( () => {
const previousIconId = savedIconIdRef.current;
savedIconIdRef.current = savedIconId;

if (
previousIconId === undefined ||
savedIconId === undefined ||
previousIconId === savedIconId
) {
return;
}

invalidateResolution( 'getEntityRecord', [ 'root', '__unstableBase' ] );
}, [ savedIconId, invalidateResolution ] );

// Tracks the icon the admin bar is showing. Seeded on the first resolution,
// where the server-rendered icon is already correct, so that a page load
// never rewrites the markup it just received.
const renderedIconUrlRef = useRef();
useEffect( () => {
if ( iconUrl === undefined ) {
return;
}

if ( renderedIconUrlRef.current === undefined ) {
renderedIconUrlRef.current = iconUrl;
return;
}

if ( renderedIconUrlRef.current === iconUrl ) {
return;
}

renderedIconUrlRef.current = iconUrl;

const siteName = document.querySelector( SITE_NAME_SELECTOR );

// The class is absent when the `wp_admin_bar_show_site_icons` filter
// turns icons off, which is not otherwise distinguishable from an
// unset icon.
if ( ! siteName?.classList.contains( SUPPORTS_SITE_ICON_CLASS ) ) {
return;
}

const link = siteName.querySelector( ':scope > .ab-item' );

if ( ! link ) {
return;
}

let image = link.querySelector( SITE_ICON_SELECTOR );

if ( ! iconUrl ) {
image?.remove();
siteName.classList.remove( HAS_SITE_ICON_CLASS );
return;
}

if ( ! image ) {
image = document.createElement( 'img' );
image.className = 'site-icon';
image.alt = '';
image.width = SITE_ICON_SIZE;
image.height = SITE_ICON_SIZE;
link.prepend( image );
}

// `site_icon_url` is the full-size icon, so the 2x `srcset` rendered
// alongside the initial markup is both unnecessary and stale — leaving
// it would keep showing the previous icon on high-density screens.
image.removeAttribute( 'srcset' );
image.src = iconUrl;
siteName.classList.add( HAS_SITE_ICON_CLASS );
}, [ iconUrl ] );
}
2 changes: 2 additions & 0 deletions packages/editor/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { StyleBookPreview } from './components/style-book';
import { useGlobalStyles, useStyle } from './components/global-styles/hooks';
import { GlobalStylesActionMenu } from './components/global-styles/menu';
import UploadProgressSnackbar from './components/upload-progress-snackbar';
import useSyncAdminBarSiteIcon from './hooks/use-sync-admin-bar-site-icon';

const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis;

Expand All @@ -40,6 +41,7 @@ lock( privateApis, {
ViewMoreMenuGroup,
ResizableEditor,
UploadProgressSnackbar,
useSyncAdminBarSiteIcon,
registerCoreBlockBindingsSources,
// Global Styles
GlobalStylesUIWrapper,
Expand Down
Loading