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

134 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( id ) as HTMLIFrameElement;
return ( iframe?.contentWindow?.document.querySelector(
'#tinymce'
) as HTMLElement ).innerHTML;
};
const getTextareaValue = ( id: string ) => {
return ( document.querySelector( id ) as HTMLTextAreaElement ).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(
'#set-post-thumbnail img'
) as HTMLImageElement )?.src || ''
);
};
// Parses categories into a string of true/false. Should be enough to catch any change.
const getProductCategoriesValue = () => {
return Array.from(
document.querySelectorAll(
'#product_cat-all #product_catchecklist input'
)
)
.map( ( x ) => ( x as HTMLInputElement ).checked )
.join( ',' );
};
// Parses all tags as string of tags separated by comma.
const getProductTagsValue = () => {
return Array.from( document.querySelectorAll( '#product_tag li' ) )
.map(
( x ) => ( ( x as HTMLLIElement ).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 };
};