UNPKG

@plone/volto

Version:
348 lines (305 loc) 8.88 kB
import React from 'react'; import { defineMessages } from 'react-intl'; import { useIntl } from 'react-intl'; import find from 'lodash/find'; import isEmpty from 'lodash/isEmpty'; import config from '@plone/volto/registry'; import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils'; const messages = defineMessages({ variation: { id: 'Variation', defaultMessage: 'Variation', }, styling: { id: 'Styling', defaultMessage: 'Styling', }, }); /** * Sets the field name as first field in schema */ function _addField(schema, name) { if (schema.fieldsets[0].fields.indexOf(name) === -1) { schema.fieldsets[0].fields.unshift(name); } } /** * Gets the blocksConfig from the props or from the global config object */ function getBlocksConfig(props) { const { blocks } = config; if (props.blocksConfig) { return props.blocksConfig; } return blocks?.blocksConfig; } /** * Utility function that adds the Select dropdown field to a schema */ export const addExtensionFieldToSchema = ({ schema, name, items, intl, title, description = '', insertFieldToOrder = _addField, }) => { const _ = intl.formatMessage; insertFieldToOrder(schema, name); const hasDefaultExtension = items?.findIndex(({ isDefault }) => isDefault) > -1; if (!hasDefaultExtension) { // eslint-disable-next-line console.warn('You should provide a default extension in extension:', name); } schema.properties[name] = { title: _(title), // TODO: is description sensible in here? The argument is not used anywhere // description: _(description), choices: items?.map(({ id, title }) => [ id, _({ id: title, defaultMessage: title }), ]), noValueOption: false, default: hasDefaultExtension ? items?.find((item) => item.isDefault).id : null, }; return schema; }; /** * A generic HOC that provides "schema enhancer functionality" for any custom * block extension. * * This enables blocks to have additional "variations", beyond the usual * `variations` field. This function is not directly used by Volto. * * To be used with a block configuration like: * * ``` * { * id: 'someBlockId', * extensions: { * '<someExtensionName>': { * items: [ * { * id: 'selectFacet', * title: 'Select', * view: SelectFacet, * isDefault: true, * }, * { * id: 'checkboxFacet', * title: 'Checkbox', * view: CheckboxFacet, * isDefault: false, * }, * ] * } * } * } * ``` */ export const withBlockSchemaEnhancer = (FormComponent, extensionName = 'vendor', insertFieldToOrder = _addField) => ({ ...props }) => { const { formData, schema: originalSchema } = props; const intl = useIntl(); const blocksConfig = getBlocksConfig(props); const blockType = formData['@type']; const extensionConfig = blocksConfig?.[blockType]?.extensions?.[extensionName]; if (!extensionConfig) return <FormComponent {...props} schema={originalSchema} />; const activeItemName = formData?.[extensionName]; let activeItem = extensionConfig.items?.find( (item) => item.id === activeItemName, ); if (!activeItem) activeItem = extensionConfig.items?.find((item) => item.isDefault); const schemaEnhancer = // For the main "variation" of blocks, allow simply passing a // schemaEnhancer in the block configuration activeItem?.['schemaEnhancer'] || (extensionName === 'variation' && blocksConfig?.[blockType]?.schemaEnhancer); let schema = schemaEnhancer ? schemaEnhancer({ schema: cloneDeepSchema(originalSchema), formData, intl, }) : cloneDeepSchema(originalSchema); const { title = messages.variation, description } = extensionConfig; if (extensionConfig.items?.length > 1) { addExtensionFieldToSchema({ schema, name: extensionName, items: extensionConfig.items || [], intl, title, description, insertFieldToOrder, }); } return <FormComponent {...props} schema={schema} />; }; /** * Apply block variation schema enhancers to the provided schema, using block * information from the provided block data (as `formData`). * * Blocks can be enhanced with variations declared like: * * ``` * { * id: 'searchBlock', * schemaEnhancer: ({schema, formData, intl}) => schema, * variations: [ * { * id: 'facetsRightSide', * title: 'Facets on right side', * view: RightColumnFacets, * isDefault: true, * }, * { * id: 'facetsLeftSide', * title: 'Facets on left side', * view: LeftColumnFacets, * isDefault: false, * schemaEnhancer: ({schema, formData, intl}) => schema, * }, * ], * * ``` * Notice that each variation can declare an option schema enhancer, and each * block supports an optional `schemaEnhancer` function. */ export const applySchemaEnhancer = ({ schema: originalSchema, formData, intl, blocksConfig = config.blocks.blocksConfig, navRoot, contentType, }) => { let schema, schemaEnhancer; const blockType = formData['@type']; const variations = blocksConfig?.[blockType]?.variations || []; if (variations.length === 0) { // No variations present but we finalize the schema with a schemaEnhancer // in the block config (if present) schemaEnhancer = blocksConfig?.[blockType]?.schemaEnhancer; if (schemaEnhancer) schema = schemaEnhancer({ schema: cloneDeepSchema(originalSchema), formData, intl, navRoot, contentType, }); return schema || originalSchema; } const activeItemName = formData?.variation; let activeItem = variations.find((item) => item.id === activeItemName); if (!activeItem) activeItem = variations.find((item) => item.isDefault); schemaEnhancer = activeItem?.['schemaEnhancer']; schema = schemaEnhancer ? schemaEnhancer({ schema: cloneDeepSchema(originalSchema), formData, intl, navRoot, contentType, }) : cloneDeepSchema(originalSchema); // Finalize the schema with a schemaEnhancer in the block config; schemaEnhancer = blocksConfig?.[blockType]?.schemaEnhancer; if (schemaEnhancer) schema = schemaEnhancer({ schema, formData, intl, navRoot, contentType }); return schema || originalSchema; }; /** * A HOC that enhances the incoming schema prop with block variations support * by: * * - applies the selected variation's schema enhancer * - adds the variation selection input (as a choice widget) */ export const withVariationSchemaEnhancer = (FormComponent) => (props) => { const { formData, schema: originalSchema, navRoot, contentType } = props; const intl = useIntl(); const blocksConfig = getBlocksConfig(props); const blockType = formData['@type']; const variations = blocksConfig[blockType]?.variations || []; let schema = cloneDeepSchema(originalSchema); if (variations.length > 1) { schema = addExtensionFieldToSchema({ schema, name: 'variation', items: variations, intl, title: messages.variation, insertFieldToOrder: _addField, }); } schema = applySchemaEnhancer({ schema, formData, intl, blocksConfig, navRoot, contentType, }); return <FormComponent {...props} schema={schema} />; }; export const EMPTY_STYLES_SCHEMA = { fieldsets: [ { id: 'default', title: 'Default', fields: [], }, ], properties: {}, required: [], }; /** * Adds the `styles` field and 'styling' fieldset in a given schema */ export const addStyling = ({ schema, formData, intl }) => { if (isEmpty(find(schema.fieldsets, { id: 'styling' }))) { schema.fieldsets.push({ id: 'styling', title: intl.formatMessage(messages.styling), fields: ['styles'], }); schema.properties.styles = { widget: 'object', title: intl.formatMessage(messages.styling), schema: cloneDeepSchema(EMPTY_STYLES_SCHEMA), }; } return schema; }; /** * Allows compose-like declaration of schema enhancers * * Example usage: * const schemaEnhancer = composeSchema(schemaEnhancerA, schemaEnhancerB) * * where each enhancer is a function with signature * ({schema, formData, ...rest}) => schema * */ export function composeSchema() { const enhancers = Array.from(arguments); const composer = (args) => { const props = enhancers.reduce( (acc, enhancer) => (enhancer ? { ...acc, schema: enhancer(acc) } : acc), { ...args }, ); return props.schema; }; return composer; }