Skip to content

Commit

Permalink
Editor: Display Site Icon (if one is set) in Gutenberg Fullscreen Mode (
Browse files Browse the repository at this point in the history
#22952)

* Register setting for site icon url

Info about site icon was previously unavailable in the Gutenberg editor. Registering a setting addresses the problem by adding site icon url as a field to the wp/v2/settings REST API. The information can then be fetched by the client.

* Display user uploaded site icon for edit site if available

* Apply default site icon styling only to default icon

* Remove unnecessary whitespace

* Fix PHP formatting with linter

* Use isResolving to determine site icon loading state

Previously, we were using hasFinishedResolution and inverting the resulting boolean to determine site icon loading state.

Implementing isResolving instead let's us determine the same loading state without boolean inversion.

* Add unit tests for edit-site

* Refactor button icon logic

Previously, to render the default icon, we were passing an SVG through the icon prop to the @wordpress/components Button. The data provided for user uploaded site icons, however, are incompatible. We receive custom URLs, and not SVGs, for custom site icons.

Rather than support the logic for both Button icon props and site icon image tags, we do away with the Button icon prop entirely.

We now explicitly define the children for Button, which consolidates the display logic for both default and custom site icons.

* Update styling for site icon

* Add user uploaded site icon to post editor

* Display tooltip for editor back button

* Use getEntityRecord to fetch site icon URL for edit-site

useEntityProp was pulling information about site icon url from the core-data store. Although the method worked, it relied on a context provider around the block editor.

The context is unnecessary in this instance, and so, to prevent unnecessary coupling, we replace useEntityProp with getEntityRecord instead.

* Use getEntityRecord to fetch site icon URL for edit-post

* Register site icon url with rest index filter

All roles that could access the editor but that could not edit site configurations (Contributor, Author, and Editor) received a 403 response when querying the settings WordPress REST API. This is because site configurations and the settings API are only accessible to Admins and Super Admins.

We want the read-only site icon url to be accessible to all roles. In order to accomplish that, we opt to register the site icon url with the WordPress REST index endpoint instead of the WordPress REST settings endpoint.

* Fetch site icon url from the WordPress REST index

* Add inline documentation when registering site icon url

* Add __unstable prefix to base entity

Because of limitations with permissioning, we are adding site icon url to the WP Rest index endpoint instead of the settings endpoint.

Once the settings endpoint is updated and accessible by more than the admin and super admin roles, we will remove the base entity (hence the unstable prefix). At that time, we will subsequently add site icon url to the settings endpoint.

* Preload index endpoint

* Translate alt text for site icon in edit post

* Translate alt text for site icon in edit site

* Preload edit post index endpoint

Data from WordPress REST endpoints can be preloaded. We're effectively asking the server to inject a response on the initial page load that we know will always be triggered on that page. The alternative is waiting for the page to load, waiting for the scripts to executre, and then waiting for a response to a network request.

Preloading the site icon means that upon initial page load, rather than a blank placeholder, the icon will be displayed almost immediately. Edit post preloading happens in core, and the endpoints we specify for preloading are extensible through the block_editor_preload_paths filter.

This change preloads the endpoint that serves site icon data to edit post.

* Add core trac ticket annotation
  • Loading branch information
jeyip authored Jul 13, 2020
1 parent 93d301d commit 73f3a1d
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 35 deletions.
20 changes: 20 additions & 0 deletions gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,23 @@ function gutenberg_rest_nonce() {
exit( wp_create_nonce( 'wp_rest' ) );
}
add_action( 'wp_ajax_gutenberg_rest_nonce', 'gutenberg_rest_nonce' );


/**
* Exposes the site icon url to the Gutenberg editor through the WordPress REST
* API. The site icon url should instead be fetched from the wp/v2/settings
* endpoint when https://github.com/WordPress/gutenberg/pull/19967 is complete.
*
* @since 8.2.1
*
* @param WP_REST_Response $response Response data served by the WordPress REST index endpoint.
* @return WP_REST_Response
*/
function register_site_icon_url( $response ) {
$data = $response->data;
$data['site_icon_url'] = get_site_icon_url();
$response->set_data( $data );
return $response;
}

add_filter( 'rest_index', 'register_site_icon_url' );
17 changes: 17 additions & 0 deletions lib/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,20 @@ function gutenberg_output_html_nav_menu_item( $item_output, $item, $depth, $args
return $item_output;
}
add_filter( 'walker_nav_menu_start_el', 'gutenberg_output_html_nav_menu_item', 10, 4 );

/**
* Amends the paths to preload when initializing edit post.
*
* @see https://core.trac.wordpress.org/ticket/50606
*
* @since 8.4.0
*
* @param array $preload_paths Default path list that will be preloaded.
* @return array Modified path list to preload.
*/
function gutenberg_preload_edit_post( $preload_paths ) {
$additional_paths = array( '/?context=edit' );
return array_merge( $preload_paths, $additional_paths );
}

add_filter( 'block_editor_preload_paths', 'gutenberg_preload_edit_post' );
2 changes: 1 addition & 1 deletion lib/edit-site-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ function gutenberg_edit_site_init( $hook ) {
// Preload block editor paths.
// most of these are copied from edit-forms-blocks.php.
$preload_paths = array(
'/',
'/?context=edit',
'/wp/v2/types?context=edit',
'/wp/v2/taxonomies?per_page=100&context=edit',
'/wp/v2/pages?per_page=100&context=edit',
Expand Down
6 changes: 6 additions & 0 deletions packages/core-data/src/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import { apiFetch, select } from './controls';
export const DEFAULT_ENTITY_KEY = 'id';

export const defaultEntities = [
{
label: __( 'Base' ),
name: '__unstableBase',
kind: 'root',
baseURL: '',
},
{
label: __( 'Site' ),
name: 'site',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,64 @@ import { get } from 'lodash';
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { Button } from '@wordpress/components';
import { Button, Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url';
import { wordpress } from '@wordpress/icons';

function FullscreenModeClose() {
const { isActive, postType } = useSelect( ( select ) => {
const { getCurrentPostType } = select( 'core/editor' );
const { isFeatureActive } = select( 'core/edit-post' );
const { getPostType } = select( 'core' );
const { isActive, isRequestingSiteIcon, postType, siteIconUrl } = useSelect(
( select ) => {
const { getCurrentPostType } = select( 'core/editor' );
const { isFeatureActive } = select( 'core/edit-post' );
const { isResolving } = select( 'core/data' );
const { getEntityRecord, getPostType } = select( 'core' );
const siteData =
getEntityRecord( 'root', '__unstableBase', undefined ) || {};

return {
isActive: isFeatureActive( 'fullscreenMode' ),
postType: getPostType( getCurrentPostType() ),
};
}, [] );
return {
isActive: isFeatureActive( 'fullscreenMode' ),
isRequestingSiteIcon: isResolving( 'core', 'getEntityRecord', [
'root',
'__unstableBase',
undefined,
] ),
postType: getPostType( getCurrentPostType() ),
siteIconUrl: siteData.site_icon_url,
};
},
[]
);

if ( ! isActive || ! postType ) {
return null;
}

let buttonIcon = <Icon size="36px" icon={ wordpress } />;

if ( siteIconUrl ) {
buttonIcon = (
<img
alt={ __( 'Site Icon' ) }
className="edit-post-fullscreen-mode-close_site-icon"
src={ siteIconUrl }
/>
);
} else if ( isRequestingSiteIcon ) {
buttonIcon = null;
}

return (
<Button
className="edit-post-fullscreen-mode-close"
icon={ wordpress }
iconSize={ 36 }
className="edit-post-fullscreen-mode-close has-icon"
href={ addQueryArgs( 'edit.php', {
post_type: postType.slug,
} ) }
label={ get( postType, [ 'labels', 'view_items' ], __( 'Back' ) ) }
/>
showTooltip
>
{ buttonIcon }
</Button>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@
}
}
}

.edit-post-fullscreen-mode-close_site-icon {
width: 36px;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';

/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import FullscreenModeClose from '../';

jest.mock( '@wordpress/data/src/components/use-select', () => {
// This allows us to tweak the returned value on each test
const mock = jest.fn();
return mock;
} );

jest.mock( '@wordpress/core-data' );

describe( 'FullscreenModeClose', () => {
describe( 'when in full screen mode', () => {
it( 'should display a user uploaded site icon if it exists', () => {
useSelect.mockImplementation( ( cb ) => {
return cb( () => ( {
isResolving: () => false,
isFeatureActive: () => true,
getCurrentPostType: () => {},
getPostType: () => true,
getEntityRecord: () => ( {
site_icon_url: 'https://fakeUrl.com',
} ),
} ) );
} );

const { container } = render( <FullscreenModeClose /> );
const siteIcon = container.querySelector(
'.edit-post-fullscreen-mode-close_site-icon'
);

expect( siteIcon ).toBeTruthy();
} );

it( 'should display a default site icon if no user uploaded site icon exists', () => {
useSelect.mockImplementation( ( cb ) => {
return cb( () => ( {
isResolving: () => false,
isFeatureActive: () => true,
getCurrentPostType: () => {},
getPostType: () => true,
getEntityRecord: () => ( {
site_icon_url: '',
} ),
} ) );
} );

const { container } = render( <FullscreenModeClose /> );
const siteIcon = container.querySelector(
'.edit-post-fullscreen-mode-close_site-icon'
);
const defaultIcon = container.querySelector( 'svg' );

expect( siteIcon ).toBeFalsy();
expect( defaultIcon ).toBeTruthy();
} );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,61 @@
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { Button } from '@wordpress/components';
import { Button, Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { wordpress } from '@wordpress/icons';

function FullscreenModeClose( { icon } ) {
const isActive = useSelect( ( select ) => {
return select( 'core/edit-site' ).isFeatureActive( 'fullscreenMode' );
}, [] );
const { isActive, isRequestingSiteIcon, siteIconUrl } = useSelect(
( select ) => {
const { isFeatureActive } = select( 'core/edit-site' );
const { getEntityRecord } = select( 'core' );
const { isResolving } = select( 'core/data' );
const siteData =
getEntityRecord( 'root', '__unstableBase', undefined ) || {};

return {
isActive: isFeatureActive( 'fullscreenMode' ),
isRequestingSiteIcon: isResolving( 'core', 'getEntityRecord', [
'root',
'__unstableBase',
undefined,
] ),
siteIconUrl: siteData.site_icon_url,
};
},
[]
);

if ( ! isActive ) {
return null;
}

const buttonIcon = icon || wordpress;
let buttonIcon = <Icon size="36px" icon={ wordpress } />;

if ( siteIconUrl ) {
buttonIcon = (
<img
alt={ __( 'Site Icon' ) }
className="edit-site-fullscreen-mode-close_site-icon"
src={ siteIconUrl }
/>
);
} else if ( isRequestingSiteIcon ) {
buttonIcon = null;
} else if ( icon ) {
buttonIcon = <Icon size="36px" icon={ icon } />;
}

return (
<Button
className="edit-site-fullscreen-mode-close"
icon={ buttonIcon }
iconSize={ 36 }
className="edit-site-fullscreen-mode-close has-icon"
href="index.php"
label={ __( 'Back' ) }
/>
showTooltip
>
{ buttonIcon }
</Button>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@
color: $white;
border-radius: 0;
height: $header-height;
width: $header-height;
min-width: $header-height;

&:hover {
background: #32373d; // WP-admin light-gray.
}

&:active {
color: $white;
}

&:focus {
box-shadow: inset 0 0 0 $border-width-focus var(--wp-admin-theme-color), inset 0 0 0 3px $white;
&.has-icon {
&:hover {
background: #32373d; // WP-admin light-gray.
}
&:active {
color: $white;
}
&:focus {
box-shadow: inset 0 0 0 $border-width-focus var(--wp-admin-theme-color), inset 0 0 0 3px $white;
}
}
}
}

.edit-site-fullscreen-mode-close_site-icon {
width: 36px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';

/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import FullscreenModeClose from '../';

jest.mock( '@wordpress/data/src/components/use-select', () => {
// This allows us to tweak the returned value on each test
const mock = jest.fn();
return mock;
} );

jest.mock( '@wordpress/core-data' );

describe( 'FullscreenModeClose', () => {
describe( 'when in full screen mode', () => {
it( 'should display a user uploaded site icon if it exists', () => {
useSelect.mockImplementation( ( cb ) => {
return cb( () => ( {
isResolving: () => false,
isFeatureActive: () => true,
getEntityRecord: () => ( {
site_icon_url: 'https://fakeUrl.com',
} ),
} ) );
} );

const { container } = render( <FullscreenModeClose /> );
const siteIcon = container.querySelector(
'.edit-site-fullscreen-mode-close_site-icon'
);

expect( siteIcon ).toBeTruthy();
} );

it( 'should display a default site icon if no user uploaded site icon exists', () => {
useSelect.mockImplementation( ( cb ) => {
return cb( () => ( {
isResolving: () => false,
isFeatureActive: () => true,
getEntityRecord: () => ( {
site_icon_url: '',
} ),
} ) );
} );

const { container } = render( <FullscreenModeClose /> );
const siteIcon = container.querySelector(
'.edit-site-fullscreen-mode-close_site-icon'
);
const defaultIcon = container.querySelector( 'svg' );

expect( siteIcon ).toBeFalsy();
expect( defaultIcon ).toBeTruthy();
} );
} );
} );

0 comments on commit 73f3a1d

Please sign in to comment.