woocommerce/plugins/woocommerce-admin/client/navigation/utils.ts

260 lines
5.2 KiB
TypeScript
Raw Normal View History

/**
* External dependencies
*/
import { getAdminLink } from '@woocommerce/settings';
type MenuId = 'primary' | 'favorites' | 'plugins' | 'secondary';
interface Item {
id: string;
matchExpression: string;
url: string;
order: number;
title: string;
parent: string;
menuId: MenuId;
capability: string;
isCategory: boolean;
}
interface Category {
id: string;
isCategory: boolean;
menuId: MenuId;
migrate: boolean;
order: number;
parent: string;
title: string;
primary?: Item[];
favorites?: Item[];
plugins?: Item[];
secondary?: Item[];
}
/**
* Get the full URL if a relative path is passed.
*/
export const getFullUrl = ( url: string ): string => {
if ( url.indexOf( 'http' ) === 0 ) {
return url;
}
return getAdminLink( url );
};
/**
* Get a default regex expression to match the path and provided params.
*/
export const getDefaultMatchExpression = ( url: string ): string => {
const escapedUrl = url.replace( /[-\/\\^$*+?.()|[\]{}]/gi, '\\$&' );
const [ path, args, hash ] = escapedUrl.split( /\\\?|#/ );
const hashExpression = hash ? `(.*#${ hash }$)` : '';
const argsExpression = args
? args.split( '&' ).reduce( ( acc, param ) => {
return `${ acc }(?=.*[?|&]${ param }(&|$|#))`;
}, '' )
: '';
return '^' + path + argsExpression + hashExpression;
};
/**
* Get a match score for a menu item given a location.
*/
export const getMatchScore = (
location: Location,
itemUrl: string,
itemExpression: string | null = null
): number => {
if ( ! itemUrl ) {
return 0;
}
const fullUrl = getFullUrl( itemUrl );
const { href } = location;
// Return highest possible score for exact match.
if ( fullUrl === href ) {
return Number.MAX_SAFE_INTEGER;
}
const defaultExpression = getDefaultMatchExpression( fullUrl );
const regexp = new RegExp( itemExpression || defaultExpression, 'i' );
return ( decodeURIComponent( href ).match( regexp ) || [] ).length;
};
interface wcNavigation {
menuItems: Item[];
rootBackLabel: string;
rootBackUrl: string;
historyPatched: boolean;
}
declare global {
interface Window {
wcNavigation: wcNavigation;
}
}
interface wcNavigation {
menuItems: Item[];
rootBackLabel: string;
rootBackUrl: string;
historyPatched: boolean;
}
declare global {
interface Window {
wcNavigation: wcNavigation;
}
}
/**
* Get the closest matching item.
*
* @param {Array} items An array of items to match against.
*/
export const getMatchingItem = ( items: Item[] ): Item | null => {
let matchedItem = null;
let highestMatchScore = 0;
items.forEach( ( item ) => {
const score = getMatchScore(
window.location,
item.url,
item.matchExpression
);
if ( score > 0 && score >= highestMatchScore ) {
highestMatchScore = score;
matchedItem = item;
}
} );
return matchedItem || null;
};
/**
* Available menu IDs.
*/
export const menuIds: MenuId[] = [
'primary',
'favorites',
'plugins',
'secondary',
];
interface Category {
id: string;
isCategory: boolean;
menuId: MenuId;
migrate: boolean;
order: number;
parent: string;
title: string;
primary?: Item[];
favorites?: Item[];
plugins?: Item[];
secondary?: Item[];
}
/**
* Default categories for the menu.
*/
export const defaultCategories: {
[ key: string ]: Category;
} = {
woocommerce: {
id: 'woocommerce',
isCategory: true,
menuId: 'primary',
migrate: true,
order: 10,
parent: '',
title: 'WooCommerce',
},
};
/**
* Sort an array of menu items by their order property.
*
* @param {Array} menuItems Array of menu items.
* @return {Array} Sorted menu items.
*/
export const sortMenuItems = ( menuItems: Item[] ): Item[] => {
return menuItems.sort( ( a, b ) => {
if ( a.order === b.order ) {
return a.title.localeCompare( b.title );
}
return a.order - b.order;
} );
};
/**
Fix/37502: Correct spelling errors. (#37887) * change reference of Catpure to Capture Co-Authored-By: Vikram <93216400+vikrampm1@users.noreply.github.com> * change reference of expicitly to explicitly Co-Authored-By: Vikram <93216400+vikrampm1@users.noreply.github.com> * change reference 'cutted' to 'cut' * change reference 'determening' to 'determining' * change reference 'retreive' to 'retrieve' * change reference 'neccessary' to 'necessary' * change reference 'Fitler' to 'Filter' * change reference of "seperate" to "separate" Co-Authored-By: Ankit K Gupta <ankit.himcs@gmail.com> * change reference of "wether" to "whether" Co-Authored-By: Sumit Bagthariya <67687255+qasumitbagthariya@users.noreply.github.com> * change reference of "staus" to "status" * change reference of "retrive" to "retrieve" * change references of "gatways" to "gateways" * change references of "existant" to "existent" * change reference of "requries" to "requires" * change reference of "configuation" to "configuration" * change reference of "processsing" to "processing" * change reference of "represenation" to "representation" * change reference of "dimentions" to "dimensions" * change references of "reigster" to "register" * change reference of "colum" to "column" * change reference of "transtions" to "transitions" * change references of "intially" to "initially" * change references of "orignal" to "original" * change references of "deprected" to "deprecated" * change references of "paramter" to "parameter" * change reference of "intance" to "instance" * change reference of "elemets" to "elements" * change references of "funcitons" to "functions" * change reference of "specificed" to "specified" * change references of "atributes" to "attributes" * change reference of "tast" to "task" * change reference of "chaning" to "changing" * change reference of "retreiving" to "retrieving" * change reference of "caluclation" to "calculation" * change references of "Invaid" to "Invalid" * change references of "paramaters" to "parameters" * change reference of "Additonal" to "Additional" * change reference of "teh" to "the" * change reference of "evalutes" to "evaluates" * change reference of "addedd" to "added" * change reference of "excempt" to "exempt" * change reference of "sequencially" to "sequentially" * change reference of "previos" to "previous" * change reference of "elegible" to "eligible" * change references of "Boostrap" to "Bootstrap" * change references of "compability" to "compatibility" * change reference of "heirarchy" to "hierarchy" * change references of "visibilty" to "visibility" * change reference of "comparsion" to "comparison" * change reference of "capabilties" to "capabilities" * change reference of "datatores" to "datastores" * change reference of "occured" to "occurred" * change reference of "coresponding" to "corresponding" * change references of "thier" to "their" * change reference of "sucessfully" to "successfully" * change reference of "insde" to "inside" * change reference of "nagivation" to "navigation" * change references of "visiblity" to "visibility" * change reference of "documentaiton" to "documentation" * change reference of "anayltics" to "analytics" * change reference of "intalling" to "installing" * change reference of "mininum" to "minimum" * change references of "intial" to "initial" * change reference of "Feld" to "Field" * change reference of "taks" to "task" * change reference of "trasnfer" to "transfer" * change reference of "respone" to "response" * change reference of "Extenstions" to "Extensions" * change reference of "detault" to "default" * change reference of "simultanious" to "simultaneous" * change reference of "overides" to "overrides" * change references of "Indvidual" to "Individual" * change reference of "refering" to "referring" * change reference of "aginst" to "against" * change reference of "execuatable" to "executable" * change reference of "repsonse" to "response" * change reference of "documention" to "documentation" * change reference of "asumed" to "assumed" * change reference of "Minium" to "Minimum" * change reference of "unqiue" to "unique" * change references of "existance" to "existence" * change reference of "compatability" to "compatibility" * change reference of "Taxnomy" to "Taxonomy" * change reference of "quering" to "querying" * change reference of "retrun" to "return" * change reference of "informations" to "information" Co-Authored-By: Viktor Szépe <viktor@szepe.net> * Add changelog * Add changelog * Fix typo --------- Co-authored-by: Vikram <93216400+vikrampm1@users.noreply.github.com> Co-authored-by: Ankit K Gupta <ankit.himcs@gmail.com> Co-authored-by: Sumit Bagthariya <67687255+qasumitbagthariya@users.noreply.github.com> Co-authored-by: Viktor Szépe <viktor@szepe.net> Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
2023-05-08 07:55:09 +00:00
* Get a flat tree structure of all Categories and their children grouped by menuId
*
* @param {Array} menuItems Array of menu items.
* @param {Function} currentUserCan Callback method passed the capability to determine if a menu item is visible.
* @return {Object} Mapped menu items and categories.
*/
export const getMappedItemsCategories = (
menuItems: Item[],
currentUserCan: ( capability: string ) => boolean
): {
items: Record< string, Category | Record< string, Item[] > >;
categories: Record< string, Category | Item >;
} => {
const categories: {
[ key: string ]: Category | Item;
} = { ...defaultCategories };
const items = sortMenuItems( menuItems ).reduce(
(
acc: {
[ key: string ]: Category | { [ key: string ]: Item[] };
},
item: Item
) => {
// Set up the category if it doesn't yet exist.
if ( ! acc[ item.parent ] ) {
acc[ item.parent ] = {};
menuIds.forEach( ( menuId ) => {
acc[ item.parent ][ menuId ] = [];
} );
}
// Incorrect menu ID.
if ( ! acc[ item.parent ][ item.menuId ] ) {
return acc;
}
// User does not have permission to view this item.
if (
currentUserCan &&
item.capability &&
! currentUserCan( item.capability )
) {
return acc;
}
// Add categories.
if ( item.isCategory ) {
categories[ item.id ] = item;
}
const menuIdArray = acc[ item.parent ][ item.menuId ];
if ( menuIdArray ) {
menuIdArray.push( item );
}
return acc;
},
{}
);
return {
items,
categories,
};
};