@wordpress/block-library
Version:
Block library for the WordPress editor.
338 lines (316 loc) • 10.5 kB
JavaScript
/**
* External dependencies
*/
import { get } from 'lodash';
/**
* WordPress dependencies
*/
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
import { store as coreStore } from '@wordpress/core-data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { decodeEntities } from '@wordpress/html-entities';
import { cloneBlock, store as blocksStore } from '@wordpress/blocks';
/**
* Internal dependencies
*/
const {
name: queryLoopName
} = {
$schema: "https://schemas.wp.org/trunk/block.json",
apiVersion: 2,
name: "core/query",
title: "Query Loop",
category: "theme",
description: "An advanced block that allows displaying post types based on different query parameters and visual configurations.",
textdomain: "default",
attributes: {
queryId: {
type: "number"
},
query: {
type: "object",
"default": {
perPage: null,
pages: 0,
offset: 0,
postType: "post",
order: "desc",
orderBy: "date",
author: "",
search: "",
exclude: [],
sticky: "",
inherit: true,
taxQuery: null,
parents: []
}
},
tagName: {
type: "string",
"default": "div"
},
displayLayout: {
type: "object",
"default": {
type: "list"
}
},
namespace: {
type: "string"
}
},
providesContext: {
queryId: "queryId",
query: "query",
displayLayout: "displayLayout"
},
supports: {
align: ["wide", "full"],
html: false,
color: {
gradients: true,
link: true,
__experimentalDefaultControls: {
background: true,
text: true
}
},
__experimentalLayout: true
},
editorStyle: "wp-block-query-editor"
};
/**
* @typedef IHasNameAndId
* @property {string|number} id The entity's id.
* @property {string} name The entity's name.
*/
/**
* The object used in Query block that contains info and helper mappings
* from an array of IHasNameAndId objects.
*
* @typedef {Object} QueryEntitiesInfo
* @property {IHasNameAndId[]} entities The array of entities.
* @property {Object<string, IHasNameAndId>} mapById Object mapping with the id as key and the entity as value.
* @property {Object<string, IHasNameAndId>} mapByName Object mapping with the name as key and the entity as value.
* @property {string[]} names Array with the entities' names.
*/
/**
* Returns a helper object with mapping from Objects that implement
* the `IHasNameAndId` interface. The returned object is used for
* integration with `FormTokenField` component.
*
* @param {IHasNameAndId[]} entities The entities to extract of helper object.
* @return {QueryEntitiesInfo} The object with the entities information.
*/
export const getEntitiesInfo = entities => {
const mapping = entities === null || entities === void 0 ? void 0 : entities.reduce((accumulator, entity) => {
const {
mapById,
mapByName,
names
} = accumulator;
mapById[entity.id] = entity;
mapByName[entity.name] = entity;
names.push(entity.name);
return accumulator;
}, {
mapById: {},
mapByName: {},
names: []
});
return {
entities,
...mapping
};
};
/**
* Helper util to map records to add a `name` prop from a
* provided path, in order to handle all entities in the same
* fashion(implementing`IHasNameAndId` interface).
*
* @param {Object[]} entities The array of entities.
* @param {string} path The path to map a `name` property from the entity.
* @return {IHasNameAndId[]} An array of enitities that now implement the `IHasNameAndId` interface.
*/
export const mapToIHasNameAndId = (entities, path) => {
return (entities || []).map(entity => ({ ...entity,
name: decodeEntities(get(entity, path))
}));
};
/**
* Returns a helper object that contains:
* 1. An `options` object from the available post types, to be passed to a `SelectControl`.
* 2. A helper map with available taxonomies per post type.
*
* @return {Object} The helper object related to post types.
*/
export const usePostTypes = () => {
const postTypes = useSelect(select => {
var _getPostTypes;
const {
getPostTypes
} = select(coreStore);
const excludedPostTypes = ['attachment'];
const filteredPostTypes = (_getPostTypes = getPostTypes({
per_page: -1
})) === null || _getPostTypes === void 0 ? void 0 : _getPostTypes.filter(_ref => {
let {
viewable,
slug
} = _ref;
return viewable && !excludedPostTypes.includes(slug);
});
return filteredPostTypes;
}, []);
const postTypesTaxonomiesMap = useMemo(() => {
if (!(postTypes !== null && postTypes !== void 0 && postTypes.length)) return;
return postTypes.reduce((accumulator, type) => {
accumulator[type.slug] = type.taxonomies;
return accumulator;
}, {});
}, [postTypes]);
const postTypesSelectOptions = useMemo(() => (postTypes || []).map(_ref2 => {
let {
labels,
slug
} = _ref2;
return {
label: labels.singular_name,
value: slug
};
}), [postTypes]);
return {
postTypesTaxonomiesMap,
postTypesSelectOptions
};
};
/**
* Hook that returns the taxonomies associated with a specific post type.
*
* @param {string} postType The post type from which to retrieve the associated taxonomies.
* @return {Object[]} An array of the associated taxonomies.
*/
export const useTaxonomies = postType => {
const taxonomies = useSelect(select => {
const {
getTaxonomies
} = select(coreStore);
const filteredTaxonomies = getTaxonomies({
type: postType,
per_page: -1,
context: 'view'
});
return filteredTaxonomies;
}, [postType]);
return taxonomies;
};
/**
* Hook that returns whether a specific post type is hierarchical.
*
* @param {string} postType The post type to check.
* @return {boolean} Whether a specific post type is hierarchical.
*/
export function useIsPostTypeHierarchical(postType) {
return useSelect(select => {
const type = select(coreStore).getPostType(postType);
return (type === null || type === void 0 ? void 0 : type.viewable) && (type === null || type === void 0 ? void 0 : type.hierarchical);
}, [postType]);
}
/**
* Hook that returns the query properties' names defined by the active
* block variation, to determine which block's filters to show.
*
* @param {Object} attributes Block attributes.
* @return {string[]} An array of the query attributes.
*/
export function useAllowedControls(attributes) {
return useSelect(select => {
var _select$getActiveBloc;
return (_select$getActiveBloc = select(blocksStore).getActiveBlockVariation(queryLoopName, attributes)) === null || _select$getActiveBloc === void 0 ? void 0 : _select$getActiveBloc.allowControls;
}, [attributes]);
}
export function isControlAllowed(allowedControls, key) {
// Every controls is allowed if the list is not defined.
if (!allowedControls) {
return true;
}
return allowedControls.includes(key);
}
/**
* Clones a pattern's blocks and then recurses over that list of blocks,
* transforming them to retain some `query` attribute properties.
* For now we retain the `postType` and `inherit` properties as they are
* fundamental for the expected functionality of the block and don't affect
* its design and presentation.
*
* Returns the cloned/transformed blocks and array of existing Query Loop
* client ids for further manipulation, in order to avoid multiple recursions.
*
* @param {WPBlock[]} blocks The list of blocks to look through and transform(mutate).
* @param {Record<string,*>} queryBlockAttributes The existing Query Loop's attributes.
* @return {{ newBlocks: WPBlock[], queryClientIds: string[] }} An object with the cloned/transformed blocks and all the Query Loop clients from these blocks.
*/
export const getTransformedBlocksFromPattern = (blocks, queryBlockAttributes) => {
const {
query: {
postType,
inherit
}
} = queryBlockAttributes;
const clonedBlocks = blocks.map(block => cloneBlock(block));
const queryClientIds = [];
const blocksQueue = [...clonedBlocks];
while (blocksQueue.length > 0) {
var _block$innerBlocks;
const block = blocksQueue.shift();
if (block.name === 'core/query') {
block.attributes.query = { ...block.attributes.query,
postType,
inherit
};
queryClientIds.push(block.clientId);
}
(_block$innerBlocks = block.innerBlocks) === null || _block$innerBlocks === void 0 ? void 0 : _block$innerBlocks.forEach(innerBlock => {
blocksQueue.push(innerBlock);
});
}
return {
newBlocks: clonedBlocks,
queryClientIds
};
};
/**
* Helper hook that determines if there is an active variation of the block
* and if there are available specific patterns for this variation.
* If there are, these patterns are going to be the only ones suggested to
* the user in setup and replace flow, without including the default ones
* for Query Loop.
*
* If there are no such patterns, the default ones for Query Loop are going
* to be suggested.
*
* @param {string} clientId The block's client ID.
* @param {Object} attributes The block's attributes.
* @return {string} The block name to be used in the patterns suggestions.
*/
export function useBlockNameForPatterns(clientId, attributes) {
const activeVariationName = useSelect(select => {
var _select$getActiveBloc2;
return (_select$getActiveBloc2 = select(blocksStore).getActiveBlockVariation(queryLoopName, attributes)) === null || _select$getActiveBloc2 === void 0 ? void 0 : _select$getActiveBloc2.name;
}, [attributes]);
const blockName = `${queryLoopName}/${activeVariationName}`;
const activeVariationPatterns = useSelect(select => {
if (!activeVariationName) {
return;
}
const {
getBlockRootClientId,
__experimentalGetPatternsByBlockTypes
} = select(blockEditorStore);
const rootClientId = getBlockRootClientId(clientId);
return __experimentalGetPatternsByBlockTypes(blockName, rootClientId);
}, [clientId, activeVariationName]);
return activeVariationPatterns !== null && activeVariationPatterns !== void 0 && activeVariationPatterns.length ? blockName : queryLoopName;
}
//# sourceMappingURL=utils.js.map