Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactivity API: Allow global configs for namespaces #58749

Merged
merged 13 commits into from
Feb 9, 2024
Merged
7 changes: 2 additions & 5 deletions packages/block-library/src/query/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,8 @@ function block_core_query_disable_enhanced_pagination( $parsed_block ) {
}

if ( isset( $dirty_enhanced_queries[ $block['attrs']['queryId'] ] ) ) {
$p = new WP_HTML_Tag_Processor( $content );
if ( $p->next_tag() ) {
$p->set_attribute( 'data-wp-navigation-disabled', 'true' );
}
$content = $p->get_updated_html();
// Disable navigation in the router store config.
wp_interactivity_config( 'core/router', array( 'clientNavigationDisabled' => true ) );
$dirty_enhanced_queries[ $block['attrs']['queryId'] ] = null;
}

Expand Down
13 changes: 2 additions & 11 deletions packages/block-library/src/query/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,8 @@ store(
const queryRef = ref.closest(
'.wp-block-query[data-wp-router-region]'
);
const isDisabled = queryRef?.dataset.wpNavigationDisabled;

if (
isValidLink( ref ) &&
isValidEvent( event ) &&
! isDisabled
) {
if ( isValidLink( ref ) && isValidEvent( event ) ) {
event.preventDefault();

const { actions } = yield import(
Expand All @@ -50,11 +45,7 @@ store(
},
*prefetch() {
const { ref } = getElement();
const queryRef = ref.closest(
'.wp-block-query[data-wp-router-region]'
);
const isDisabled = queryRef?.dataset.wpNavigationDisabled;
if ( isValidLink( ref ) && ! isDisabled ) {
if ( isValidLink( ref ) ) {
const { actions } = yield import(
'@wordpress/interactivity-router'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
*/

wp_enqueue_script_module( 'router-navigate-view' );

if ( $attributes['disableNavigation'] ) {
wp_interactivity_config(
'core/router',
array( 'clientNavigationDisabled' => true )
);
}
?>

<div
Expand Down
4 changes: 4 additions & 0 deletions packages/interactivity-router/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

- Add the `clientNavigationDisabled` option to the `core/router` config. ([58749](https://github.com/WordPress/gutenberg/pull/58749))

## 1.0.0 (2024-01-24)

### Breaking changes
Expand Down
31 changes: 25 additions & 6 deletions packages/interactivity-router/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { store, privateApis } from '@wordpress/interactivity';
import { store, privateApis, getConfig } from '@wordpress/interactivity';

const { directivePrefix, getRegionRootFragment, initialVdom, toVdom, render } =
privateApis(
Expand Down Expand Up @@ -61,6 +61,21 @@ const renderRegions = ( page ) => {
}
};

/**
* Load the given page forcing a full page reload.
*
* The function returns a promise that won't resolve, useful to prevent any
* potential feedback indicating that the navigation has finished while the new
* page is being loaded.
*
* @param {string} href The page href.
* @return {Promise} Promise that never resolves.
*/
const forcePageReload = ( href ) => {
window.location.assign( href );
return new Promise( () => {} );
};

// Listen to the back and forward buttons and restore the page if it's in the
// cache.
window.addEventListener( 'popstate', async () => {
Expand Down Expand Up @@ -113,6 +128,11 @@ export const { state, actions } = store( 'core/router', {
* @return {Promise} Promise that resolves once the navigation is completed or aborted.
*/
*navigate( href, options = {} ) {
const { clientNavigationDisabled } = getConfig();
if ( clientNavigationDisabled ) {
yield forcePageReload( href );
}

const pagePath = getPagePath( href );
const { navigation } = state;
const {
Expand Down Expand Up @@ -183,11 +203,7 @@ export const { state, actions } = store( 'core/router', {
: '' );
}
} else {
window.location.assign( href );
// Await a promise that won't resolve to prevent any potential
// feedback indicating that the navigation has finished while
// the new page is being loaded.
yield new Promise( () => {} );
yield forcePageReload( href );
}
},

Expand All @@ -204,6 +220,9 @@ export const { state, actions } = store( 'core/router', {
* fetching the requested URL.
*/
prefetch( url, options = {} ) {
const { clientNavigationDisabled } = getConfig();
if ( clientNavigationDisabled ) return;

const pagePath = getPagePath( url );
if ( options.force || ! pages.has( pagePath ) ) {
pages.set( pagePath, fetchPage( pagePath, options ) );
Expand Down
4 changes: 4 additions & 0 deletions packages/interactivity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

- Export `getConfig()` to retrieve the server-defined configuration for the passed namespace. ([58749](https://github.com/WordPress/gutenberg/pull/58749))

### Breaking changes

- Remove the style prop (`key`) and class name arguments the `data-wp-style` and `data-wp-class` directives. ([#58835](https://github.com/WordPress/gutenberg/pull/58835)).
Expand Down
2 changes: 1 addition & 1 deletion packages/interactivity/src/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const namespaceStack: string[] = [];
* @return The context content.
*/
export const getContext = < T extends object >( namespace?: string ): T =>
getScope()?.context[ namespace || namespaceStack.slice( -1 )[ 0 ] ];
getScope()?.context[ namespace || getNamespace() ];

/**
* Retrieves a representation of the element where a function from the store
Expand Down
2 changes: 1 addition & 1 deletion packages/interactivity/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { directivePrefix } from './constants';
import { toVdom } from './vdom';
import { directive, getNamespace } from './hooks';

export { store } from './store';
export { store, getConfig } from './store';
export { getContext, getElement } from './hooks';
export {
withScope,
Expand Down
44 changes: 31 additions & 13 deletions packages/interactivity/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getScope,
setScope,
resetScope,
getNamespace,
setNamespace,
resetNamespace,
} from './hooks';
Expand All @@ -34,25 +35,24 @@ const deepMerge = ( target: any, source: any ) => {
}
};

const parseInitialState = () => {
const parseInitialData = () => {
const storeTag = document.querySelector(
`script[type="application/json"]#wp-interactivity-data`
);
if ( ! storeTag?.textContent ) return {};
try {
const { state } = JSON.parse( storeTag.textContent );
if ( isObject( state ) ) return state;
throw Error( 'Parsed state is not an object' );
} catch ( e ) {
// eslint-disable-next-line no-console
console.log( e );
if ( storeTag?.textContent ) {
try {
return JSON.parse( storeTag.textContent );
} catch ( e ) {
// Do nothing.
}
}
return {};
};

export const stores = new Map();
const rawStores = new Map();
const storeLocks = new Map();
const storeConfigs = new Map();

const objToProxy = new WeakMap();
const proxyToNs = new WeakMap();
Expand Down Expand Up @@ -164,6 +164,16 @@ const handlers = {
return result;
},
};

/**
* Get the defined config for the store with the passed namespace.
*
* @param namespace Store's namespace from which to retrieve the config.
* @return Defined config for the given namespace.
*/
export const getConfig = ( namespace: string ) =>
storeConfigs.get( namespace || getNamespace() ) || {};

interface StoreOptions {
/**
* Property to block/unblock private store namespaces.
Expand Down Expand Up @@ -300,7 +310,15 @@ export function store(
return stores.get( namespace );
}

// Parse and populate the initial state.
Object.entries( parseInitialState() ).forEach( ( [ namespace, state ] ) => {
store( namespace, { state }, { lock: universalUnlock } );
} );
// Parse and populate the initial state and config.
const data = parseInitialData();
if ( isObject( data?.state ) ) {
Object.entries( data.state ).forEach( ( [ namespace, state ] ) => {
store( namespace, { state }, { lock: universalUnlock } );
} );
}
if ( isObject( data?.config ) ) {
Object.entries( data.config ).forEach( ( [ namespace, config ] ) => {
storeConfigs.set( namespace, config );
} );
}
58 changes: 42 additions & 16 deletions phpunit/blocks/render-query-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class Tests_Blocks_RenderQueryBlock extends WP_UnitTestCase {

private static $posts;

private $original_wp_interactivity;

public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
self::$posts = $factory->post->create_many( 3 );

Expand All @@ -29,6 +31,20 @@ public static function wpTearDownAfterClass() {
unregister_block_type( 'test/plugin-block' );
}

public function set_up() {
parent::set_up();
global $wp_interactivity;
$this->original_wp_interactivity = $wp_interactivity;
$wp_interactivity = new WP_Interactivity_API();
}

public function tear_down() {
global $wp_interactivity;
$wp_interactivity = $this->original_wp_interactivity;
parent::tear_down();
}


/**
* Tests that the `core/query` block adds the corresponding directives when
* the `enhancedPagination` attribute is set.
Expand Down Expand Up @@ -86,11 +102,15 @@ public function test_rendering_query_with_enhanced_pagination() {
$this->assertSame( 'core/query::actions.navigate', $p->get_attribute( 'data-wp-on--click' ) );
$this->assertSame( 'core/query::actions.prefetch', $p->get_attribute( 'data-wp-on--mouseenter' ) );
$this->assertSame( 'core/query::callbacks.prefetch', $p->get_attribute( 'data-wp-watch' ) );

$router_config = wp_interactivity_config( 'core/router' );
$this->assertEmpty( $router_config );
}

/**
* Tests that the `core/query` block adds an extra attribute to disable the
* enhanced pagination in the browser when a plugin block is found inside.
* Tests that the `core/query` block sets the option
* `clientNavigationDisabled` to `true` in the `core/router` store config
* when a plugin block is found inside.
*/
public function test_rendering_query_with_enhanced_pagination_auto_disabled_when_plugins_blocks_are_found() {
global $wp_query, $wp_the_query;
Expand Down Expand Up @@ -120,12 +140,15 @@ public function test_rendering_query_with_enhanced_pagination_auto_disabled_when

$p->next_tag( array( 'class_name' => 'wp-block-query' ) );
$this->assertSame( 'query-0', $p->get_attribute( 'data-wp-router-region' ) );
$this->assertSame( 'true', $p->get_attribute( 'data-wp-navigation-disabled' ) );

$router_config = wp_interactivity_config( 'core/router' );
$this->assertTrue( $router_config['clientNavigationDisabled'] );
}

/**
* Tests that the `core/query` block adds an extra attribute to disable the
* enhanced pagination in the browser when a post content block is found inside.
* Tests that the `core/query` block sets the option
* `clientNavigationDisabled` to `true` in the `core/router` store config
* when a post content block is found inside.
*/
public function test_rendering_query_with_enhanced_pagination_auto_disabled_when_post_content_block_is_found() {
global $wp_query, $wp_the_query;
Expand Down Expand Up @@ -155,13 +178,14 @@ public function test_rendering_query_with_enhanced_pagination_auto_disabled_when

$p->next_tag( array( 'class_name' => 'wp-block-query' ) );
$this->assertSame( 'query-0', $p->get_attribute( 'data-wp-router-region' ) );
$this->assertSame( 'true', $p->get_attribute( 'data-wp-navigation-disabled' ) );
$router_config = wp_interactivity_config( 'core/router' );
$this->assertTrue( $router_config['clientNavigationDisabled'] );
}

/**
* Tests that the correct `core/query` blocks get the attribute that
* disables enhanced pagination only if they contain a descendant that is
* not supported (i.e., a plugin block).
* Tests that, whenever a `core/query` contains a descendant that is not
* supported (i.e., a plugin block), the option `clientNavigationDisabled`
* is set to `true` in the `core/router` store config.
*/
public function test_rendering_nested_queries_with_enhanced_pagination_auto_disabled() {
global $wp_query, $wp_the_query;
Expand Down Expand Up @@ -204,23 +228,23 @@ public function test_rendering_nested_queries_with_enhanced_pagination_auto_disa
// Query 0 contains a plugin block inside query-2 -> disabled.
$p->next_tag( array( 'class_name' => 'wp-block-query' ) );
$this->assertSame( 'query-0', $p->get_attribute( 'data-wp-router-region' ) );
$this->assertSame( 'true', $p->get_attribute( 'data-wp-navigation-disabled' ) );

// Query 1 does not contain a plugin block -> enabled.
$p->next_tag( array( 'class_name' => 'wp-block-query' ) );
$this->assertSame( 'query-1', $p->get_attribute( 'data-wp-router-region' ) );
$this->assertSame( null, $p->get_attribute( 'data-wp-navigation-disabled' ) );

// Query 2 contains a plugin block -> disabled.
$p->next_tag( array( 'class_name' => 'wp-block-query' ) );
$this->assertSame( 'query-2', $p->get_attribute( 'data-wp-router-region' ) );
$this->assertSame( 'true', $p->get_attribute( 'data-wp-navigation-disabled' ) );

$router_config = wp_interactivity_config( 'core/router' );
$this->assertTrue( $router_config['clientNavigationDisabled'] );
}

/**
* Tests that the `core/query` block adds an extra attribute to disable the
* enhanced pagination in the browser when a plugin that does not define
* clientNavigation is found inside.
* Tests that the `core/query` block sets the option
* `clientNavigationDisabled` to `true` in the `core/router` store config
* when a plugin that does not define clientNavigation is found inside.
*/
public function test_rendering_query_with_enhanced_pagination_auto_disabled_when_there_is_a_non_compatible_block() {
global $wp_query, $wp_the_query;
Expand Down Expand Up @@ -248,6 +272,8 @@ public function test_rendering_query_with_enhanced_pagination_auto_disabled_when

$p->next_tag( array( 'class_name' => 'wp-block-query' ) );
$this->assertSame( 'query-0', $p->get_attribute( 'data-wp-router-region' ) );
$this->assertSame( 'true', $p->get_attribute( 'data-wp-navigation-disabled' ) );

$router_config = wp_interactivity_config( 'core/router' );
$this->assertTrue( $router_config['clientNavigationDisabled'] );
}
}
Loading
Loading