woocommerce/plugins/woo-ai/src/utils/categoryCreator.ts

187 lines
5.1 KiB
TypeScript

/**
* Internal dependencies
*/
import { getAvailableCategories } from '../product-category/utils';
interface HTMLWPListElement extends HTMLElement {
wpList: {
settings: {
addAfter: (
returnedResponse: XMLDocument,
ajaxSettings: object,
wpListSettings: object
) => void;
};
};
}
declare global {
interface Window {
wpAjax: {
parseAjaxResponse: ( response: object ) => {
responses?: {
data?: string;
}[];
};
};
}
}
type NewCategory = {
name: string;
parent_id?: number;
};
/**
* Creates a category in the product category list. This function can only be used where the product category taxonomy list is available (e.g. on the product edit page).
*/
const createCategory = async ( category: NewCategory ) => {
const newCategoryInput = document.getElementById(
'newproduct_cat'
) as HTMLInputElement;
const newCategoryParentSelect = document.getElementById(
'newproduct_cat_parent'
) as HTMLSelectElement;
const newCategoryAddButton = document.getElementById(
'product_cat-add-submit'
) as HTMLButtonElement;
const addCategoryToggle = document.getElementById(
'product_cat-add-toggle'
) as HTMLButtonElement;
const categoryListElement = document.getElementById(
'product_catchecklist'
) as HTMLWPListElement;
if (
! [
newCategoryInput,
newCategoryParentSelect,
newCategoryAddButton,
addCategoryToggle,
categoryListElement,
].every( Boolean )
) {
throw new Error( 'Unable to find the category list elements' );
}
// show and hide the category inputs to make sure they are rendered at least once
addCategoryToggle.click();
addCategoryToggle.click();
// Preserve original addAfter function for restoration after use
const orgCatListAddAfter = categoryListElement.wpList.settings.addAfter;
const categoryCreatedPromise = new Promise< number >( ( resolve ) => {
categoryListElement.wpList.settings.addAfter = ( ...args ) => {
orgCatListAddAfter( ...args );
categoryListElement.wpList.settings.addAfter = orgCatListAddAfter;
const parsedResponse = window.wpAjax.parseAjaxResponse( args[ 0 ] );
if ( ! parsedResponse?.responses?.[ 0 ].data ) {
throw new Error( 'Unable to parse the ajax response' );
}
const parsedHtml = new DOMParser().parseFromString(
parsedResponse.responses[ 0 ].data,
'text/html'
);
const newlyAddedCategoryCheckbox = Array.from(
parsedHtml.querySelectorAll< HTMLInputElement >(
'input[name="tax_input[product_cat][]"]'
)
).find( ( input ) => {
return (
input.parentElement?.textContent?.trim() === category.name
);
} );
if ( ! newlyAddedCategoryCheckbox ) {
throw new Error( 'Unable to find the newly added category' );
}
resolve( Number( newlyAddedCategoryCheckbox.value ) );
};
} );
// Fill category name and select parent category if available
newCategoryInput.value = category.name;
if ( category.parent_id ) {
const parentEl = newCategoryParentSelect.querySelector(
'option[value="' + category.parent_id + '"]'
) as HTMLOptionElement;
if ( ! parentEl ) {
throw new Error( 'Unable to find the parent category in the list' );
}
newCategoryParentSelect.value = category.parent_id.toString();
parentEl.selected = true;
}
// click the add button to create the category
newCategoryAddButton.click();
return categoryCreatedPromise;
};
/**
* Gets the list of categories to create from a given path. The path is a string of categories separated by a > character. e.g. "Clothing > Shirts > T-Shirts"
*
* @param categoryPath
*/
const getCategoriesToCreate = async (
categoryPath: string
): Promise< NewCategory[] > => {
const categories: NewCategory[] = [];
const orderedList = categoryPath.split( ' > ' );
const availableCategories = await getAvailableCategories();
let parentCategoryId = 0;
orderedList.every( ( categoryName, index ) => {
const matchingCategory = availableCategories.find( ( category ) => {
return (
category.name === categoryName &&
category.parent === parentCategoryId
);
} );
if ( matchingCategory ) {
// This is the parent category ID for the next category in the path
parentCategoryId = matchingCategory.id;
} else {
categories.push( {
name: categoryName,
parent_id: parentCategoryId,
} );
for ( let i = index + 1; i < orderedList.length; i++ ) {
categories.push( {
name: orderedList[ i ],
} );
}
return false;
}
return true;
} );
return categories;
};
/**
* Creates categories from a given path. The path is a string of categories separated by a > character. e.g. "Clothing > Shirts > T-Shirts"
*
* @param categoryPath
*/
export const createCategoriesFromPath = async ( categoryPath: string ) => {
const categoriesToCreate = await getCategoriesToCreate( categoryPath );
while ( categoriesToCreate.length ) {
const newCategoryId = await createCategory(
categoriesToCreate.shift() as NewCategory
);
if ( categoriesToCreate.length ) {
// Set the parent ID of the next category in the list to the ID of the newly created category so that it is created as a child of the newly created category
categoriesToCreate[ 0 ].parent_id = newCategoryId;
}
}
};