Product Block Editor: populate shipping class slug automatically (#47896)
* extend shipping_classses with '/suggest-slug' endpoint * generate slug automatically * changelog * jsdoc * polish setting and pulling the slug * replace checkbox by button * reword slug inout label with "Slug" * add slug help * use prefix input property * isPrimary is deprecated. Use "variant" instead * fixing typo. props to @mdperez86
This commit is contained in:
parent
b0a1787304
commit
ac6fe9f933
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Product Block Editor: populate shipping class slug automatically
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button, Modal, TextControl } from '@wordpress/components';
|
||||
import {
|
||||
useState,
|
||||
createElement,
|
||||
|
@ -10,6 +9,19 @@ import {
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { Form, FormErrors, useFormContext } from '@woocommerce/components';
|
||||
import { ProductShippingClass } from '@woocommerce/data';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
TextControl,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore We need this to import the block modules for registration.
|
||||
__experimentalInputControl as InputControl,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore We need this to import the block modules for registration.
|
||||
__experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
|
||||
} from '@wordpress/components';
|
||||
|
||||
export type ShippingClassFormProps = {
|
||||
onAdd: () => Promise< ProductShippingClass >;
|
||||
|
@ -33,6 +45,58 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
|
|||
} );
|
||||
}
|
||||
|
||||
// State to control the automatic slug generation.
|
||||
const [ isRequestingSlug, setIsRequestingSlug ] = useState( false );
|
||||
|
||||
// Get the shipping class name value.
|
||||
const shippingNameInputValue = String( getInputProps( 'name' ).value );
|
||||
|
||||
const [ prevNameValue, setPrevNameValue ] = useState(
|
||||
shippingNameInputValue
|
||||
);
|
||||
|
||||
/**
|
||||
* Pull the slug suggestion from the server,
|
||||
* and update the slug input field.
|
||||
*/
|
||||
async function pullAndUpdateSlugInputField() {
|
||||
setIsRequestingSlug( true );
|
||||
|
||||
// Avoid making the request if the name has not changed.
|
||||
if ( prevNameValue === shippingNameInputValue ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPrevNameValue( shippingNameInputValue );
|
||||
|
||||
const url = `/wc/v3/products/shipping_classes/slug-suggestion`;
|
||||
const slug: string = await apiFetch( {
|
||||
path: addQueryArgs( url, { name: shippingNameInputValue } ),
|
||||
method: 'GET',
|
||||
} );
|
||||
|
||||
setIsRequestingSlug( false );
|
||||
|
||||
getInputProps( 'slug' ).onChange( slug );
|
||||
}
|
||||
|
||||
const isGenerateButtonDisabled =
|
||||
isRequestingSlug ||
|
||||
! shippingNameInputValue?.length ||
|
||||
prevNameValue === shippingNameInputValue;
|
||||
|
||||
/**
|
||||
* Get a slug suggestion based on the shipping class name.
|
||||
* This function is called when the name field is blurred.
|
||||
*/
|
||||
function getSlugSuggestion() {
|
||||
if ( ! isRequestingSlug ) {
|
||||
return;
|
||||
}
|
||||
|
||||
pullAndUpdateSlugInputField();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="woocommerce-add-new-shipping-class-modal__wrapper">
|
||||
<TextControl
|
||||
|
@ -48,11 +112,36 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
|
|||
),
|
||||
}
|
||||
) }
|
||||
onBlur={ getSlugSuggestion }
|
||||
/>
|
||||
<TextControl
|
||||
|
||||
<InputControl
|
||||
{ ...getInputProps( 'slug' ) }
|
||||
label={ __( 'Slug', 'woocommerce' ) }
|
||||
onChange={ ( value: string ) => {
|
||||
setPrevNameValue( '' ); // clean the previous name value.
|
||||
getInputProps( 'slug' ).onChange( value );
|
||||
} }
|
||||
disabled={ isRequestingSlug }
|
||||
help={ __(
|
||||
'Set a custom slug or generate it by clicking the button.',
|
||||
'woocommerce'
|
||||
) }
|
||||
prefix={
|
||||
<InputControlPrefixWrapper>
|
||||
<Button
|
||||
disabled={ isGenerateButtonDisabled }
|
||||
variant="secondary"
|
||||
onClick={ pullAndUpdateSlugInputField }
|
||||
isBusy={ isRequestingSlug }
|
||||
isSmall
|
||||
>
|
||||
{ __( 'Generate', 'woocommerce' ) }
|
||||
</Button>
|
||||
</InputControlPrefixWrapper>
|
||||
}
|
||||
/>
|
||||
|
||||
<TextControl
|
||||
{ ...getInputProps( 'description' ) }
|
||||
label={ __( 'Description', 'woocommerce' ) }
|
||||
|
@ -64,12 +153,13 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
|
|||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="woocommerce-add-new-shipping-class-modal__buttons">
|
||||
<Button isSecondary onClick={ onCancel }>
|
||||
{ __( 'Cancel', 'woocommerce' ) }
|
||||
</Button>
|
||||
<Button
|
||||
isPrimary
|
||||
variant="primary"
|
||||
isBusy={ isLoading }
|
||||
disabled={ ! isValidForm || isLoading }
|
||||
onClick={ handleAdd }
|
||||
|
|
|
@ -10,9 +10,14 @@
|
|||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.has-error {
|
||||
.components-base-control__help {
|
||||
color: $studio-red-50;
|
||||
}
|
||||
}
|
||||
|
||||
.components-input-control__container {
|
||||
min-height: 36px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
REST API: extened shipping_classes namespace with the /suggest-slug endpoint
|
|
@ -24,4 +24,61 @@ class WC_REST_Product_Shipping_Classes_Controller extends WC_REST_Product_Shippi
|
|||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* Register the routes for product reviews.
|
||||
*/
|
||||
public function register_routes() {
|
||||
parent::register_routes();
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/slug-suggestion',
|
||||
array(
|
||||
'args' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Suggest a slug for the term.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'suggest_slug' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback fuction for the slug-suggestion endpoint.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return string The suggested slug.
|
||||
*/
|
||||
public function suggest_slug( $request ) {
|
||||
$name = $request['name'];
|
||||
$slug = sanitize_title( $name ); // potential slug.
|
||||
$term = get_term_by( 'slug', $slug, $this->taxonomy );
|
||||
|
||||
/*
|
||||
* If the term exists, creates a unique slug
|
||||
* based on the name provided.
|
||||
* Otherwise, returns the sanitized name.
|
||||
*/
|
||||
if ( isset( $term->slug ) ) {
|
||||
/*
|
||||
* Pass a Term object that has only the taxonomy property,
|
||||
* to induce the wp_unique_term_slug() function to generate a unique slug.
|
||||
* Otherwise, the function will return the same slug.
|
||||
* @see https://core.trac.wordpress.org/browser/tags/6.5/src/wp-includes/taxonomy.php#L3130
|
||||
* @see https://github.com/WordPress/wordpress-develop/blob/a1b1e0339eb6dfa72a30933cac2a1c6ad2bbfe96/src/wp-includes/taxonomy.php#L3078-L3156
|
||||
*/
|
||||
$slug = wp_unique_term_slug( $slug, (object) array( 'taxonomy' => $this->taxonomy ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $slug );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue