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

Query and Search blocks: support for Instant Search via query_loop_block_query_vars filter #67181

Open
wants to merge 47 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
def5f01
add the filter and test
michalczaplinski Nov 13, 2024
2886139
remove
michalczaplinski Nov 14, 2024
c8b390b
Create instant search using `render_block_context` filter.
michalczaplinski Nov 14, 2024
00d90f3
Remove handling of inheritd query from `search/index.php`
michalczaplinski Nov 14, 2024
6214a72
Handle case when query is defined in block context in DB.
michalczaplinski Nov 14, 2024
cbb469c
Guard against queryId being undefined in block context
michalczaplinski Nov 18, 2024
571d851
Fix the e2e test suite
michalczaplinski Nov 19, 2024
f922695
Move the filter to `/experimental` folder.
michalczaplinski Nov 20, 2024
c1eb9de
Add an e2e test case if query.search attribute is present
michalczaplinski Nov 20, 2024
970908f
Remove the search button when instant search is enabled
michalczaplinski Nov 20, 2024
98a0e23
Use the query_loop_block_query_vars filter
michalczaplinski Nov 21, 2024
64d68da
Do not delete pages and templates in e2e tests
michalczaplinski Nov 21, 2024
2a89b6a
Set the pageId for Multiple Queries tests
michalczaplinski Nov 21, 2024
7cad091
Fix the block name via metadata when Seach is instant.
michalczaplinski Nov 25, 2024
7e1e27b
Remove stuff related to Default queries from `view.js`
michalczaplinski Nov 26, 2024
b4d16f7
Add `attributes.metadata` to useEffect dependency array
michalczaplinski Nov 27, 2024
9095031
Remove `attributes.metadata` & label from dependency array
michalczaplinski Nov 29, 2024
ca253ef
Don't use `Promise.withResolvers()` in search block's view.js
michalczaplinski Dec 3, 2024
cf27754
Explain why we disable the react-hooks/exhaustive-deps lint
michalczaplinski Nov 29, 2024
a5c2b17
Undo formatting changes
michalczaplinski Nov 29, 2024
2fc61fd
Create instant search using `render_block_context` filter.
michalczaplinski Nov 14, 2024
5846998
Undo formatting changes
michalczaplinski Nov 29, 2024
4b2f66d
Remove the `render_block_context` filter from blocks.php
michalczaplinski Dec 5, 2024
1bf3f4b
Merge branch 'trunk' into feature/search-query-using-vars-filter
michalczaplinski Dec 9, 2024
ee2739d
Fix how directives are added to the form in the search block.
michalczaplinski Dec 11, 2024
806c330
Add e2e test for renaming Search block to "Instant Search" in the Lis…
michalczaplinski Dec 12, 2024
2e7432d
Appease the PHP formatter
michalczaplinski Dec 12, 2024
049fc67
Merge remote-tracking branch 'origin/trunk' into feature/search-query…
michalczaplinski Dec 12, 2024
95a9647
Update lib/experimental/blocks.php
michalczaplinski Dec 12, 2024
ac750ae
Update the comment in Search block to clarify when Instant Search is …
michalczaplinski Dec 12, 2024
de34a1e
Merge branch 'trunk' into feature/search-query-using-vars-filter
michalczaplinski Dec 13, 2024
4ce8096
Merge branch 'trunk' into feature/search-query-using-vars-filter
michalczaplinski Dec 18, 2024
b1c11f4
Update e2e tests for Instant Search to verify block renaming in Inspe…
michalczaplinski Dec 18, 2024
3dd53b9
Simplify the logic for enqueuing `@wordpress/block-library/search/view`
michalczaplinski Dec 19, 2024
bdaaf2e
Put back the empty() calls that were erroneouslyreplaced with ternaries
michalczaplinski Dec 19, 2024
97b3c84
Only hide the search button if BOTH enhanced pagination & instant sea…
michalczaplinski Dec 19, 2024
2095706
Format PHP
michalczaplinski Dec 19, 2024
77e86d2
Removed redundant getter for `isSearchInputVisible` and simplified th…
michalczaplinski Dec 19, 2024
4fcb01b
Merge branch 'trunk' into feature/search-query-using-vars-filter
michalczaplinski Dec 19, 2024
15b80fe
Check presence of instant search experiment in addition to the checki…
michalczaplinski Dec 30, 2024
dd42f50
Simplify instant search and enhanced pagination checks.
michalczaplinski Jan 6, 2025
1d70719
Update comments
michalczaplinski Jan 6, 2025
e0dd93b
Refactor Instant Search tests to verify toolbar button visibility whe…
michalczaplinski Jan 6, 2025
2db363a
Update Search block label in ListView when it's an Instant Search
michalczaplinski Jan 6, 2025
c20526f
Enhance Instant Search e2e tests to verify List View label updates
michalczaplinski Jan 6, 2025
6b975c4
Update Search block label to default to 'Search' when no custom name …
michalczaplinski Jan 6, 2025
7ca6d27
Remove default label attribute from Search block in Instant Search tests
michalczaplinski Jan 6, 2025
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
33 changes: 33 additions & 0 deletions lib/experimental/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,39 @@ function gutenberg_register_block_style( $block_name, $style_properties ) {
return $result;
}

