woocommerce/plugins/woocommerce-admin/client/wp-admin-scripts/product-tour/use-product-step-change.ts

133 lines
3.9 KiB
TypeScript
Raw Normal View History

/**
* External dependencies
*/
import { useCallback, useEffect, useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { useActiveEditorType } from './use-active-editor-type';
export type ProductTourStepName =
| 'product-name'
| 'product-description'
| 'product-data'
| 'product-short-description'
| 'product-image'
| 'product-tags'
| 'product-categories';
const getInputValue = ( id: string ) => {
return ( document.querySelector( id ) as HTMLInputElement ).value;
};
const getTinyMceValue = ( id: string ) => {
const iframe = document.querySelector< HTMLIFrameElement >( id );
const tinymce = iframe?.contentWindow?.document.querySelector< HTMLElement >(
'#tinymce'
);
return tinymce?.innerHTML || '';
};
const getTextareaValue = ( id: string ) => {
return document.querySelector< HTMLTextAreaElement >( id )?.value || '';
};
const getProductDescriptionValue = ( isContentEditorTmceActive: boolean ) => {
return isContentEditorTmceActive
? getTinyMceValue( '#content_ifr' )
: getTextareaValue( '#wp-content-editor-container > .wp-editor-area' );
};
const getProductShortDescriptionValue = (
isExcerptEditorTmceActive: boolean
) => {
return isExcerptEditorTmceActive
? getTinyMceValue( '#excerpt_ifr' )
: getTextareaValue( '#wp-excerpt-editor-container > .wp-editor-area' );
};
const getProductImageValue = () => {
return (
document.querySelector< HTMLImageElement >( '#set-post-thumbnail img' )
?.src || ''
);
};
// Parses categories into a string of true/false. Should be enough to catch any change.
const getProductCategoriesValue = () => {
return Array.from(
document.querySelectorAll< HTMLInputElement >(
'#product_cat-all #product_catchecklist input'
)
)
.map( ( x ) => x.checked )
.join( ',' );
};
// Parses all tags as string of tags separated by comma.
const getProductTagsValue = () => {
return Array.from(
document.querySelectorAll< HTMLLIElement >( '#product_tag li' )
)
.map( ( x ) => ( x.lastChild as Text ).textContent )
.join( ',' );
};
/**
* Custom hook that is used to detect if the product form has any changes and isn't empty.
* This hook returns two functions:
* 1. setIsLoaded which is used to save initial product form values when form is ready.
* 2. hasChanged which is used for querying for the step's input changes.
*/
export const useProductStepChange = () => {
const { isTmce: isContentEditorTmceActive } = useActiveEditorType( {
editorWrapSelector: '#wp-content-wrap',
} );
const { isTmce: isExcerptEditorTmceActive } = useActiveEditorType( {
editorWrapSelector: '#wp-excerpt-wrap',
} );
const [ initialValues, setInitialValues ] = useState<
Partial< Record< ProductTourStepName, string > >
>( {} );
const [ isLoaded, setIsLoaded ] = useState( false );
const getValues: () => Partial<
Record< ProductTourStepName, string >
> = useCallback( () => {
return {
'product-name': getInputValue( '#title' ),
'product-description': getProductDescriptionValue(
isContentEditorTmceActive
),
// For product data, we're just going to detect change if price is changed.
'product-data': getInputValue( '#_regular_price' ),
'product-short-description': getProductShortDescriptionValue(
isExcerptEditorTmceActive
),
'product-image': getProductImageValue(),
'product-tags': getProductTagsValue(),
'product-categories': getProductCategoriesValue(),
};
}, [ isContentEditorTmceActive, isExcerptEditorTmceActive ] );
// If value has changed and isn't empty, returns as changed.
const hasUpdatedInfo: ( key: ProductTourStepName ) => boolean = useCallback(
( key ) => {
const newValues = getValues();
return (
initialValues[ key ] !== newValues[ key ] &&
newValues[ key ] !== ''
);
},
[ getValues, initialValues ]
);
useEffect( () => {
if ( isLoaded ) {
setInitialValues( getValues() );
}
}, [ setInitialValues, isLoaded, getValues ] );
return { setIsLoaded, hasUpdatedInfo };
};