UNPKG

@wordpress/block-editor

Version:
223 lines (215 loc) 7.59 kB
/** * External dependencies */ import memoize from 'memize'; /** * WordPress dependencies */ import { __, _n, sprintf } from '@wordpress/i18n'; import { getBlockTypes } from '@wordpress/blocks'; const globalStylesChangesCache = new Map(); const EMPTY_ARRAY = []; const translationMap = { caption: __('Caption'), link: __('Link'), button: __('Button'), heading: __('Heading'), h1: __('H1'), h2: __('H2'), h3: __('H3'), h4: __('H4'), h5: __('H5'), h6: __('H6'), 'settings.color': __('Color'), 'settings.typography': __('Typography'), 'styles.color': __('Colors'), 'styles.spacing': __('Spacing'), 'styles.background': __('Background'), 'styles.typography': __('Typography') }; const getBlockNames = memoize(() => getBlockTypes().reduce((accumulator, { name, title }) => { accumulator[name] = title; return accumulator; }, {})); const isObject = obj => obj !== null && typeof obj === 'object'; /** * Get the translation for a given global styles key. * @param {string} key A key representing a path to a global style property or setting. * @return {string|undefined} A translated key or undefined if no translation exists. */ function getTranslation(key) { if (translationMap[key]) { return translationMap[key]; } const keyArray = key.split('.'); if (keyArray?.[0] === 'blocks') { const blockName = getBlockNames()?.[keyArray[1]]; return blockName || keyArray[1]; } if (keyArray?.[0] === 'elements') { return translationMap[keyArray[1]] || keyArray[1]; } return undefined; } /** * A deep comparison of two objects, optimized for comparing global styles. * @param {Object} changedObject The changed object to compare. * @param {Object} originalObject The original object to compare against. * @param {string} parentPath A key/value pair object of block names and their rendered titles. * @return {string[]} An array of paths whose values have changed. */ function deepCompare(changedObject, originalObject, parentPath = '') { // We have two non-object values to compare. if (!isObject(changedObject) && !isObject(originalObject)) { /* * Only return a path if the value has changed. * And then only the path name up to 2 levels deep. */ return changedObject !== originalObject ? parentPath.split('.').slice(0, 2).join('.') : undefined; } // Enable comparison when an object doesn't have a corresponding property to compare. changedObject = isObject(changedObject) ? changedObject : {}; originalObject = isObject(originalObject) ? originalObject : {}; const allKeys = new Set([...Object.keys(changedObject), ...Object.keys(originalObject)]); let diffs = []; for (const key of allKeys) { const path = parentPath ? parentPath + '.' + key : key; const changedPath = deepCompare(changedObject[key], originalObject[key], path); if (changedPath) { diffs = diffs.concat(changedPath); } } return diffs; } /** * Returns an array of translated summarized global styles changes. * Results are cached using a Map() key of `JSON.stringify( { next, previous } )`. * * @param {Object} next The changed object to compare. * @param {Object} previous The original object to compare against. * @return {Array[]} A 2-dimensional array of tuples: [ "group", "translated change" ]. */ export function getGlobalStylesChangelist(next, previous) { const cacheKey = JSON.stringify({ next, previous }); if (globalStylesChangesCache.has(cacheKey)) { return globalStylesChangesCache.get(cacheKey); } /* * Compare the two changesets with normalized keys. * The order of these keys determines the order in which * they'll appear in the results. */ const changedValueTree = deepCompare({ styles: { background: next?.styles?.background, color: next?.styles?.color, typography: next?.styles?.typography, spacing: next?.styles?.spacing }, blocks: next?.styles?.blocks, elements: next?.styles?.elements, settings: next?.settings }, { styles: { background: previous?.styles?.background, color: previous?.styles?.color, typography: previous?.styles?.typography, spacing: previous?.styles?.spacing }, blocks: previous?.styles?.blocks, elements: previous?.styles?.elements, settings: previous?.settings }); if (!changedValueTree.length) { globalStylesChangesCache.set(cacheKey, EMPTY_ARRAY); return EMPTY_ARRAY; } // Remove duplicate results. const result = [...new Set(changedValueTree)] /* * Translate the keys. * Remove empty translations. */.reduce((acc, curr) => { const translation = getTranslation(curr); if (translation) { acc.push([curr.split('.')[0], translation]); } return acc; }, []); globalStylesChangesCache.set(cacheKey, result); return result; } /** * From a getGlobalStylesChangelist() result, returns an array of translated global styles changes, grouped by type. * The types are 'blocks', 'elements', 'settings', and 'styles'. * * @param {Object} next The changed object to compare. * @param {Object} previous The original object to compare against. * @param {{maxResults:number}} options Options. maxResults: results to return before truncating. * @return {string[]} An array of translated changes. */ export default function getGlobalStylesChanges(next, previous, options = {}) { let changeList = getGlobalStylesChangelist(next, previous); const changesLength = changeList.length; const { maxResults } = options; if (changesLength) { // Truncate to `n` results if necessary. if (!!maxResults && changesLength > maxResults) { changeList = changeList.slice(0, maxResults); } return Object.entries(changeList.reduce((acc, curr) => { const group = acc[curr[0]] || []; if (!group.includes(curr[1])) { acc[curr[0]] = [...group, curr[1]]; } return acc; }, {})).map(([key, changeValues]) => { const changeValuesLength = changeValues.length; const joinedChangesValue = changeValues.join(/* translators: Used between list items, there is a space after the comma. */ __(', ') // eslint-disable-line @wordpress/i18n-no-flanking-whitespace ); switch (key) { case 'blocks': { return sprintf( // translators: %s: a list of block names separated by a comma. _n('%s block.', '%s blocks.', changeValuesLength), joinedChangesValue); } case 'elements': { return sprintf( // translators: %s: a list of element names separated by a comma. _n('%s element.', '%s elements.', changeValuesLength), joinedChangesValue); } case 'settings': { return sprintf( // translators: %s: a list of theme.json setting labels separated by a comma. __('%s settings.'), joinedChangesValue); } case 'styles': { return sprintf( // translators: %s: a list of theme.json top-level styles labels separated by a comma. __('%s styles.'), joinedChangesValue); } default: { return sprintf( // translators: %s: a list of global styles changes separated by a comma. __('%s.'), joinedChangesValue); } } }); } return EMPTY_ARRAY; } //# sourceMappingURL=get-global-styles-changes.js.map