Skip to content

Commit

Permalink
Interactivity API: Allow global configs for namespaces (#58749)
Browse files Browse the repository at this point in the history
* Add `clientNavigation: false` to the router config

* Parse store configs from initial data

* Expose `getConfig` function

* Use router config to disable client navigation

* Handle `clientNavigation` cofing inside router's `navigate`

* Add a test for `clientNavigation` false

* Update changelogs

* Bail out in prefetch if clientNavigation is disabled

* Change `clientNavigation` config to `clientNavigationDisabled`

* Update Query block phpunit tests

* Remove console log

* Support default namespaces in getConfig

---------

Co-authored-by: DAreRodz <[email protected]>
Co-authored-by: luisherranz <[email protected]>
Co-authored-by: c4rl0sbr4v0 <[email protected]>
  • Loading branch information
4 people authored Feb 9, 2024
1 parent 76ca987 commit a7a7c6f
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 53 deletions.
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

0 comments on commit a7a7c6f

Please sign in to comment.