/**
* Passes the search query param to Query Loop blocks, if the instant search experiment is enabled.
*
* @param array $query The query variables.
* @param WP_Block $block Block instance.
* @return array Modified query variables.
*/
function gutenberg_block_core_query_add_url_filtering( $query, $block ) {
// Check if the instant search gutenberg experiment is enabled
$gutenberg_experiments = get_option( 'gutenberg-experiments' );
$instant_search_enabled = $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments );
if ( ! $instant_search_enabled ) {
return $query;
}

// Make sure block has a queryId
if ( empty( $block->context['queryId'] ) ) {
return $query;
}

// Get the search key from the URL
$search_key = 'instant-search-' . $block->context['queryId'];
if ( ! isset( $_GET[ $search_key ] ) ) {
return $query;
}

// Add the search parameter to the query
$query['s'] = sanitize_text_field( $_GET[ $search_key ] );

return $query;
}
add_filter( 'query_loop_block_query_vars', 'gutenberg_block_core_query_add_url_filtering', 10, 2 );

/**
* Additional data to expose to the view script module in the Form block.
*/
Expand Down
3 changes: 3 additions & 0 deletions lib/experimental/editor-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ function gutenberg_enable_experiments() {
if ( gutenberg_is_experiment_enabled( 'gutenberg-full-page-client-side-navigation' ) ) {
wp_add_inline_script( 'wp-block-library', 'window.__experimentalFullPageClientSideNavigation = true', 'before' );
}
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableSearchQueryBlock = true', 'before' );
}
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-comment', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableBlockComment = true', 'before' );
}
Expand Down
12 changes: 12 additions & 0 deletions lib/experiments-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,18 @@ function gutenberg_initialize_experiments_settings() {
)
);

add_settings_field(
'gutenberg-search-query-block',
__( 'Instant Search and Query Block', 'gutenberg' ),
'gutenberg_display_experiment_field',
'gutenberg-experiments',
'gutenberg_experiments_section',
array(
'label' => __( 'Enable instant search functionality of the Search + Query blocks.', 'gutenberg' ),
'id' => 'gutenberg-search-query-block',
)
);

