From 471c8b2880e3374b075f583d365c86ccfbedbefb Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 16 May 2024 16:38:35 +0200 Subject: [PATCH] Update: Move pattern actions to the editor package. [take 2] (#61612) Co-authored-by: jorgefilipecosta Co-authored-by: youknowriad --- package-lock.json | 19 +- package.json | 1 + packages/edit-site/package.json | 1 - .../dataviews-pattern-actions.js | 247 +----------------- .../src/components/page-patterns/index.js | 18 +- packages/editor/package.json | 2 + .../src/components/post-actions/actions.js | 234 +++++++++++++++-- .../post-actions/export-pattern-action.js | 74 ++++++ .../export-pattern-action.native.js | 4 + test/unit/config/global-mocks.js | 9 + 10 files changed, 312 insertions(+), 297 deletions(-) create mode 100644 packages/editor/src/components/post-actions/export-pattern-action.js create mode 100644 packages/editor/src/components/post-actions/export-pattern-action.native.js diff --git a/package-lock.json b/package-lock.json index a8dbbedaf11cb..bba5bb915c4ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -173,6 +173,7 @@ "caniuse-lite": "1.0.30001579", "chalk": "4.1.1", "change-case": "4.1.2", + "client-zip": "^2.4.5", "commander": "9.2.0", "concurrently": "3.5.0", "copy-webpack-plugin": "10.2.0", @@ -22052,9 +22053,9 @@ "dev": true }, "node_modules/client-zip": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/client-zip/-/client-zip-2.4.4.tgz", - "integrity": "sha512-Ixk40BUI7VvNDxW7SCze20GbCuC+gjP4tGkXUpo6/W96bOf96HSed6cOQVeUOIe74SJAG/dIrBr7AtR4xBVnsA==" + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/client-zip/-/client-zip-2.4.5.tgz", + "integrity": "sha512-4y4d5ZeTH/szIAMQeC8ju67pxtvj+3u20wMGwOFrZk+pegy3aSEA2JkwgC8XVDTXP/Iqn1gyqNQXmkyBp4KLEQ==" }, "node_modules/cliui": { "version": "6.0.0", @@ -54145,7 +54146,6 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "client-zip": "^2.4.4", "clsx": "^2.1.1", "colord": "^2.9.2", "fast-deep-equal": "^3.1.3", @@ -54241,6 +54241,8 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", + "client-zip": "^2.4.5", "clsx": "^2.1.1", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", @@ -69216,7 +69218,6 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "client-zip": "^2.4.4", "clsx": "^2.1.1", "colord": "^2.9.2", "fast-deep-equal": "^3.1.3", @@ -69294,6 +69295,8 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", + "client-zip": "^2.4.5", "clsx": "^2.1.1", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", @@ -73181,9 +73184,9 @@ "dev": true }, "client-zip": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/client-zip/-/client-zip-2.4.4.tgz", - "integrity": "sha512-Ixk40BUI7VvNDxW7SCze20GbCuC+gjP4tGkXUpo6/W96bOf96HSed6cOQVeUOIe74SJAG/dIrBr7AtR4xBVnsA==" + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/client-zip/-/client-zip-2.4.5.tgz", + "integrity": "sha512-4y4d5ZeTH/szIAMQeC8ju67pxtvj+3u20wMGwOFrZk+pegy3aSEA2JkwgC8XVDTXP/Iqn1gyqNQXmkyBp4KLEQ==" }, "cliui": { "version": "6.0.0", diff --git a/package.json b/package.json index 2a1c5f0ae35f8..7995d6a9755a7 100644 --- a/package.json +++ b/package.json @@ -185,6 +185,7 @@ "caniuse-lite": "1.0.30001579", "chalk": "4.1.1", "change-case": "4.1.2", + "client-zip": "^2.4.5", "commander": "9.2.0", "concurrently": "3.5.0", "copy-webpack-plugin": "10.2.0", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 2e70debcc0ede..a8b12bdd15b61 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -69,7 +69,6 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "client-zip": "^2.4.4", "clsx": "^2.1.1", "colord": "^2.9.2", "fast-deep-equal": "^3.1.3", diff --git a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js index fe7d2fb5e32d1..a37ee426709cb 100644 --- a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js +++ b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js @@ -1,25 +1,10 @@ -/** - * External dependencies - */ -import { paramCase as kebabCase } from 'change-case'; -import { downloadZip } from 'client-zip'; - /** * WordPress dependencies */ -import { downloadBlob } from '@wordpress/blob'; import { __, _x, sprintf } from '@wordpress/i18n'; -import { - Button, - __experimentalHStack as HStack, - __experimentalVStack as VStack, - __experimentalText as Text, -} from '@wordpress/components'; + import { useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; -import { decodeEntities } from '@wordpress/html-entities'; -import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; -import { store as editorStore } from '@wordpress/editor'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; @@ -27,7 +12,6 @@ import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; * Internal dependencies */ import { unlock } from '../../lock-unlock'; -import { store as editSiteStore } from '../../store'; import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, @@ -39,235 +23,6 @@ const { useHistory, useLocation } = unlock( routerPrivateApis ); const { CreatePatternModalContents, useDuplicatePatternProps } = unlock( patternsPrivateApis ); -function getJsonFromItem( item ) { - return JSON.stringify( - { - __file: item.type, - title: item.title || item.name, - content: item.patternPost.content.raw, - syncStatus: item.patternPost.wp_pattern_sync_status, - }, - null, - 2 - ); -} - -export const exportJSONaction = { - id: 'export-pattern', - label: __( 'Export as JSON' ), - supportsBulk: true, - isEligible: ( item ) => item.type === PATTERN_TYPES.user, - callback: async ( items ) => { - if ( items.length === 1 ) { - return downloadBlob( - `${ kebabCase( items[ 0 ].title || items[ 0 ].name ) }.json`, - getJsonFromItem( items[ 0 ] ), - 'application/json' - ); - } - const nameCount = {}; - const filesToZip = items.map( ( item ) => { - const name = kebabCase( item.title || item.name ); - nameCount[ name ] = ( nameCount[ name ] || 0 ) + 1; - return { - name: `${ - name + - ( nameCount[ name ] > 1 - ? '-' + ( nameCount[ name ] - 1 ) - : '' ) - }.json`, - lastModified: new Date(), - input: getJsonFromItem( item ), - }; - } ); - return downloadBlob( - __( 'patterns-export' ) + '.zip', - await downloadZip( filesToZip ).blob(), - 'application/zip' - ); - }, -}; - -const canDeleteOrReset = ( item ) => { - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const isUserPattern = item.type === PATTERN_TYPES.user; - return isUserPattern || ( isTemplatePart && item.isCustom ); -}; - -export const deleteAction = { - id: 'delete-pattern', - label: __( 'Delete' ), - isEligible: ( item ) => { - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; - return canDeleteOrReset( item ) && ! hasThemeFile; - }, - hideModalHeader: true, - supportsBulk: true, - RenderModal: ( { items, closeModal, onActionPerformed } ) => { - const { __experimentalDeleteReusableBlock } = - useDispatch( reusableBlocksStore ); - const { createErrorNotice, createSuccessNotice } = - useDispatch( noticesStore ); - const { removeTemplates } = unlock( useDispatch( editorStore ) ); - - const deletePattern = async () => { - const promiseResult = await Promise.allSettled( - items.map( ( item ) => { - return __experimentalDeleteReusableBlock( item.id ); - } ) - ); - // If all the promises were fulfilled with success. - if ( - promiseResult.every( ( { status } ) => status === 'fulfilled' ) - ) { - let successMessage; - if ( promiseResult.length === 1 ) { - successMessage = sprintf( - /* translators: The posts's title. */ - __( '"%s" deleted.' ), - items[ 0 ].title - ); - } else { - successMessage = __( 'The patterns were deleted.' ); - } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'edit-site-page-trashed', - } ); - } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to delete a single pattern. - if ( promiseResult.length === 1 ) { - if ( promiseResult[ 0 ].reason?.message ) { - errorMessage = promiseResult[ 0 ].reason.message; - } else { - errorMessage = __( - 'An error occurred while deleting the pattern.' - ); - } - // If we were trying to delete multiple patterns. - } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' - ); - for ( const failedPromise of failedPromises ) { - if ( failedPromise.reason?.message ) { - errorMessages.add( failedPromise.reason.message ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while deleting the patterns.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while deleting the patterns: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while deleting the patterns: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } - } - }; - const deleteItem = () => { - if ( items[ 0 ].type === TEMPLATE_PART_POST_TYPE ) { - removeTemplates( items ); - } else { - deletePattern(); - } - if ( onActionPerformed ) { - onActionPerformed(); - } - closeModal(); - }; - let questionMessage; - if ( items.length === 1 ) { - questionMessage = sprintf( - // translators: %s: The page's title. - __( 'Are you sure you want to delete "%s"?' ), - decodeEntities( items[ 0 ].title || items[ 0 ].name ) - ); - } else if ( - items.length > 1 && - items[ 0 ].type === TEMPLATE_PART_POST_TYPE - ) { - questionMessage = sprintf( - // translators: %d: The number of template parts (2 or more). - __( 'Are you sure you want to delete %d template parts?' ), - items.length - ); - } else { - questionMessage = sprintf( - // translators: %d: The number of patterns (2 or more). - __( 'Are you sure you want to delete %d patterns?' ), - items.length - ); - } - return ( - - { questionMessage } - - - - - - ); - }, -}; - -export const resetAction = { - id: 'reset-action', - label: __( 'Reset' ), - isEligible: ( item ) => { - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; - return canDeleteOrReset( item ) && hasThemeFile; - }, - hideModalHeader: true, - RenderModal: ( { items, closeModal } ) => { - const [ item ] = items; - const { removeTemplate } = useDispatch( editSiteStore ); - return ( - - - { __( 'Reset to default and clear all customizations?' ) } - - - - - - - ); - }, -}; - export const duplicatePatternAction = { id: 'duplicate-pattern', label: _x( 'Duplicate', 'action label' ), diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index 724f60ba39103..f8314d65f34ff 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -47,9 +47,6 @@ import { OPERATOR_IS, } from '../../utils/constants'; import { - exportJSONaction, - resetAction, - deleteAction, duplicatePatternAction, duplicateTemplatePartAction, } from './dataviews-pattern-actions'; @@ -383,20 +380,13 @@ export default function DataviewsPatterns() { if ( type === TEMPLATE_PART_POST_TYPE ) { return [ editAction, - ...templatePartActions, duplicateTemplatePartAction, - resetAction, - deleteAction, + ...templatePartActions, ].filter( Boolean ); } - return [ - editAction, - ...patternActions, - duplicatePatternAction, - exportJSONaction, - resetAction, - deleteAction, - ].filter( Boolean ); + return [ editAction, duplicatePatternAction, ...patternActions ].filter( + Boolean + ); }, [ editAction, type, templatePartActions, patternActions ] ); const onChangeView = useCallback( ( newView ) => { diff --git a/packages/editor/package.json b/packages/editor/package.json index c87f49b1f04a6..7dba536e425dc 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -64,6 +64,8 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", + "client-zip": "^2.4.5", "clsx": "^2.1.1", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 194dd338f49e6..3628701367741 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -9,6 +9,8 @@ import { store as coreStore } from '@wordpress/core-data'; import { __, _n, sprintf, _x } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useMemo, useState } from '@wordpress/element'; +import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; +import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; import { Button, @@ -30,6 +32,10 @@ import { import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import isTemplateRevertable from '../../store/utils/is-template-revertable'; +import { exportPatternAsJSONAction } from './export-pattern-action'; + +// Patterns. +const { PATTERN_TYPES } = unlock( patternsPrivateApis ); function getItemTitle( item ) { if ( typeof item.title === 'string' ) { @@ -680,10 +686,22 @@ const duplicatePostAction = { }, }; +const isTemplatePartRevertable = ( item ) => { + if ( ! item ) { + return false; + } + const hasThemeFile = item.templatePart?.has_theme_file; + return canDeleteOrReset( item ) && hasThemeFile; +}; + const resetTemplateAction = { id: 'reset-template', label: __( 'Reset' ), - isEligible: isTemplateRevertable, + isEligible: ( item ) => { + return item.type === TEMPLATE_PART_POST_TYPE + ? isTemplatePartRevertable( item ) + : isTemplateRevertable( item ); + }, icon: backup, supportsBulk: true, hideModalHeader: true, @@ -694,40 +712,47 @@ const resetTemplateAction = { onActionPerformed, } ) => { const [ isBusy, setIsBusy ] = useState( false ); - const { revertTemplate } = unlock( useDispatch( editorStore ) ); + const { revertTemplate, removeTemplates } = unlock( + useDispatch( editorStore ) + ); const { saveEditedEntityRecord } = useDispatch( coreStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); const onConfirm = async () => { try { - for ( const template of items ) { - await revertTemplate( template, { - allowUndo: false, - } ); - await saveEditedEntityRecord( - 'postType', - template.type, - template.id + if ( items[ 0 ].type === TEMPLATE_PART_POST_TYPE ) { + await removeTemplates( items ); + } else { + for ( const template of items ) { + if ( template.type === TEMPLATE_POST_TYPE ) { + await revertTemplate( template, { + allowUndo: false, + } ); + await saveEditedEntityRecord( + 'postType', + template.type, + template.id + ); + } + } + createSuccessNotice( + items.length > 1 + ? sprintf( + /* translators: The number of items. */ + __( '%s items reset.' ), + items.length + ) + : sprintf( + /* translators: The template/part's name. */ + __( '"%s" reset.' ), + decodeEntities( getItemTitle( items[ 0 ] ) ) + ), + { + type: 'snackbar', + id: 'revert-template-action', + } ); } - - createSuccessNotice( - items.length > 1 - ? sprintf( - /* translators: The number of items. */ - __( '%s items reset.' ), - items.length - ) - : sprintf( - /* translators: The template/part's name. */ - __( '"%s" reset.' ), - decodeEntities( items[ 0 ].title.rendered ) - ), - { - type: 'snackbar', - id: 'revert-template-action', - } - ); } catch ( error ) { let fallbackErrorMessage; if ( items[ 0 ].type === TEMPLATE_POST_TYPE ) { @@ -988,6 +1013,157 @@ const renameTemplateAction = { }, }; +const canDeleteOrReset = ( item ) => { + const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; + const isUserPattern = item.type === PATTERN_TYPES.user; + return isUserPattern || ( isTemplatePart && item.isCustom ); +}; + +export const deletePatternAction = { + id: 'delete-pattern', + label: __( 'Delete' ), + isEligible: ( item ) => { + if ( ! item ) { + return false; + } + const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; + const hasThemeFile = + isTemplatePart && item.templatePart?.has_theme_file; + return canDeleteOrReset( item ) && ! hasThemeFile; + }, + hideModalHeader: true, + supportsBulk: true, + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const { __experimentalDeleteReusableBlock } = + useDispatch( reusableBlocksStore ); + const { createErrorNotice, createSuccessNotice } = + useDispatch( noticesStore ); + const { removeTemplates } = unlock( useDispatch( editorStore ) ); + + const deletePattern = async () => { + const promiseResult = await Promise.allSettled( + items.map( ( item ) => { + return __experimentalDeleteReusableBlock( item.id ); + } ) + ); + // If all the promises were fulfilled with success. + if ( + promiseResult.every( ( { status } ) => status === 'fulfilled' ) + ) { + let successMessage; + if ( promiseResult.length === 1 ) { + successMessage = sprintf( + /* translators: The posts's title. */ + __( '"%s" deleted.' ), + items[ 0 ].title + ); + } else { + successMessage = __( 'The patterns were deleted.' ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'edit-site-page-trashed', + } ); + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to delete a single pattern. + if ( promiseResult.length === 1 ) { + if ( promiseResult[ 0 ].reason?.message ) { + errorMessage = promiseResult[ 0 ].reason.message; + } else { + errorMessage = __( + 'An error occurred while deleting the pattern.' + ); + } + // If we were trying to delete multiple patterns. + } else { + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + if ( failedPromise.reason?.message ) { + errorMessages.add( failedPromise.reason.message ); + } + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while deleting the patterns.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while deleting the patterns: %s' + ), + [ ...errorMessages ][ 0 ] + ); + } else { + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while deleting the patterns: %s' + ), + [ ...errorMessages ].join( ',' ) + ); + } + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + } + }; + const deleteItem = () => { + if ( items[ 0 ].type === TEMPLATE_PART_POST_TYPE ) { + removeTemplates( items ); + } else { + deletePattern(); + } + if ( onActionPerformed ) { + onActionPerformed(); + } + closeModal(); + }; + let questionMessage; + if ( items.length === 1 ) { + questionMessage = sprintf( + // translators: %s: The page's title. + __( 'Are you sure you want to delete "%s"?' ), + decodeEntities( items[ 0 ].title || items[ 0 ].name ) + ); + } else if ( + items.length > 1 && + items[ 0 ].type === TEMPLATE_PART_POST_TYPE + ) { + questionMessage = sprintf( + // translators: %d: The number of template parts (2 or more). + __( 'Are you sure you want to delete %d template parts?' ), + items.length + ); + } else { + questionMessage = sprintf( + // translators: %d: The number of patterns (2 or more). + __( 'Are you sure you want to delete %d patterns?' ), + items.length + ); + } + return ( + + { questionMessage } + + + + + + ); + }, +}; + export function usePostActions( postType, onActionPerformed ) { const { postTypeObject } = useSelect( ( select ) => { @@ -1026,6 +1202,8 @@ export function usePostActions( postType, onActionPerformed ) { : false, ! isTemplateOrTemplatePart && renamePostAction, isTemplateOrTemplatePart && renameTemplateAction, + isPattern && exportPatternAsJSONAction, + isPattern && deletePatternAction, ! isTemplateOrTemplatePart && trashPostAction, ].filter( Boolean ); diff --git a/packages/editor/src/components/post-actions/export-pattern-action.js b/packages/editor/src/components/post-actions/export-pattern-action.js new file mode 100644 index 0000000000000..deeac4314f653 --- /dev/null +++ b/packages/editor/src/components/post-actions/export-pattern-action.js @@ -0,0 +1,74 @@ +/** + * External dependencies + */ +import { paramCase as kebabCase } from 'change-case'; +import { downloadZip } from 'client-zip'; + +/** + * WordPress dependencies + */ +import { downloadBlob } from '@wordpress/blob'; +import { __ } from '@wordpress/i18n'; +import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +// Patterns. +const { PATTERN_TYPES } = unlock( patternsPrivateApis ); + +function getJsonFromItem( item ) { + return JSON.stringify( + { + __file: item.type, + title: item.title || item.name, + content: item.patternPost.content.raw, + syncStatus: item.patternPost.wp_pattern_sync_status, + }, + null, + 2 + ); +} + +export const exportPatternAsJSONAction = { + id: 'export-pattern', + label: __( 'Export as JSON' ), + supportsBulk: true, + isEligible: ( item ) => { + if ( ! item.type ) { + return false; + } + return item.type === PATTERN_TYPES.user; + }, + callback: async ( items ) => { + if ( items.length === 1 ) { + return downloadBlob( + `${ kebabCase( items[ 0 ].title || items[ 0 ].name ) }.json`, + getJsonFromItem( items[ 0 ] ), + 'application/json' + ); + } + const nameCount = {}; + const filesToZip = items.map( ( item ) => { + const name = kebabCase( item.title || item.name ); + nameCount[ name ] = ( nameCount[ name ] || 0 ) + 1; + return { + name: `${ + name + + ( nameCount[ name ] > 1 + ? '-' + ( nameCount[ name ] - 1 ) + : '' ) + }.json`, + lastModified: new Date(), + input: getJsonFromItem( item ), + }; + } ); + return downloadBlob( + __( 'patterns-export' ) + '.zip', + await downloadZip( filesToZip ).blob(), + 'application/zip' + ); + }, +}; diff --git a/packages/editor/src/components/post-actions/export-pattern-action.native.js b/packages/editor/src/components/post-actions/export-pattern-action.native.js new file mode 100644 index 0000000000000..b09f5a034a552 --- /dev/null +++ b/packages/editor/src/components/post-actions/export-pattern-action.native.js @@ -0,0 +1,4 @@ +// Client-zip is meant to be used in a browser and is therefore released as an ES6 module only, +// in order for the native build to succeed we are importing a null action and avoiding importing +// the non working in native context client-zip module. +export const exportPatternAsJSONAction = null; diff --git a/test/unit/config/global-mocks.js b/test/unit/config/global-mocks.js index a64d01e55c896..fa9c1d0733aa9 100644 --- a/test/unit/config/global-mocks.js +++ b/test/unit/config/global-mocks.js @@ -10,6 +10,15 @@ jest.mock( '@wordpress/compose', () => { }; } ); +/** + * client-zip is meant to be used in a browser and is therefore released as an ES6 module only, + * in order to use it in node environment, we need to mock it. + * See: https://github.com/Touffy/client-zip/issues/28 + */ +jest.mock( 'client-zip', () => ( { + downloadZip: jest.fn(), +} ) ); + /** * The new gallery block format is not compatible with the use_BalanceTags option * so a flag is set in lib/compat.php to allow disabling the new block in this instance.