From 2dff99b899834c925d9e9fe43f6c546082fcb8c0 Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 24 Apr 2024 17:52:28 +1000 Subject: [PATCH 001/226] Docs: handle "oneOf" in theme.json schema doc generation (#61024) * When generating theme.json docs, handle "oneOf" multiple types * Group multiple object types using curlies Co-authored-by: ramonjd Co-authored-by: t-hamano Co-authored-by: carolinan --- bin/api-docs/gen-theme-reference.js | 34 +++++++++++++++++-- .../theme-json-reference/theme-json-living.md | 24 ++++++------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/bin/api-docs/gen-theme-reference.js b/bin/api-docs/gen-theme-reference.js index 0ea9e282e5463..6bb0e5d5b4ce2 100644 --- a/bin/api-docs/gen-theme-reference.js +++ b/bin/api-docs/gen-theme-reference.js @@ -127,16 +127,44 @@ const getSettingsPropertiesMarkup = ( struct ) => { } let markup = '| Property | Type | Default | Props |\n'; - markup += '| --- | --- | --- |--- |\n'; + markup += '| --- | --- | --- |--- |\n'; ks.forEach( ( key ) => { const def = 'default' in props[ key ] ? props[ key ].default : ''; - const ps = + let type = props[ key ].type || ''; + let ps = props[ key ].type === 'array' ? keys( getPropertiesFromArray( props[ key ].items ) ) .sort() .join( ', ' ) : ''; - markup += `| ${ key } | ${ props[ key ].type } | ${ def } | ${ ps } |\n`; + + /* + * Handle`oneOf` type definitions - extract the type and properties. + * See: https://json-schema.org/understanding-json-schema/reference/combining#oneOf + */ + if ( props[ key ].oneOf && Array.isArray( props[ key ].oneOf ) ) { + if ( ! type ) { + type = props[ key ].oneOf + .map( ( item ) => item.type ) + .join( ', ' ); + } + + if ( ! ps ) { + ps = props[ key ].oneOf + .map( ( item ) => + item?.type === 'object' && item?.properties + ? '_{' + + keys( getPropertiesFromArray( item ) ) + .sort() + .join( ', ' ) + + '}_' + : '' + ) + .join( ' ' ); + } + } + + markup += `| ${ key } | ${ type } | ${ def } | ${ ps } |\n`; } ); return markup; diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 0377f6a3c3e05..9120af4f7456c 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -58,7 +58,7 @@ Please note that when using this setting, `styles.spacing.padding` should always Settings related to borders. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | color | boolean | false | | | radius | boolean | false | | | style | boolean | false | | @@ -71,7 +71,7 @@ Settings related to borders. Settings related to shadows. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | defaultPresets | boolean | true | | | presets | array | | name, shadow, slug | @@ -82,7 +82,7 @@ Settings related to shadows. Settings related to colors. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | background | boolean | true | | | custom | boolean | true | | | customDuotone | boolean | true | | @@ -106,7 +106,7 @@ Settings related to colors. Settings related to background. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | backgroundImage | boolean | false | | | backgroundSize | boolean | false | | @@ -117,7 +117,7 @@ Settings related to background. Settings related to dimensions. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | aspectRatio | boolean | false | | | minHeight | boolean | false | | @@ -128,7 +128,7 @@ Settings related to dimensions. Settings related to layout. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | contentSize | string | | | | wideSize | string | | | | allowEditing | boolean | true | | @@ -141,7 +141,7 @@ Settings related to layout. Settings related to the lightbox. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | enabled | boolean | | | | allowEditing | boolean | | | @@ -152,7 +152,7 @@ Settings related to the lightbox. Settings related to position. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | sticky | boolean | false | | --- @@ -162,8 +162,8 @@ Settings related to position. Settings related to spacing. | Property | Type | Default | Props | -| --- | --- | --- |--- | -| blockGap | undefined | null | | +| --- | --- | --- |--- | +| blockGap | boolean, null | null | | | margin | boolean | false | | | padding | boolean | false | | | units | array | px,em,rem,vh,vw,% | | @@ -178,11 +178,11 @@ Settings related to spacing. Settings related to typography. | Property | Type | Default | Props | -| --- | --- | --- |--- | +| --- | --- | --- |--- | | customFontSize | boolean | true | | | fontStyle | boolean | true | | | fontWeight | boolean | true | | -| fluid | undefined | false | | +| fluid | object, boolean | false | _{maxViewportWidth, minFontSize, minViewportWidth}_ | | letterSpacing | boolean | true | | | lineHeight | boolean | false | | | textAlign | boolean | true | | From 0a3c1c65786ecedad11a128a93f915f5d6fb7250 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:38:05 +0900 Subject: [PATCH 002/226] Site Editor: Tweak Template/Template Parts/Page creation modal (#61005) Co-authored-by: t-hamano Co-authored-by: jasmussen --- packages/edit-site/src/components/add-new-page/index.js | 8 +++++++- .../add-custom-generic-template-modal-content.js | 3 +++ .../src/components/create-template-part-modal/index.js | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js index 09d2e17ff94d1..56544c83f491b 100644 --- a/packages/edit-site/src/components/add-new-page/index.js +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -72,16 +72,22 @@ export default function AddNewPageModal( { onSave, onClose } ) {
- - ) } - /> - - - - + { __( 'Open Media Library' ) } + + ) } + /> + ) } { isMobile && ( - + { ( category ) => ( ( <> @@ -183,8 +192,10 @@ function InserterMenu( ), [ destinationRootClientId, + onHoverPattern, onInsertPattern, onClickPatternCategory, + patternFilter, selectedPatternCategory, showPatternPanel, ] @@ -197,13 +208,22 @@ function InserterMenu( selectedCategory={ selectedMediaCategory } onSelectCategory={ setSelectedMediaCategory } onInsert={ onInsert } - /> + > + { showMediaPanel && ( + + ) } + ), [ destinationRootClientId, onInsert, selectedMediaCategory, setSelectedMediaCategory, + showMediaPanel, ] ); @@ -224,10 +244,6 @@ function InserterMenu( } ) ); const showAsTabs = ! delayedFilterValue && ( showPatterns || showMedia ); - const showMediaPanel = - selectedTab === 'media' && - ! delayedFilterValue && - selectedMediaCategory; // When the pattern panel is showing, we want to use zoom out mode useZoomOut( showPatternPanel ); @@ -243,7 +259,7 @@ function InserterMenu( return (
) }
- { showMediaPanel && ( - - ) } { showInserterHelpPanel && hoveredItem && ( button { - display: block; - } + .block-editor-inserter__media-list__item-preview-options > button { + display: block; } + } - .block-editor-inserter__media-list__item-preview-options { - position: absolute; - right: $grid-unit-10; - top: $grid-unit-10; + .block-editor-inserter__media-list__item-preview-options { + position: absolute; + right: $grid-unit-10; + top: $grid-unit-10; - > button { - background: $white; - border-radius: $radius-block-ui; - display: none; + > button { + background: $white; + border-radius: $radius-block-ui; + display: none; - // These styles are important so as focus isn't lost - // when the button is focused and we hover away. - &.is-opened, - &:focus { - display: block; - } + // These styles are important so as focus isn't lost + // when the button is focused and we hover away. + &.is-opened, + &:focus { + display: block; + } - &:hover { - box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + &:hover { + box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; } } } +} - .block-editor-inserter__media-list__item { - height: 100%; +.block-editor-inserter__media-list__item { + height: 100%; - .block-editor-inserter__media-list__item-preview { - display: flex; - align-items: center; - overflow: hidden; - border-radius: 2px; + .block-editor-inserter__media-list__item-preview { + display: flex; + align-items: center; + overflow: hidden; + border-radius: 2px; - > * { - margin: 0 auto; - max-width: 100%; - } + > * { + margin: 0 auto; + max-width: 100%; + } - .block-editor-inserter__media-list__item-preview-spinner { - display: flex; - height: 100%; - width: 100%; - position: absolute; - justify-content: center; - background: rgba($white, 0.7); - align-items: center; - pointer-events: none; - } + .block-editor-inserter__media-list__item-preview-spinner { + display: flex; + height: 100%; + width: 100%; + position: absolute; + justify-content: center; + background: rgba($white, 0.7); + align-items: center; + pointer-events: none; } + } - &:focus .block-editor-inserter__media-list__item-preview { - box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + &:focus .block-editor-inserter__media-list__item-preview { + box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; } } @@ -790,8 +714,4 @@ $block-inserter-tabs-height: 44px; height: 100%; } } - - .block-editor-inserter__media-dialog { - position: static; - } } diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js index 940e4b0d47b03..24fff3e579f68 100644 --- a/test/e2e/specs/editor/blocks/image.spec.js +++ b/test/e2e/specs/editor/blocks/image.spec.js @@ -448,7 +448,7 @@ test.describe( 'Image', () => { await blockLibrary .getByRole( 'tabpanel', { name: 'Media' } ) - .getByRole( 'button', { name: 'Openverse' } ) + .getByRole( 'tab', { name: 'Openverse' } ) .click(); } diff --git a/test/e2e/specs/editor/various/inserting-blocks.spec.js b/test/e2e/specs/editor/various/inserting-blocks.spec.js index b3da80c12ab09..6e19fd31a71b0 100644 --- a/test/e2e/specs/editor/various/inserting-blocks.spec.js +++ b/test/e2e/specs/editor/various/inserting-blocks.spec.js @@ -724,18 +724,10 @@ test.describe( 'insert media from inserter', () => { } ) => { await admin.createNewPost(); - await page.click( - 'role=region[name="Editor top bar"i] >> role=button[name="Toggle block inserter"i]' - ); - await page.click( - 'role=region[name="Block Library"i] >> role=tab[name="Media"i]' - ); - await page.click( - '[aria-label="Media categories"i] >> role=button[name="Images"i]' - ); - await page.click( - `role=listbox[name="Media List"i] >> role=option[name="${ uploadedMedia.title.raw }"]` - ); + await page.getByLabel( 'Toggle block inserter' ).click(); + await page.getByRole( 'tab', { name: 'Media' } ).click(); + await page.getByRole( 'tab', { name: 'Images' } ).click(); + await page.getByLabel( uploadedMedia.title.raw ).click(); await expect.poll( editor.getEditedPostContent ).toBe( `
${ uploadedMedia.alt_text }
From 58b8abcce4d29381da5c9804a40f45554c35b1af Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 24 Apr 2024 18:06:06 +0400 Subject: [PATCH 006/226] Blocks: Add a warning when registering variation without a name (#61037) Co-authored-by: Mamaduka Co-authored-by: tyxla Co-authored-by: ndiego Co-authored-by: jsnajdr --- packages/blocks/src/api/registration.js | 4 ++++ packages/blocks/src/api/test/registration.js | 22 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 6633adf40050c..52a2f30983732 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -723,6 +723,10 @@ export const getBlockVariations = ( blockName, scope ) => { * ``` */ export const registerBlockVariation = ( blockName, variation ) => { + if ( typeof variation.name !== 'string' ) { + console.warn( 'Variation names must be unique strings.' ); + } + dispatch( blocksStore ).addBlockVariations( blockName, variation ); }; diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index f9a62346a72e4..81f45f1999803 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -13,6 +13,7 @@ import { select, dispatch } from '@wordpress/data'; import { registerBlockType, registerBlockCollection, + registerBlockVariation, unregisterBlockCollection, unregisterBlockType, setFreeformContentHandlerName, @@ -26,6 +27,7 @@ import { getBlockType, getBlockTypes, getBlockSupport, + getBlockVariations, hasBlockSupport, isReusableBlock, unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase @@ -1410,6 +1412,26 @@ describe( 'blocks', () => { expect( isReusableBlock( block ) ).toBe( false ); } ); } ); + + describe( 'registerBlockVariation', () => { + it( 'should warn when registering block variation without a name', () => { + registerBlockType( 'core/variation-block', defaultBlockSettings ); + registerBlockVariation( 'core/variation-block', { + title: 'Variation Title', + description: 'Variation description', + } ); + + expect( console ).toHaveWarnedWith( + 'Variation names must be unique strings.' + ); + expect( getBlockVariations( 'core/variation-block' ) ).toEqual( [ + { + title: 'Variation Title', + description: 'Variation description', + }, + ] ); + } ); + } ); } ); /* eslint-enable react/forbid-elements */ From 562e10a4a1971b878ab2a21c440a0ab5312cad12 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Wed, 24 Apr 2024 17:52:56 +0200 Subject: [PATCH 007/226] [Mobile] - Crash fixes when pasting HTML content (#60676) * Mobile - JSdom - Add dataset polyfill * Mobile - image-corrector - Set empty src attribute for file: values * Mobile - Jest config - Enable special-comment-converter tests * Update Changelog * Replace const name Co-authored-by: geriux Co-authored-by: twstokes --- .../raw-handling/image-corrector.native.js | 4 +++ packages/react-native-editor/CHANGELOG.md | 1 + .../react-native-editor/src/jsdom-patches.js | 31 +++++++++++++++++++ test/native/jest.config.js | 1 - 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/blocks/src/api/raw-handling/image-corrector.native.js b/packages/blocks/src/api/raw-handling/image-corrector.native.js index 10974e5dc10bd..c6a9288ede2d3 100644 --- a/packages/blocks/src/api/raw-handling/image-corrector.native.js +++ b/packages/blocks/src/api/raw-handling/image-corrector.native.js @@ -10,6 +10,10 @@ export default function imageCorrector( node ) { return; } + if ( node.src.indexOf( 'file:' ) === 0 ) { + node.setAttribute( 'src', '' ); + } + // Remove trackers and hardly visible images. if ( node.height === 1 || node.width === 1 ) { node.parentNode.removeChild( node ); diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index e9bd5e54a764d..4b8e321c72e1a 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,6 +10,7 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased +- [*] Fix a crash when pasting file images and special comment markup [#60476] ## 1.117.0 - [*] Add empty fallback option for the BottomSheetSelectControl component [#60333] diff --git a/packages/react-native-editor/src/jsdom-patches.js b/packages/react-native-editor/src/jsdom-patches.js index c86e4c82f3127..680bdcf1eb12e 100644 --- a/packages/react-native-editor/src/jsdom-patches.js +++ b/packages/react-native-editor/src/jsdom-patches.js @@ -257,6 +257,37 @@ Object.defineProperties( Node.prototype, { return sibling; }, }, + dataset: { + get() { + const node = this; + + // Helper function to convert property name to data-* attribute name + function toDataAttributeName( property ) { + return ( + 'data-' + + property.replace( + /[A-Z]/g, + ( match ) => '-' + match.toLowerCase() + ) + ); + } + return new Proxy( + {}, + { + set( _target, property, value ) { + const attributeName = toDataAttributeName( property ); + node.setAttribute( attributeName, value ); + return true; + }, + get( _target, property ) { + const attributeName = toDataAttributeName( property ); + return node.getAttribute( attributeName ); + }, + } + ); + }, + set() {}, + }, } ); class DOMParser { diff --git a/test/native/jest.config.js b/test/native/jest.config.js index 8bd77280dc550..71232758734fe 100644 --- a/test/native/jest.config.js +++ b/test/native/jest.config.js @@ -25,7 +25,6 @@ const RAW_HANDLING_UNSUPPORTED_UNIT_TESTS = [ 'html-formatting-remover', 'phrasing-content-reducer', 'figure-content-reducer', - 'special-comment-converter', 'normalise-blocks', 'image-corrector', ]; From 1ff2215992aa7157ae29b404b5ceea739d84257b Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 24 Apr 2024 18:55:05 +0200 Subject: [PATCH 008/226] Remove block-editor package usage from components (#60999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove references to @wordpress/block-editor from @wordpress/components package. This fixes an issue where dependencies crossed TypeScript project boundaries triggering errors like: > error TS5055: Cannot write file '…/alignment-matrix-control/icon.d.ts' because it would overwrite input file. This is because the block-editor project and the components project both attempt to write the same files because project references do not reflect this interdependence. In fact, block-editor depends on components. That would create a circular dependency between the projects which TypeScript does not allow. --------- Co-authored-by: sirreal Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: jsnajdr Co-authored-by: geriux --- .../src/popover/stories/index.story.tsx | 2 - .../src/utils/hooks/stories/use-cx.js | 157 ------------------ packages/components/tsconfig.json | 3 +- 3 files changed, 1 insertion(+), 161 deletions(-) delete mode 100644 packages/components/src/utils/hooks/stories/use-cx.js diff --git a/packages/components/src/popover/stories/index.story.tsx b/packages/components/src/popover/stories/index.story.tsx index 7f06045ac9b05..e0670ac275afa 100644 --- a/packages/components/src/popover/stories/index.story.tsx +++ b/packages/components/src/popover/stories/index.story.tsx @@ -7,8 +7,6 @@ import type { StoryFn, Meta } from '@storybook/react'; * WordPress dependencies */ import { useState, useRef, useEffect } from '@wordpress/element'; -// @ts-expect-error The `@wordpress/block-editor` is not typed -import { __unstableIframe as Iframe } from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/components/src/utils/hooks/stories/use-cx.js b/packages/components/src/utils/hooks/stories/use-cx.js deleted file mode 100644 index e7054a8f53952..0000000000000 --- a/packages/components/src/utils/hooks/stories/use-cx.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * External dependencies - */ -import { css } from '@emotion/react'; - -/** - * WordPress dependencies - */ -import { __unstableIframe as Iframe } from '@wordpress/block-editor'; -import { useMemo } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { useCx } from '..'; -import StyleProvider from '../../../style-provider'; -import { - createSlotFill, - Provider as SlotFillProvider, -} from '../../../slot-fill'; - -export default { - title: 'Components (Experimental)/useCx', -}; - -const Example = ( { serializedStyles, children } ) => { - const cx = useCx(); - const classes = cx( serializedStyles ); - return { children }; -}; - -const ExampleWithUseMemoWrong = ( { serializedStyles, children } ) => { - const cx = useCx(); - // Wrong: using 'useMemo' without adding 'cx' to the dependency list. - const classes = useMemo( - () => cx( serializedStyles ), - // eslint-disable-next-line react-hooks/exhaustive-deps - [ serializedStyles ] - ); - return { children }; -}; - -const ExampleWithUseMemoRight = ( { serializedStyles, children } ) => { - const cx = useCx(); - // Right: using 'useMemo' with 'cx' listed as a dependency. - const classes = useMemo( - () => cx( serializedStyles ), - [ cx, serializedStyles ] - ); - return { children }; -}; - -export const _slotFill = () => { - const { Fill, Slot } = createSlotFill( 'UseCxExampleSlot' ); - - const redText = css` - color: red; - `; - const blueText = css` - color: blue; - `; - const greenText = css` - color: green; - `; - - return ( - - - - - - - - - - - ); -}; - -export const _slotFillSimple = () => { - const { Fill, Slot } = createSlotFill( 'UseCxExampleSlotTwo' ); - - const redText = css` - color: red; - `; - - return ( - - - - - ); -}; - -export const _useMemoBadPractices = () => { - const redText = css` - color: red; - `; - const blueText = css` - color: blue; - `; - - return ( - <> - - This text should be red - - - This text should be blue - - - - ); -}; - -export const _default = () => { - const redText = css` - color: red; - `; - return ( - - ); -}; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 51c3da801cafb..81045d05c21fc 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -12,8 +12,7 @@ ], // TODO: Remove `skipLibCheck` after resolving duplicate declaration of the `process` variable // between `@types/webpack-env` (from @storybook packages) and `gutenberg-env`. - "skipLibCheck": true, - "strictNullChecks": true + "skipLibCheck": true }, "references": [ { "path": "../a11y" }, From 9924b68e995b107061a05911d879b009a1de93f9 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 24 Apr 2024 20:02:28 +0200 Subject: [PATCH 009/226] Add duplicate post action (#60637) Co-authored-by: jorgefilipecosta Co-authored-by: carolinan Co-authored-by: jameskoster Co-authored-by: annezazu Co-authored-by: ajitbohra --- .../sidebar/settings-sidebar/index.js | 62 ++++++++-- .../src/components/page-pages/index.js | 1 + .../sidebar-edit-mode/page-panels/index.js | 60 +++++++-- .../sidebar-navigation-screen-page/index.js | 57 +++++++-- .../src/components/post-actions/actions.js | 114 +++++++++++++++++- .../src/components/post-actions/index.js | 27 ++++- 6 files changed, 286 insertions(+), 35 deletions(-) diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index 05fb4fc91e69c..7c0e6fea68aaa 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -13,7 +13,7 @@ import { useEffect, useRef, } from '@wordpress/element'; -import { isRTL, __ } from '@wordpress/i18n'; +import { isRTL, __, sprintf } from '@wordpress/i18n'; import { drawerLeft, drawerRight } from '@wordpress/icons'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { @@ -28,6 +28,7 @@ import { privateApis as editorPrivateApis, } from '@wordpress/editor'; import { addQueryArgs } from '@wordpress/url'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -54,15 +55,6 @@ export const sidebars = { block: 'edit-post/block', }; -function onActionPerformed( actionId, items ) { - if ( actionId === 'move-to-trash' ) { - const postType = items[ 0 ].type; - document.location.href = addQueryArgs( 'edit.php', { - post_type: postType, - } ); - } -} - const SidebarContent = ( { tabName, keyboardShortcut, isEditingTemplate } ) => { const tabListRef = useRef( null ); // Because `PluginSidebar` renders a `ComplementaryArea`, we @@ -96,6 +88,56 @@ const SidebarContent = ( { tabName, keyboardShortcut, isEditingTemplate } ) => { selectedTabElement?.focus(); } }, [ tabName ] ); + const { createSuccessNotice } = useDispatch( noticesStore ); + + const onActionPerformed = useCallback( + ( actionId, items ) => { + switch ( actionId ) { + case 'move-to-trash': + { + const postType = items[ 0 ].type; + document.location.href = addQueryArgs( 'edit.php', { + post_type: postType, + } ); + } + break; + case 'duplicate-post': + { + const newItem = items[ 0 ]; + const title = + typeof newItem.title === 'string' + ? newItem.title + : newItem.title?.rendered; + createSuccessNotice( + sprintf( + // translators: %s: Title of the created post e.g: "Post 1". + __( '"%s" successfully created.' ), + title + ), + { + type: 'snackbar', + id: 'duplicate-post-action', + actions: [ + { + label: __( 'Edit' ), + onClick: () => { + const postId = newItem.id; + document.location.href = + addQueryArgs( 'post.php', { + post: postId, + action: 'edit', + } ); + }, + }, + ], + } + ); + } + break; + } + }, + [ createSuccessNotice ] + ); return ( { - if ( actionId === 'move-to-trash' ) { - history.push( { - path: '/' + items[ 0 ].type, - postId: undefined, - postType: undefined, - canvas: 'view', - } ); + switch ( actionId ) { + case 'move-to-trash': + { + history.push( { + path: '/' + items[ 0 ].type, + postId: undefined, + postType: undefined, + canvas: 'view', + } ); + } + break; + case 'duplicate-post': + { + const newItem = items[ 0 ]; + const title = + typeof newItem.title === 'string' + ? newItem.title + : newItem.title?.rendered; + createSuccessNotice( + sprintf( + // translators: %s: Title of the created post e.g: "Post 1". + __( '"%s" successfully created.' ), + title + ), + { + type: 'snackbar', + id: 'duplicate-post-action', + actions: [ + { + label: __( 'Edit' ), + onClick: () => { + history.push( { + path: undefined, + postId: newItem.id, + postType: newItem.type, + canvas: 'edit', + } ); + }, + }, + ], + } + ); + } + break; } }, - [ history ] + [ history, createSuccessNotice ] ); if ( ! hasResolved ) { diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js index e8df6a2b7677d..51251dcf846e6 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useDispatch, useSelect } from '@wordpress/data'; import { __experimentalVStack as VStack, @@ -17,6 +17,7 @@ import { safeDecodeURIComponent, filterURLForDisplay } from '@wordpress/url'; import { useEffect, useCallback } from '@wordpress/element'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -34,6 +35,7 @@ const { PostActions } = unlock( editorPrivateApis ); export default function SidebarNavigationScreenPage( { backPath } ) { const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); const history = useHistory(); + const { createSuccessNotice } = useDispatch( noticesStore ); const { params: { postId }, } = useLocation(); @@ -83,16 +85,53 @@ export default function SidebarNavigationScreenPage( { backPath } ) { const onActionPerformed = useCallback( ( actionId, items ) => { - if ( actionId === 'move-to-trash' ) { - history.push( { - path: '/' + items[ 0 ].type, - postId: undefined, - postType: undefined, - canvas: 'view', - } ); + switch ( actionId ) { + case 'move-to-trash': + { + history.push( { + path: '/' + items[ 0 ].type, + postId: undefined, + postType: undefined, + canvas: 'view', + } ); + } + break; + case 'duplicate-post': + { + const newItem = items[ 0 ]; + const title = + typeof newItem.title === 'string' + ? newItem.title + : newItem.title?.rendered; + createSuccessNotice( + sprintf( + // translators: %s: Title of the created post e.g: "Post 1". + __( '"%s" successfully created.' ), + title + ), + { + type: 'snackbar', + id: 'duplicate-post-action', + actions: [ + { + label: __( 'Edit' ), + onClick: () => { + history.push( { + path: undefined, + postId: newItem.id, + postType: newItem.type, + canvas: 'edit', + } ); + }, + }, + ], + } + ); + } + break; } }, - [ history ] + [ history, createSuccessNotice ] ); const featureImageAltText = featuredMediaAltText diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 7b91c2c5c96e3..286fce66e6186 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -6,7 +6,7 @@ import { addQueryArgs } from '@wordpress/url'; import { useDispatch } from '@wordpress/data'; import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf, _x } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useMemo, useState } from '@wordpress/element'; @@ -500,6 +500,117 @@ const renamePostAction = { }, }; +export const duplicatePostAction = { + id: 'duplicate-post', + label: _x( 'Duplicate', 'action label' ), + isEligible( { status } ) { + return status !== 'trash'; + }, + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const [ item ] = items; + const [ isCreatingPage, setIsCreatingPage ] = useState( false ); + const [ title, setTitle ] = useState( + sprintf( + /* translators: %s: Existing item title */ + __( '%s (Copy)' ), + getItemTitle( item ) + ) + ); + + const { saveEntityRecord } = useDispatch( coreStore ); + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticesStore ); + + async function createPage( event ) { + event.preventDefault(); + + if ( isCreatingPage ) { + return; + } + setIsCreatingPage( true ); + try { + const newItem = await saveEntityRecord( + 'postType', + item.type, + { + status: 'draft', + title, + slug: title || __( 'No title' ), + author: item.author, + comment_status: item.comment_status, + content: + typeof item.content === 'string' + ? item.content + : item.content.raw, + excerpt: item.excerpt.raw, + meta: item.meta, + parent: item.parent, + password: item.password, + template: item.template, + featured_media: item.featured_media, + menu_order: item.menu_order, + ping_status: item.ping_status, + }, + { throwOnError: true } + ); + + createSuccessNotice( + sprintf( + // translators: %s: Title of the created template e.g: "Category". + __( '"%s" successfully created.' ), + newItem.title?.rendered || title + ), + { + id: 'duplicate-post-action', + type: 'snackbar', + } + ); + + if ( onActionPerformed ) { + onActionPerformed( [ newItem ] ); + } + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while duplicating the page.' ); + + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } finally { + setIsCreatingPage( false ); + closeModal(); + } + } + return ( + + + + + + + + + + ); + }, +}; + const resetTemplateAction = { id: 'reset-template', label: __( 'Reset' ), @@ -782,6 +893,7 @@ export function usePostActions( onActionPerformed, actionIds = null ) { deleteTemplateAction, permanentlyDeletePostAction, postRevisionsAction, + duplicatePostAction, renamePostAction, renameTemplateAction, trashPostAction, diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js index 48d83f992e705..e0dcd14e56d52 100644 --- a/packages/editor/src/components/post-actions/index.js +++ b/packages/editor/src/components/post-actions/index.js @@ -34,11 +34,13 @@ const { const POST_ACTIONS_WHILE_EDITING = [ 'view-post', 'view-post-revisions', + 'duplicate-post', 'rename-post', 'move-to-trash', ]; export default function PostActions( { onActionPerformed, buttonProps } ) { + const [ isActionsMenuOpen, setIsActionsMenuOpen ] = useState( false ); const { postType, item } = useSelect( ( select ) => { const { getCurrentPostType, getCurrentPost } = select( editorStore ); return { @@ -68,6 +70,7 @@ export default function PostActions( { onActionPerformed, buttonProps } ) { } return ( + setIsActionsMenuOpen( ! isActionsMenuOpen ) + } { ...buttonProps } /> } + onOpenChange={ setIsActionsMenuOpen } placement="bottom-end" > - + { + setIsActionsMenuOpen( false ); + } } + /> ); } @@ -103,7 +116,8 @@ function DropdownMenuItemTrigger( { action, onClick } ) { } // Copied as is from packages/dataviews/src/item-actions.js -function ActionWithModal( { action, item, ActionTrigger } ) { +// With an added onClose prop. +function ActionWithModal( { action, item, ActionTrigger, onClose } ) { const [ isModalOpen, setIsModalOpen ] = useState( false ); const actionTriggerProps = { action, @@ -126,7 +140,10 @@ function ActionWithModal( { action, item, ActionTrigger } ) { > setIsModalOpen( false ) } + closeModal={ () => { + setIsModalOpen( false ); + onClose(); + } } /> ) } @@ -135,7 +152,8 @@ function ActionWithModal( { action, item, ActionTrigger } ) { } // Copied as is from packages/dataviews/src/item-actions.js -function ActionsDropdownMenuGroup( { actions, item } ) { +// With an added onClose prop. +function ActionsDropdownMenuGroup( { actions, item, onClose } ) { return ( { actions.map( ( action ) => { @@ -146,6 +164,7 @@ function ActionsDropdownMenuGroup( { actions, item } ) { action={ action } item={ item } ActionTrigger={ DropdownMenuItemTrigger } + onClose={ onClose } /> ); } From cfb5db9e00b1955e84f2d2d086053ab4915ec6f9 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 24 Apr 2024 18:08:09 +0000 Subject: [PATCH 010/226] Bump plugin version to 18.2.0 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 1302a078a2c2e..06faf9a84df5f 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.3 * Requires PHP: 7.2 - * Version: 18.2.0-rc.1 + * Version: 18.2.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 68ee222ddb1be..131b3ae8124da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "18.2.0-rc.1", + "version": "18.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "18.2.0-rc.1", + "version": "18.2.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index e8aca80dcb8ef..fc9b52821918d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "18.2.0-rc.1", + "version": "18.2.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From b6f4947f21b41a5e1ceee05b406624047fbbf70b Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Thu, 25 Apr 2024 03:22:20 +0900 Subject: [PATCH 011/226] View: Fix TypeScript types (#60919) * View: Fix TypeScript types * ItemGroup: Remove unneeded typecasting * Navigator: Fix View-related types * Update snapshots * Navigator: Remove polymorphism * View: Widen types * Navigator: Revert changes * Fixup * View: Make generic * View: Add back selector * Add changelog Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: DaniGuardiola Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 1 + .../card/test/__snapshots__/index.tsx.snap | 70 +++++++++---------- .../flex/test/__snapshots__/index.tsx.snap | 8 +-- .../components/src/item-group/item/hook.ts | 11 +-- .../test/__snapshots__/index.js.snap | 32 ++++----- .../surface/test/__snapshots__/index.tsx.snap | 20 +++--- packages/components/src/view/component.tsx | 29 +++++--- packages/components/src/view/types.ts | 6 -- 8 files changed, 85 insertions(+), 92 deletions(-) delete mode 100644 packages/components/src/view/types.ts diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 129fcfbccf3a3..12277f793d2d9 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,7 @@ - `InputControl`: Add a password visibility toggle story ([#60898](https://github.com/WordPress/gutenberg/pull/60898)). - `BoxControl`: Allow negative values for margin controls ([#60347](https://github.com/WordPress/gutenberg/pull/60347)). +- `View`: Fix prop types ([#60919](https://github.com/WordPress/gutenberg/pull/60919)). ### Bug Fix diff --git a/packages/components/src/card/test/__snapshots__/index.tsx.snap b/packages/components/src/card/test/__snapshots__/index.tsx.snap index 0b723732e3dba..60dcd306f3f52 100644 --- a/packages/components/src/card/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/card/test/__snapshots__/index.tsx.snap @@ -8,8 +8,8 @@ Snapshot Diff: @@ -1,8 +1,8 @@
@@ -25,8 +25,8 @@ Snapshot Diff: @@ -1,8 +1,8 @@
@@ -42,8 +42,8 @@ Snapshot Diff: @@ -1,8 +1,8 @@