register_setting(
'gutenberg-experiments',
'gutenberg-experiments'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ export default function useBlockDisplayTitle( {
}

const attributes = getBlockAttributes( clientId );
const label = getBlockLabel( blockType, attributes, context );
const label = getBlockLabel(
blockType,
attributes,
context,
clientId
);
// If the label is defined we prioritize it over a possible block variation title match.
if ( label !== blockType.title ) {
return label;
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/search/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"default": false
}
},
"usesContext": [ "enhancedPagination", "query", "queryId" ],
"supports": {
"align": [ "left", "center", "right" ],
"color": {
Expand Down
68 changes: 43 additions & 25 deletions packages/block-library/src/search/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export default function SearchEdit( {
toggleSelection,
isSelected,
clientId,
context,
} ) {
const {
label,
Expand All @@ -82,6 +83,13 @@ export default function SearchEdit( {
style,
} = attributes;

// Check if the block is inside a Query block with enhanced pagination enabled
// and if the `__experimentalEnableSearchQueryBlock` flag is enabled.
const hasInstantSearch = !! (
context?.enhancedPagination &&
window?.__experimentalEnableSearchQueryBlock
);

const wasJustInsertedIntoNavigationBlock = useSelect(
( select ) => {
const { getBlockParentsByBlockName, wasBlockJustInserted } =
Expand Down Expand Up @@ -385,24 +393,28 @@ export default function SearchEdit( {
} }
className={ showLabel ? 'is-pressed' : undefined }
/>
<ToolbarDropdownMenu
icon={ getButtonPositionIcon() }
label={ __( 'Change button position' ) }
controls={ buttonPositionControls }
/>
{ ! hasNoButton && (
<ToolbarButton
title={ __( 'Use button with icon' ) }
icon={ buttonWithIcon }
onClick={ () => {
setAttributes( {
buttonUseIcon: ! buttonUseIcon,
} );
} }
className={
buttonUseIcon ? 'is-pressed' : undefined
}
/>
{ ! hasInstantSearch && (
<>
<ToolbarDropdownMenu
icon={ getButtonPositionIcon() }
label={ __( 'Change button position' ) }
controls={ buttonPositionControls }
/>
{ ! hasNoButton && (
<ToolbarButton
title={ __( 'Use button with icon' ) }
icon={ buttonWithIcon }
onClick={ () => {
setAttributes( {
buttonUseIcon: ! buttonUseIcon,
} );
} }
className={
buttonUseIcon ? 'is-pressed' : undefined
}
/>
) }
</>
) }
</ToolbarGroup>
</BlockControls>
Expand Down Expand Up @@ -596,16 +608,22 @@ export default function SearchEdit( {
} }
showHandle={ isSelected }
>
{ ( isButtonPositionInside ||
isButtonPositionOutside ||
hasOnlyButton ) && (
{ hasInstantSearch ? (
renderTextField()
) : (
<>
{ renderTextField() }
{ renderButton() }
{ ( isButtonPositionInside ||
isButtonPositionOutside ||
hasOnlyButton ) && (
<>
{ renderTextField() }
{ renderButton() }
</>
) }

{ hasNoButton && renderTextField() }
</>
) }

{ hasNoButton && renderTextField() }
</ResizableBox>
</div>
);
Expand Down
39 changes: 38 additions & 1 deletion packages/block-library/src/search/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import { search as icon } from '@wordpress/icons';
import { select } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

/**
* Internal dependencies
Expand All @@ -18,6 +20,41 @@ export { metadata, name };

export const settings = {
icon,
__experimentalLabel( attributes, { clientId } ) {
const { label } = attributes;
const customName = attributes?.metadata?.name;

// Check if the block is inside a Query Loop block.
const queryLoopBlockIds = select(
blockEditorStore
).getBlockParentsByBlockName( clientId, 'core/query' );

// If the block is not inside a Query Loop block, return the block label.
if ( ! queryLoopBlockIds.length ) {
return customName || label;
}

const queryLoopBlock = select( blockEditorStore ).getBlock(
queryLoopBlockIds[ 0 ]
);

// Check if the Query Loop block has enhanced pagination enabled and
// if the `__experimentalEnableSearchQueryBlock` flag is enabled.
const hasInstantSearch = !! (
queryLoopBlock?.attributes?.enhancedPagination &&
window?.__experimentalEnableSearchQueryBlock
);

if ( ! hasInstantSearch ) {
return customName || label;
}

return sprintf(
/* translators: %s: The block label */
__( '%s (Instant search enabled)' ),
customName || label || 'Search'
);
},
example: {
attributes: { buttonText: __( 'Search' ), label: __( 'Search' ) },
viewportWidth: 400,
Expand Down
84 changes: 68 additions & 16 deletions packages/block-library/src/search/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*
* @return string The search block markup.
*/
function render_block_core_search( $attributes ) {
function render_block_core_search( $attributes, $content, $block ) {
// Older versions of the Search block defaulted the label and buttonText
// attributes to `__( 'Search' )` meaning that many posts contain `<!--
// wp:search /-->`. Support these by defaulting an undefined label and
Expand All @@ -29,11 +29,26 @@ function render_block_core_search( $attributes ) {
)
);

$input_id = wp_unique_id( 'wp-block-search__input-' );
$classnames = classnames_for_block_core_search( $attributes );
$show_label = ! empty( $attributes['showLabel'] );
$use_icon_button = ! empty( $attributes['buttonUseIcon'] );
$show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true;
$input_id = wp_unique_id( 'wp-block-search__input-' );
$classnames = classnames_for_block_core_search( $attributes );
$show_label = ! empty( $attributes['showLabel'] );
$use_icon_button = ! empty( $attributes['buttonUseIcon'] );

// Check if the block is using the enhanced pagination.
$enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination'];

// Check if the block is using the instant search experiment, which requires the enhanced pagination.
$gutenberg_experiments = get_option( 'gutenberg-experiments' );
$instant_search_enabled = $enhanced_pagination && $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments );

$show_button = true;

if ( $instant_search_enabled ) {
$show_button = false;
// If the button position is no-button, ALSO hide the button.
} elseif ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) {
$show_button = false;
}
$button_position = $show_button ? $attributes['buttonPosition'] : null;
$query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array();
$button = '';
Expand Down Expand Up @@ -90,6 +105,19 @@ function render_block_core_search( $attributes ) {
$input->set_attribute( 'aria-hidden', 'true' );
$input->set_attribute( 'tabindex', '-1' );
}

// Instant search is only enabled when both are true:
// 1. The block is a child of a Query Loop block.
// 2. The Query Loop block has the enhanced pagination feature enabled.
//
// Instant search functionality does not make sense without enhanced pagination
// because we might have to paginate the results of the search too!
if ( $instant_search_enabled ) {
wp_enqueue_script_module( '@wordpress/block-library/search/view' );

$input->set_attribute( 'data-wp-bind--value', 'context.search' );
$input->set_attribute( 'data-wp-on-async--input', 'actions.updateSearch' );
}
}

if ( count( $query_params ) > 0 ) {
Expand Down Expand Up @@ -163,28 +191,52 @@ function render_block_core_search( $attributes ) {
array( 'class' => $classnames )
);
$form_directives = '';
$form_context = array();

// If it's interactive, add the directives.
if ( $is_expandable_searchfield || $instant_search_enabled ) {
$form_directives = 'data-wp-interactive="core/search"';
}

if ( $is_expandable_searchfield ) {
$aria_label_expanded = __( 'Submit Search' );
$aria_label_collapsed = __( 'Expand search field' );
$form_context = wp_interactivity_data_wp_context(
array(
'isSearchInputVisible' => $open_by_default,
'inputId' => $input_id,
'ariaLabelExpanded' => $aria_label_expanded,
'ariaLabelCollapsed' => $aria_label_collapsed,
)
$form_context = array(
'isSearchInputInitiallyVisible' => $open_by_default,
'inputId' => $input_id,
'ariaLabelExpanded' => $aria_label_expanded,
'ariaLabelCollapsed' => $aria_label_collapsed,
);
$form_directives = '
data-wp-interactive="core/search"
' . $form_context . '
$form_directives .= '
data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible"
data-wp-on-async--keydown="actions.handleSearchKeydown"
data-wp-on-async--focusout="actions.handleSearchFocusout"
';
}

if ( $instant_search_enabled && isset( $block->context['queryId'] ) ) {

$search = '';

// If the query is defined in the block context, use it
if ( isset( $block->context['query']['search'] ) && '' !== $block->context['query']['search'] ) {
$search = $block->context['query']['search'];
}

// If the query is defined in the URL, it overrides the block context value if defined
$search = empty( $_GET[ 'instant-search-' . $block->context['queryId'] ] ) ? $search : sanitize_text_field( $_GET[ 'instant-search-' . $block->context['queryId'] ] );

$form_context = array_merge(
$form_context,
array(
'search' => $search,
'queryId' => $block->context['queryId'],
)
);
}

$form_directives .= wp_interactivity_data_wp_context( $form_context );

return sprintf(
'<form role="search" method="get" action="%1s" %2s %3s>%4s</form>',
esc_url( home_url( '/' ) ),
Expand Down
Loading
Loading