import isEmpty from 'lodash/isEmpty'; import apiFetch from '@wordpress/api-fetch'; import { BiteStyleProps } from '@components/DesignPanel/types'; import { isLayoutVisualBlock } from '@components/ExtendControls/ControlHelpers'; export type Bite = { name: string; css: string; content: any; }; export type Util = { id: string; tw: string; type: string; style: BiteStyleProps[]; }; let flatUtils = [] as any; export const purgeBites = () => { flatUtils = []; const bitesData = getBites(); return bitesData; }; export const validateBites = (bitesData: any) => { const { utils } = bitesData; let validateString = ''; let relatedUtils = [] as any; if (isEmpty(utils)) { return ''; } utils.forEach((util: any) => { if (util?.related && util.related.length > 0) { relatedUtils = relatedUtils.concat(util.related); } else { validateString += util.tw + ' '; } }); relatedUtils.forEach((element: any) => { const relatedUtil = utils.find((util: any) => util.id === element.id); if (relatedUtil && relatedUtil.tw !== '') { // remove from validateString validateString = validateString.replace(relatedUtil.tw, ''); } }); return validateString.trim(); }; export const saveBites = (bitesData: any) => { const { utils, bites, blockStyles } = bitesData; const { id } = blockbite.data; /* Save the bites */ apiFetch({ path: `${blockbite.api}/bites/purge`, method: 'POST', data: { post_id: id, utils: JSON.stringify(utils), bites: JSON.stringify(bites), blockstyles: JSON.stringify(blockStyles), }, }).then((response: any) => { if (response.status === 200) { console.log('Bites purged successfully', response); } }); }; const getBites = () => { const biteWraps = wp.data.select('core/block-editor').getBlocks(); const utils = wp.data.select('biteStore/editor').getUtils(); let flatBlocks = [] as any; let blockStyles = [] as any; // get main blocks biteWraps .filter((block: any) => block.name === 'blockbite/bites-wrap') .forEach((wrapBlock: any) => { const { addAsBlockStyles } = wrapBlock.attributes; wrapBlock.innerBlocks.map((block: any) => { const { bitesId, metadata } = block.attributes; const transformedBlock = processBiteSync( block, addAsBlockStyles, true, utils ); const blockQuerySelector = document.getElementById( `block-${block.clientId}` ); let width = 400; let height = 400; if (blockQuerySelector) { width = blockQuerySelector.offsetWidth; height = blockQuerySelector.offsetHeight; } // iff metadata get name const name = metadata ? metadata.name : null; // save both verions flatBlocks.push({ id: bitesId, name: name ? name : block.name, component: wp.blocks.serialize(transformedBlock), raw: wp.blocks.serialize(block), width, height, }); if (addAsBlockStyles) { blockStyles.push({ name: transformedBlock.name, style_handle: bitesId, label: metadata ? metadata.name : transformedBlock.name, }); } }); }); return { utils: [...flatUtils], bites: flatBlocks, blockStyles: blockStyles, }; }; /* processBiteSync Transform the block to a clean blockwithout any bite styles except the new component name */ const processBiteSync = ( block: any, addAsBlockStyles: Boolean, firstLevel: Boolean, utils: any ) => { const newBlock = JSON.parse(JSON.stringify(block)); const { bitesId } = block.attributes; /* generate new bitename */ CreateUtil( newBlock, { attr: 'biteStyle', suffix: '_bi', classAttr: 'biteClass', motion: true, }, bitesId, addAsBlockStyles, firstLevel ); const isLayoutVisual = isLayoutVisualBlock(block.name); if (isLayoutVisual.layout) { CreateUtil( newBlock, { attr: 'flexStyle', suffix: '_fl', classAttr: 'flexClass', }, bitesId, addAsBlockStyles, firstLevel ); } if (isLayoutVisual.visual) { CreateUtil( newBlock, { attr: 'mediaStyle', suffix: '_me', classAttr: 'mediaClass', }, bitesId, addAsBlockStyles, firstLevel ); } newBlock.innerBlocks = block.innerBlocks.map((innerBlock: any) => processBiteSync(innerBlock, addAsBlockStyles, false, utils) ); return newBlock; }; const CreateUtil = ( newBlock: any, utilProps: any, bitesId: String, addAsBlockStyles: Boolean, firstLevel: Boolean ) => { const { attr, suffix, classAttr, motion } = utilProps; if (newBlock.attributes[attr] === undefined) return; // EG .bit1234_bi let biteName = `${bitesId}${suffix}`; // EG .is-style-bit1234 if (firstLevel && addAsBlockStyles && attr === 'biteStyle') { biteName = `is-style-${bitesId}`; // EG chained styles .is-style-bit1234.bite_fl or .is-style-bit1234.bite_mo } else if (firstLevel && addAsBlockStyles && attr == 'biteMotionStyle') { biteName = `is-style-${bitesId}.b${suffix}`; } else if (firstLevel && addAsBlockStyles && attr == 'flexStyle') { biteName = `is-style-${bitesId} > .b${suffix}`; } else if (firstLevel && addAsBlockStyles && attr == 'mediaStyle') { biteName = `is-style-${bitesId} > .b${suffix}`; } // add a bite as a modifier so the design editor can detect it // Ensure utilClass is a string newBlock.attributes[attr].findIndex((bite: any) => bite.id === 'bites'); newBlock.attributes[attr] = [ { id: 'bites', screen: 'all', value: biteName, }, ]; const utilClass = newBlock.attributes[classAttr] as string; // Split and classify the classes const [bitClasses, otherClasses] = utilClass .split(' ') .reduce<[string[], string[]]>( (acc, c) => { if (c.startsWith('bit_')) { acc[0].push(c); } else { acc[1].push(c); } return acc; }, [[], []] ); // Join the classified classes back into strings const bitClassesString = bitClasses.join(' ').trim(); let otherClassesString = otherClasses.join(' ').trim(); // clone const biteStyles = JSON.parse(JSON.stringify(newBlock.attributes[attr])); // join biteMotionStyle to biteStyle, no need to create a seperate class let motionStyle = null; if (motion) { motionStyle = JSON.parse( JSON.stringify(newBlock.attributes['biteMotionStyle']) ); const joinClass = newBlock.attributes['biteMotionClass']; otherClassesString = `${otherClassesString} ${joinClass}`; } // only add the bite styles they contain classnames if (otherClassesString) { let utilityClass = `${bitClassesString} ${otherClassesString}`; // remove trailing or leading spaces utilityClass = utilityClass.trim(); // only push if the utility class is not empty if (utilityClass) { flatUtils.push({ id: biteName, tw: utilityClass, type: attr, style: biteStyles, motion: motionStyle, related: bitClasses, }); } } /* 1. Add the bite class to the block by default bites in production without styles still can be changed afterwards 2. allow multiple bit_ classes within the symbol */ newBlock.attributes[classAttr] = `${biteName} ${bitClassesString}`; }; export default { purgeBites, validateBites, saveBites, };