From e7724382e790db993682263121e917120d4daa68 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:00:48 +0200 Subject: [PATCH 01/74] Navigation: Fix active item hover color (#67732) * Navigation: Fix active item hover color * Add CHANGELOG entry * Fix duplicate Enhancements sections Co-authored-by: tyxla Co-authored-by: mirka <0mirka00@git.wordpress.org> --- packages/components/CHANGELOG.md | 13 +++++-------- .../src/navigation/styles/navigation-styles.tsx | 1 + 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2d2761924fb633..047d190ee3c5d8 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -11,6 +11,11 @@ - `GradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) - `CustomGradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) - `RangeControl`: Update the design of the range control marks ([#67611](https://github.com/WordPress/gutenberg/pull/67611)) +- `BorderBoxControl`: Reduce gap value when unlinked ([#67049](https://github.com/WordPress/gutenberg/pull/67049)). +- `DropdownMenu`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). +- `MenuItem`: Increase height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). +- `MenuItemsChoice`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). +- `Navigation`: Fix active item hover color ([#67732](https://github.com/WordPress/gutenberg/pull/67732)). ### Deprecations @@ -22,14 +27,6 @@ - `FormFileUpload`: Deprecate 36px default size ([#67438](https://github.com/WordPress/gutenberg/pull/67438)). - `FormTokenField`: Deprecate 36px default size ([#67454](https://github.com/WordPress/gutenberg/pull/67454)). - -### Enhancements - -- `BorderBoxControl`: Reduce gap value when unlinked ([#67049](https://github.com/WordPress/gutenberg/pull/67049)). -- `DropdownMenu`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). -- `MenuItem`: Increase height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). -- `MenuItemsChoice`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). - ### Experimental - `Menu`: throw when subcomponents are not rendered inside top level `Menu` ([#67411](https://github.com/WordPress/gutenberg/pull/67411)). diff --git a/packages/components/src/navigation/styles/navigation-styles.tsx b/packages/components/src/navigation/styles/navigation-styles.tsx index 580c0eef4dba8f..71dbccd7717c49 100644 --- a/packages/components/src/navigation/styles/navigation-styles.tsx +++ b/packages/components/src/navigation/styles/navigation-styles.tsx @@ -137,6 +137,7 @@ export const ItemBaseUI = styled.li` color: ${ COLORS.white }; > button, + .components-button:hover, > a { color: ${ COLORS.white }; opacity: 1; From 896b316bc774f0b2da4b4b916196fba4877945a0 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Mon, 9 Dec 2024 23:09:46 +0900 Subject: [PATCH 02/74] Components: Deprecate `COLORS.white` (#67649) * Components: Deprecate `COLORS.white` * Add changelogs * Update snapshot Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 3 ++ packages/components/src/menu/styles.ts | 2 +- .../navigation/styles/navigation-styles.tsx | 4 +- .../test/__snapshots__/index.tsx.snap | 44 +++++++++---------- .../styles.ts | 12 ++--- .../toggle-group-control/styles.ts | 2 +- .../components/src/utils/colors-values.js | 3 ++ .../components/src/utils/config-values.js | 1 - 8 files changed, 38 insertions(+), 33 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 047d190ee3c5d8..249d4f3f65b936 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,9 @@ - `GradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) - `CustomGradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974)) +- `Menu`: Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)). +- `Navigation` (deprecated): Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)). +- `ToggleGroupControl`: Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)). - `RangeControl`: Update the design of the range control marks ([#67611](https://github.com/WordPress/gutenberg/pull/67611)) - `BorderBoxControl`: Reduce gap value when unlinked ([#67049](https://github.com/WordPress/gutenberg/pull/67049)). - `DropdownMenu`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)). diff --git a/packages/components/src/menu/styles.ts b/packages/components/src/menu/styles.ts index 0e0752bf24cd10..cda5c7321f38b4 100644 --- a/packages/components/src/menu/styles.ts +++ b/packages/components/src/menu/styles.ts @@ -201,7 +201,7 @@ const baseItem = css` [aria-disabled='true'] ) { background-color: ${ COLORS.theme.accent }; - color: ${ COLORS.white }; + color: ${ COLORS.theme.accentInverted }; } /* Keyboard focus (focus-visible) */ diff --git a/packages/components/src/navigation/styles/navigation-styles.tsx b/packages/components/src/navigation/styles/navigation-styles.tsx index 71dbccd7717c49..aa0976a9a0f277 100644 --- a/packages/components/src/navigation/styles/navigation-styles.tsx +++ b/packages/components/src/navigation/styles/navigation-styles.tsx @@ -134,12 +134,12 @@ export const ItemBaseUI = styled.li` &.is-active { background-color: ${ COLORS.theme.accent }; - color: ${ COLORS.white }; + color: ${ COLORS.theme.accentInverted }; > button, .components-button:hover, > a { - color: ${ COLORS.white }; + color: ${ COLORS.theme.accentInverted }; opacity: 1; } } diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap index f344cd6ba16528..91e9f291ddf018 100644 --- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap @@ -72,7 +72,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -134,7 +134,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -158,12 +158,12 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; - color: #fff; + color: var(--wp-components-color-foreground-inverted, #fff); } @media not ( prefers-reduced-motion ) { @@ -183,7 +183,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-12:active { @@ -211,7 +211,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -235,7 +235,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); height: 32px; aspect-ratio: 1; padding-left: 0; @@ -259,7 +259,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = } .emotion-17:active { - background: #fff; + background: var(--wp-components-color-background, #fff); }
@@ -437,7 +437,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -499,7 +499,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -542,7 +542,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-13 { @@ -706,7 +706,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -768,7 +768,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -792,12 +792,12 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; - color: #fff; + color: var(--wp-components-color-foreground-inverted, #fff); } @media not ( prefers-reduced-motion ) { @@ -817,7 +817,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-12:active { @@ -845,7 +845,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -869,7 +869,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] user-select: none; width: 100%; z-index: 2; - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); height: 32px; aspect-ratio: 1; padding-left: 0; @@ -893,7 +893,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] } .emotion-17:active { - background: #fff; + background: var(--wp-components-color-background, #fff); }
@@ -1065,7 +1065,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio content: ''; position: absolute; pointer-events: none; - background: #1e1e1e; + background: var(--wp-components-color-foreground, #1e1e1e); outline: 2px solid transparent; outline-offset: -3px; --antialiasing-factor: 100; @@ -1127,7 +1127,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio background: transparent; border: none; border-radius: 1px; - color: #757575; + color: var(--wp-components-color-gray-700, #757575); fill: currentColor; cursor: pointer; display: -webkit-box; @@ -1170,7 +1170,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio } .emotion-12:active { - background: #fff; + background: var(--wp-components-color-background, #fff); } .emotion-13 { diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts index c0248f9b3f7f22..a53eced1219db4 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts +++ b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts @@ -38,7 +38,7 @@ export const buttonView = ( { background: transparent; border: none; border-radius: ${ CONFIG.radiusXSmall }; - color: ${ COLORS.gray[ 700 ] }; + color: ${ COLORS.theme.gray[ 700 ] }; fill: currentColor; cursor: pointer; display: flex; @@ -70,7 +70,7 @@ export const buttonView = ( { } &:active { - background: ${ CONFIG.controlBackgroundColor }; + background: ${ COLORS.ui.background }; } ${ isDeselectable && deselectable } @@ -79,7 +79,7 @@ export const buttonView = ( { `; const pressed = css` - color: ${ COLORS.white }; + color: ${ COLORS.theme.foregroundInverted }; &:active { background: transparent; @@ -87,11 +87,11 @@ const pressed = css` `; const deselectable = css` - color: ${ COLORS.gray[ 900 ] }; + color: ${ COLORS.theme.foreground }; &:focus { box-shadow: - inset 0 0 0 1px ${ COLORS.white }, + inset 0 0 0 1px ${ COLORS.ui.background }, 0 0 0 ${ CONFIG.borderWidthFocus } ${ COLORS.theme.accent }; outline: 2px solid transparent; } @@ -112,7 +112,7 @@ const isIconStyles = ( { }; return css` - color: ${ COLORS.gray[ 900 ] }; + color: ${ COLORS.theme.foreground }; height: ${ iconButtonSizes[ size ] }; aspect-ratio: 1; padding-left: 0; diff --git a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts index bb6efe476b2b2c..8376b66a5a86cf 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts +++ b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts @@ -39,7 +39,7 @@ export const toggleGroupControl = ( { content: ''; position: absolute; pointer-events: none; - background: ${ COLORS.gray[ 900 ] }; + background: ${ COLORS.theme.foreground }; // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index 1ad528d13f0108..439d464f1460b1 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -75,6 +75,9 @@ export const COLORS = Object.freeze( { * @deprecated Use semantic aliases in `COLORS.ui` or theme-ready variables in `COLORS.theme.gray`. */ gray: GRAY, // TODO: Stop exporting this when everything is migrated to `theme` or `ui` + /** + * @deprecated Prefer theme-ready variables in `COLORS.theme`. + */ white, alert: ALERT, /** diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js index 1bc3945f9b3b16..13b704540e9c4c 100644 --- a/packages/components/src/utils/config-values.js +++ b/packages/components/src/utils/config-values.js @@ -12,7 +12,6 @@ const CONTROL_PROPS = { controlPaddingXSmall: 8, controlPaddingXLarge: 12 * 1.3334, // TODO: Deprecate - controlBackgroundColor: COLORS.white, controlBoxShadowFocus: `0 0 0 0.5px ${ COLORS.theme.accent }`, controlHeight: CONTROL_HEIGHT, controlHeightXSmall: `calc( ${ CONTROL_HEIGHT } * 0.6 )`, From 9da58a749e2958f65c51352afd2f469379057fc4 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 9 Dec 2024 18:38:42 +0400 Subject: [PATCH 03/74] Data: Expose 'useSelect' warning to third-party consumers (#67735) Co-authored-by: Mamaduka Co-authored-by: youknowriad Co-authored-by: gziolo Co-authored-by: tyxla --- .../block-editor/src/hooks/test/font-size.js | 27 ++++++++++--------- .../src/hooks/test/use-query-select.js | 12 +++++++++ .../data/src/components/use-select/index.js | 2 +- .../src/components/use-select/test/index.js | 13 +++++++++ .../src/components/with-select/test/index.js | 13 +++++++++ 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/hooks/test/font-size.js b/packages/block-editor/src/hooks/test/font-size.js index 11cd024bf8a285..dc3fc9100e4759 100644 --- a/packages/block-editor/src/hooks/test/font-size.js +++ b/packages/block-editor/src/hooks/test/font-size.js @@ -19,6 +19,15 @@ import { import _fontSize from '../font-size'; const noop = () => {}; +const EMPTY_ARRAY = []; +const EMPTY_OBJECT = {}; +const fontSizes = [ + { + name: 'A larger font', + size: '32px', + slug: 'larger', + }, +]; function addUseSettingFilter( callback ) { addFilter( @@ -55,13 +64,7 @@ describe( 'useBlockProps', () => { registerBlockType( blockSettings.name, blockSettings ); addUseSettingFilter( ( result, path ) => { if ( 'typography.fontSizes' === path ) { - return [ - { - name: 'A larger font', - size: '32px', - slug: 'larger', - }, - ]; + return fontSizes; } if ( 'typography.fluid' === path ) { @@ -69,7 +72,7 @@ describe( 'useBlockProps', () => { } if ( 'layout' === path ) { - return {}; + return EMPTY_OBJECT; } return result; @@ -95,7 +98,7 @@ describe( 'useBlockProps', () => { registerBlockType( blockSettings.name, blockSettings ); addUseSettingFilter( ( result, path ) => { if ( 'typography.fontSizes' === path ) { - return []; + return EMPTY_ARRAY; } if ( 'typography.fluid' === path ) { @@ -103,7 +106,7 @@ describe( 'useBlockProps', () => { } if ( 'layout' === path ) { - return {}; + return EMPTY_OBJECT; } return result; @@ -132,7 +135,7 @@ describe( 'useBlockProps', () => { registerBlockType( blockSettings.name, blockSettings ); addUseSettingFilter( ( result, path ) => { if ( 'typography.fontSizes' === path ) { - return []; + return EMPTY_ARRAY; } if ( 'typography.fluid' === path ) { @@ -140,7 +143,7 @@ describe( 'useBlockProps', () => { } if ( 'layout' === path ) { - return {}; + return EMPTY_OBJECT; } return result; diff --git a/packages/core-data/src/hooks/test/use-query-select.js b/packages/core-data/src/hooks/test/use-query-select.js index 894851f79a9c78..873b897fff4e7d 100644 --- a/packages/core-data/src/hooks/test/use-query-select.js +++ b/packages/core-data/src/hooks/test/use-query-select.js @@ -17,9 +17,16 @@ import { render, screen, waitFor } from '@testing-library/react'; */ import useQuerySelect from '../use-query-select'; +/* eslint-disable @wordpress/wp-global-usage */ describe( 'useQuerySelect', () => { + const initialScriptDebug = globalThis.SCRIPT_DEBUG; let registry; + beforeAll( () => { + // Do not run hook in development mode; it will call `mapSelect` an extra time. + globalThis.SCRIPT_DEBUG = false; + } ); + beforeEach( () => { registry = createRegistry(); registry.registerStore( 'testStore', { @@ -31,6 +38,10 @@ describe( 'useQuerySelect', () => { } ); } ); + afterAll( () => { + globalThis.SCRIPT_DEBUG = initialScriptDebug; + } ); + const getTestComponent = ( mapSelectSpy, dependencyKey ) => ( props ) => { const dependencies = props[ dependencyKey ]; mapSelectSpy.mockImplementation( ( select ) => ( { @@ -188,3 +199,4 @@ describe( 'useQuerySelect', () => { ); } ); } ); +/* eslint-enable @wordpress/wp-global-usage */ diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index 6931743151773c..ea4869bb4e7a92 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -174,7 +174,7 @@ function Store( registry, suspense ) { listeningStores ); - if ( process.env.NODE_ENV === 'development' ) { + if ( globalThis.SCRIPT_DEBUG ) { if ( ! didWarnUnstableReference ) { const secondMapResult = mapSelect( select, registry ); if ( ! isShallowEqual( mapResult, secondMapResult ) ) { diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js index 3320f7d4638a59..6bb47d3c68e9ed 100644 --- a/packages/data/src/components/use-select/test/index.js +++ b/packages/data/src/components/use-select/test/index.js @@ -32,12 +32,24 @@ function counterStore( initialCount = 0, step = 1 ) { }; } +/* eslint-disable @wordpress/wp-global-usage */ describe( 'useSelect', () => { + const initialScriptDebug = globalThis.SCRIPT_DEBUG; let registry; + + beforeAll( () => { + // Do not run hook in development mode; it will call `mapSelect` an extra time. + globalThis.SCRIPT_DEBUG = false; + } ); + beforeEach( () => { registry = createRegistry(); } ); + afterAll( () => { + globalThis.SCRIPT_DEBUG = initialScriptDebug; + } ); + it( 'passes the relevant data to the component', () => { registry.registerStore( 'testStore', { reducer: () => ( { foo: 'bar' } ), @@ -1257,3 +1269,4 @@ describe( 'useSelect', () => { } ); } ); } ); +/* eslint-enable @wordpress/wp-global-usage */ diff --git a/packages/data/src/components/with-select/test/index.js b/packages/data/src/components/with-select/test/index.js index fe798354cba20d..9e01bb17cbb7e1 100644 --- a/packages/data/src/components/with-select/test/index.js +++ b/packages/data/src/components/with-select/test/index.js @@ -18,7 +18,19 @@ import withDispatch from '../../with-dispatch'; import { createRegistry } from '../../../registry'; import { RegistryProvider } from '../../registry-provider'; +/* eslint-disable @wordpress/wp-global-usage */ describe( 'withSelect', () => { + const initialScriptDebug = globalThis.SCRIPT_DEBUG; + + beforeAll( () => { + // Do not run HOC in development mode; it will call `mapSelect` an extra time. + globalThis.SCRIPT_DEBUG = false; + } ); + + afterAll( () => { + globalThis.SCRIPT_DEBUG = initialScriptDebug; + } ); + it( 'passes the relevant data to the component', () => { const registry = createRegistry(); registry.registerStore( 'reactReducer', { @@ -615,3 +627,4 @@ describe( 'withSelect', () => { expect( screen.getByRole( 'status' ) ).toHaveTextContent( 'second' ); } ); } ); +/* eslint-enable @wordpress/wp-global-usage */ From e85937ff6868a4b35856845755636c39dbbc555a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20K=C3=A4gy?= Date: Mon, 9 Dec 2024 15:51:18 +0100 Subject: [PATCH 04/74] Feature: Add `navigation.isLoading` state to core/router store (#67680) --- packages/interactivity-router/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 8a46d4ce350113..0c10e896ce1ef5 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -223,6 +223,7 @@ interface Store { state: { url: string; navigation: { + isLoading: boolean; hasStarted: boolean; hasFinished: boolean; }; @@ -237,6 +238,7 @@ export const { state, actions } = store< Store >( 'core/router', { state: { url: window.location.href, navigation: { + isLoading: false, hasStarted: false, hasFinished: false, }, @@ -289,6 +291,7 @@ export const { state, actions } = store< Store >( 'core/router', { return; } + navigation.isLoading = true; if ( loadingAnimation ) { navigation.hasStarted = true; navigation.hasFinished = false; @@ -328,6 +331,7 @@ export const { state, actions } = store< Store >( 'core/router', { // Update the navigation status once the the new page rendering // has been completed. + navigation.isLoading = false; if ( loadingAnimation ) { navigation.hasStarted = false; navigation.hasFinished = true; From 6b8e47fbd17c0710a4e55d4104dce185fdfac53b Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Mon, 9 Dec 2024 07:09:23 -0800 Subject: [PATCH 05/74] TypeScript: Convert factory utils in data package to TS (#67667) * TypeScript: Convert factory utils in data package to TS * Fix docgen build error * Update docs * Revert and fix TS error * Extract and improve signature --- packages/core-data/src/private-selectors.ts | 2 +- packages/data/README.md | 17 +++------ ...{create-selector.js => create-selector.ts} | 4 --- packages/data/src/{factory.js => factory.ts} | 35 +++++++++++++------ 4 files changed, 30 insertions(+), 28 deletions(-) rename packages/data/src/{create-selector.js => create-selector.ts} (55%) rename packages/data/src/{factory.js => factory.ts} (75%) diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 77790512653065..abdf2c837e8ed6 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -92,7 +92,7 @@ export function getEntityRecordPermissions( name: string, id: string ) { - return getEntityRecordsPermissions( state, kind, name, id )[ 0 ]; + return getEntityRecordsPermissions( state, kind, name, [ id ] )[ 0 ]; } /** diff --git a/packages/data/README.md b/packages/data/README.md index b6e0e03b1d8b72..67c01af24bde32 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -418,11 +418,11 @@ When registering a control created with `createRegistryControl` with a store, th _Parameters_ -- _registryControl_ `Function`: Function receiving a registry object and returning a control. +- _registryControl_ `T & { isRegistryControl?: boolean; }`: Function receiving a registry object and returning a control. _Returns_ -- `Function`: Registry control that can be registered with a store. +- Registry control that can be registered with a store. ### createRegistrySelector @@ -471,11 +471,11 @@ with a store. _Parameters_ -- _registrySelector_ `Function`: Function receiving a registry `select` function and returning a state selector. +- _registrySelector_ `( select: ) => Selector`: Function receiving a registry `select` function and returning a state selector. _Returns_ -- `Function`: Registry selector that can be registered with a store. +- `RegistrySelector< Selector >`: Registry selector that can be registered with a store. ### createSelector @@ -485,15 +485,6 @@ _Related_ - The documentation for the `rememo` package from which the `createSelector` function is reexported. -_Parameters_ - -- _selector_ `Function`: Selector function that calculates a value from state and parameters. -- _getDependants_ `Function`: Function that returns an array of "dependant" objects. - -_Returns_ - -- `Function`: A memoized version of `selector` that caches the calculated return values. - ### dispatch Given a store descriptor, returns an object of the store's action creators. Calling an action creator will cause it to be dispatched, updating the state value accordingly. diff --git a/packages/data/src/create-selector.js b/packages/data/src/create-selector.ts similarity index 55% rename from packages/data/src/create-selector.js rename to packages/data/src/create-selector.ts index 069932e8007e28..bfb7a1d283733f 100644 --- a/packages/data/src/create-selector.js +++ b/packages/data/src/create-selector.ts @@ -3,9 +3,5 @@ * and the selector parameters, and recomputes the values only when any of them changes. * * @see The documentation for the `rememo` package from which the `createSelector` function is reexported. - * - * @param {Function} selector Selector function that calculates a value from state and parameters. - * @param {Function} getDependants Function that returns an array of "dependant" objects. - * @return {Function} A memoized version of `selector` that caches the calculated return values. */ export { default as createSelector } from 'rememo'; diff --git a/packages/data/src/factory.js b/packages/data/src/factory.ts similarity index 75% rename from packages/data/src/factory.js rename to packages/data/src/factory.ts index be4ef8cf673c5e..8218fd2cdb07db 100644 --- a/packages/data/src/factory.js +++ b/packages/data/src/factory.ts @@ -1,3 +1,14 @@ +/** + * Internal dependencies + */ +import type { select as globalSelect } from './select'; + +type RegistrySelector< Selector extends ( ...args: any[] ) => any > = { + ( ...args: Parameters< Selector > ): ReturnType< Selector >; + isRegistrySelector?: boolean; + registry?: any; +}; + /** * Creates a selector function that takes additional curried argument with the * registry `select` function. While a regular selector has signature @@ -33,17 +44,21 @@ * registry as argument. The registry binding happens automatically when registering the selector * with a store. * - * @param {Function} registrySelector Function receiving a registry `select` - * function and returning a state selector. + * @param registrySelector Function receiving a registry `select` + * function and returning a state selector. * - * @return {Function} Registry selector that can be registered with a store. + * @return Registry selector that can be registered with a store. */ -export function createRegistrySelector( registrySelector ) { +export function createRegistrySelector< + Selector extends ( ...args: any[] ) => any, +>( + registrySelector: ( select: typeof globalSelect ) => Selector +): RegistrySelector< Selector > { const selectorsByRegistry = new WeakMap(); // Create a selector function that is bound to the registry referenced by `selector.registry` // and that has the same API as a regular selector. Binding it in such a way makes it // possible to call the selector directly from another selector. - const wrappedSelector = ( ...args ) => { + const wrappedSelector: RegistrySelector< Selector > = ( ...args ) => { let selector = selectorsByRegistry.get( wrappedSelector.registry ); // We want to make sure the cache persists even when new registry // instances are created. For example patterns create their own editors @@ -60,8 +75,6 @@ export function createRegistrySelector( registrySelector ) { * Flag indicating that the selector is a registry selector that needs the correct registry * reference to be assigned to `selector.registry` to make it work correctly. * be mapped as a registry selector. - * - * @type {boolean} */ wrappedSelector.isRegistrySelector = true; @@ -84,11 +97,13 @@ export function createRegistrySelector( registrySelector ) { * When registering a control created with `createRegistryControl` with a store, the store * knows which calling convention to use when executing the control. * - * @param {Function} registryControl Function receiving a registry object and returning a control. + * @param registryControl Function receiving a registry object and returning a control. * - * @return {Function} Registry control that can be registered with a store. + * @return Registry control that can be registered with a store. */ -export function createRegistryControl( registryControl ) { +export function createRegistryControl< T extends ( ...args: any ) => any >( + registryControl: T & { isRegistryControl?: boolean } +) { registryControl.isRegistryControl = true; return registryControl; From 9ae9ec35ff68f3426d3a75e3c543d80c1f16c3e2 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 9 Dec 2024 16:39:08 +0100 Subject: [PATCH 06/74] DataViews: Fix filters lost when switching layouts (#67740) Co-authored-by: youknowriad Co-authored-by: ntsekouras --- .../src/components/post-list/index.js | 90 ++++++++++--------- test/e2e/specs/site-editor/page-list.spec.js | 56 ++++++++++++ 2 files changed, 104 insertions(+), 42 deletions(-) create mode 100644 test/e2e/specs/site-editor/page-list.spec.js diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js index 145a5e8243ac54..a67a505795b3c8 100644 --- a/packages/edit-site/src/components/post-list/index.js +++ b/packages/edit-site/src/components/post-list/index.js @@ -13,7 +13,7 @@ import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { drawerRight } from '@wordpress/icons'; -import { usePrevious } from '@wordpress/compose'; +import { useEvent, usePrevious } from '@wordpress/compose'; import { addQueryArgs } from '@wordpress/url'; /** @@ -112,53 +112,50 @@ function useView( postType ) { }; } ); - const setViewWithUrlUpdate = useCallback( - ( newView ) => { - if ( newView.type === LAYOUT_LIST && ! layout ) { - // Skip updating the layout URL param if - // it is not present and the newView.type is LAYOUT_LIST. - } else if ( newView.type !== layout ) { - history.navigate( - addQueryArgs( path, { - layout: newView.type, - } ) - ); - } + const setViewWithUrlUpdate = useEvent( ( newView ) => { + setView( newView ); - setView( newView ); + if ( isCustom === 'true' && editedEntityRecord?.id ) { + editEntityRecord( + 'postType', + 'wp_dataviews', + editedEntityRecord?.id, + { + content: JSON.stringify( newView ), + } + ); + } - if ( isCustom === 'true' && editedEntityRecord?.id ) { - editEntityRecord( - 'postType', - 'wp_dataviews', - editedEntityRecord?.id, - { - content: JSON.stringify( newView ), - } - ); - } - }, - [ - history, - isCustom, - editEntityRecord, - editedEntityRecord?.id, - layout, - path, - ] - ); + const currentUrlLayout = layout ?? LAYOUT_LIST; + if ( newView.type !== currentUrlLayout ) { + history.navigate( + addQueryArgs( path, { + layout: newView.type, + } ) + ); + } + } ); // When layout URL param changes, update the view type // without affecting any other config. + const onUrlLayoutChange = useEvent( () => { + setView( ( prevView ) => { + const layoutToApply = layout ?? LAYOUT_LIST; + if ( layoutToApply === prevView.type ) { + return prevView; + } + return { + ...prevView, + type: layout ?? LAYOUT_LIST, + }; + } ); + } ); useEffect( () => { - setView( ( prevView ) => ( { - ...prevView, - type: layout ?? LAYOUT_LIST, - } ) ); - }, [ layout ] ); + onUrlLayoutChange(); + }, [ onUrlLayoutChange, layout ] ); // When activeView or isCustom URL parameters change, reset the view. - useEffect( () => { + const onUrlActiveViewChange = useEvent( () => { let newView; if ( isCustom === 'true' ) { newView = getCustomView( editedEntityRecord ); @@ -173,9 +170,18 @@ function useView( postType ) { type, } ); } - }, [ activeView, isCustom, layout, defaultViews, editedEntityRecord ] ); + } ); + useEffect( () => { + onUrlActiveViewChange(); + }, [ + onUrlActiveViewChange, + activeView, + isCustom, + defaultViews, + editedEntityRecord, + ] ); - return [ view, setViewWithUrlUpdate, setViewWithUrlUpdate ]; + return [ view, setViewWithUrlUpdate ]; } const DEFAULT_STATUSES = 'draft,future,pending,private,publish'; // All but 'trash'. diff --git a/test/e2e/specs/site-editor/page-list.spec.js b/test/e2e/specs/site-editor/page-list.spec.js new file mode 100644 index 00000000000000..fa9cb86cd1d62e --- /dev/null +++ b/test/e2e/specs/site-editor/page-list.spec.js @@ -0,0 +1,56 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Page List', () => { + test.beforeAll( async ( { requestUtils } ) => { + // Activate a theme with permissions to access the site editor. + await requestUtils.activateTheme( 'emptytheme' ); + await requestUtils.createPage( { + title: 'Privacy Policy', + status: 'publish', + } ); + await requestUtils.createPage( { + title: 'Sample Page', + status: 'publish', + } ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + // Go back to the default theme. + await Promise.all( [ + requestUtils.activateTheme( 'twentytwentyone' ), + requestUtils.deleteAllPages(), + ] ); + } ); + + test.beforeEach( async ( { admin, page } ) => { + // Go to the pages page, as it has the list layout enabled by default. + await admin.visitSiteEditor(); + await page.getByRole( 'button', { name: 'Pages' } ).click(); + } ); + + test( 'Persists filter/search when switching layout', async ( { + page, + } ) => { + // Search pages + await page + .getByRole( 'searchbox', { name: 'Search' } ) + .fill( 'Privacy' ); + + // Switch layout + await page.getByRole( 'button', { name: 'Layout' } ).click(); + await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); + + // Confirm the table is visible + await expect( page.getByRole( 'table' ) ).toContainText( + 'Privacy Policy' + ); + + // The search should still contain the search term + await expect( + page.getByRole( 'searchbox', { name: 'Search' } ) + ).toHaveValue( 'Privacy' ); + } ); +} ); From d03eeae0d48b35fb3e5e05a90bbc7b956a6b72f5 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <150562+mcsf@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:15:04 +0000 Subject: [PATCH 07/74] wp-env: Add phpMyAdmin support (#67588) * wp-env: Add phpMyAdmin support Defines two new docker-compose services: `phpmyadmin` and `tests-phpmyadmin`. These are off by default. They can be individually turned on by: - Specifying a port in any enviroment's config via key `phpmyadminPort` (see bottom of commit message for example). - By setting environment variable `WP_ENV_PHPMYADMIN_PORT` and/or `WP_ENV_TESTS_PHPMYADMIN_PORT`. * Opt into the phpMyAdmin service in the Gutenberg environment * wp-env: start: refactor computation of command output - Refactor repeating logic into getPublicDockerPort - Remove trailing newlines from dockerCompose output - Refactor evaluation of spinner.prefixText so as to clearly reveal newlines. { env: { development: { ... phpmyadminPort: 9000 }, tests: { ... } } } --------- Co-authored-by: Riad Benguella --- .wp-env.json | 3 + packages/env/CHANGELOG.md | 4 + packages/env/README.md | 2 +- .../env/lib/build-docker-compose-config.js | 27 +++++++ packages/env/lib/commands/start.js | 76 +++++++++++++++---- .../get-config-from-environment-vars.js | 7 ++ packages/env/lib/config/parse-config.js | 27 +++++-- .../__snapshots__/config-integration.js.snap | 8 ++ packages/env/lib/config/test/parse-config.js | 54 +++++++++++++ packages/env/lib/validate-run-container.js | 1 + schemas/json/wp-env.json | 7 +- 11 files changed, 190 insertions(+), 26 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index 05ea05b2809f9c..d368f3ea1c72a6 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -4,6 +4,9 @@ "plugins": [ "." ], "themes": [ "./test/emptytheme" ], "env": { + "development": { + "phpmyadminPort": 9000 + }, "tests": { "mappings": { "wp-content/plugins/gutenberg": ".", diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 651d6b285e1bd6..6ee37efdf850c9 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- Add phpMyAdmin as an optional service. Enabled via the new `phpmyadminPort` environment config, as well as env vars `WP_ENV_PHPMYADMIN_PORT` and `WP_ENV_TESTS_PHPMYADMIN_PORT`. + ## 10.13.0 (2024-11-27) ## 10.12.0 (2024-11-16) diff --git a/packages/env/README.md b/packages/env/README.md index 35b00fe83e8f4d..af037f5ee80ab6 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -349,7 +349,7 @@ containers. Positionals: container The Docker service to run the command on. [string] [required] [choices: "mysql", "tests-mysql", "wordpress", - "tests-wordpress", "cli", "tests-cli", "composer", "phpunit"] + "tests-wordpress", "cli", "tests-cli", "composer", "phpmyadmin"] command The command to run. [required] Options: diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index a394537e360872..a1a4f68256b688 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -174,6 +174,13 @@ module.exports = function buildDockerComposeConfig( config ) { config.env.tests.mysqlPort ?? '' }}:3306`; + const developmentPhpmyadminPorts = `\${WP_ENV_PHPMYADMIN_PORT:-${ + config.env.development.phpmyadminPort ?? '' + }}:80`; + const testsPhpmyadminPorts = `\${WP_ENV_TESTS_PHPMYADMIN_PORT:-${ + config.env.tests.phpmyadminPort ?? '' + }}:80`; + return { services: { mysql: { @@ -266,6 +273,26 @@ module.exports = function buildDockerComposeConfig( config ) { }, extra_hosts: [ 'host.docker.internal:host-gateway' ], }, + phpmyadmin: { + image: 'phpmyadmin', + ports: [ developmentPhpmyadminPorts ], + environment: { + PMA_PORT: 3306, + PMA_HOST: 'mysql', + PMA_USER: 'root', + PMA_PASSWORD: 'password', + }, + }, + 'tests-phpmyadmin': { + image: 'phpmyadmin', + ports: [ testsPhpmyadminPorts ], + environment: { + PMA_PORT: 3306, + PMA_HOST: 'tests-mysql', + PMA_USER: 'root', + PMA_PASSWORD: 'password', + }, + }, }, volumes: { ...( ! config.env.development.coreSource && { wordpress: {} } ), diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index 4203ac74632287..24d57d71fd3c62 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -180,6 +180,24 @@ module.exports = async function start( { } ); + if ( config.env.development.phpmyadminPort ) { + await dockerCompose.upOne( 'phpmyadmin', { + ...dockerComposeConfig, + commandOptions: shouldConfigureWp + ? [ '--build', '--force-recreate' ] + : [], + } ); + } + + if ( config.env.tests.phpmyadminPort ) { + await dockerCompose.upOne( 'tests-phpmyadmin', { + ...dockerComposeConfig, + commandOptions: shouldConfigureWp + ? [ '--build', '--force-recreate' ] + : [], + } ); + } + // Make sure we've consumed the custom CLI dockerfile. if ( shouldConfigureWp ) { await dockerCompose.buildOne( [ 'cli' ], { ...dockerComposeConfig } ); @@ -225,35 +243,61 @@ module.exports = async function start( { const siteUrl = config.env.development.config.WP_SITEURL; const testsSiteUrl = config.env.tests.config.WP_SITEURL; - const { out: mySQLAddress } = await dockerCompose.port( + const mySQLPort = await getPublicDockerPort( 'mysql', 3306, dockerComposeConfig ); - const mySQLPort = mySQLAddress.split( ':' ).pop(); - const { out: testsMySQLAddress } = await dockerCompose.port( + const testsMySQLPort = await getPublicDockerPort( 'tests-mysql', 3306, dockerComposeConfig ); - const testsMySQLPort = testsMySQLAddress.split( ':' ).pop(); - - spinner.prefixText = 'WordPress development site started' - .concat( siteUrl ? ` at ${ siteUrl }` : '.' ) - .concat( '\n' ) - .concat( 'WordPress test site started' ) - .concat( testsSiteUrl ? ` at ${ testsSiteUrl }` : '.' ) - .concat( '\n' ) - .concat( `MySQL is listening on port ${ mySQLPort }` ) - .concat( - `MySQL for automated testing is listening on port ${ testsMySQLPort }` - ) - .concat( '\n' ); + const phpmyadminPort = config.env.development.phpmyadminPort + ? await getPublicDockerPort( 'phpmyadmin', 80, dockerComposeConfig ) + : null; + + const testsPhpmyadminPort = config.env.tests.phpmyadminPort + ? await getPublicDockerPort( + 'tests-phpmyadmin', + 80, + dockerComposeConfig + ) + : null; + + spinner.prefixText = [ + 'WordPress development site started' + + ( siteUrl ? ` at ${ siteUrl }` : '.' ), + 'WordPress test site started' + + ( testsSiteUrl ? ` at ${ testsSiteUrl }` : '.' ), + `MySQL is listening on port ${ mySQLPort }`, + `MySQL for automated testing is listening on port ${ testsMySQLPort }`, + phpmyadminPort && + `phpMyAdmin started at http://localhost:${ phpmyadminPort }`, + testsPhpmyadminPort && + `phpMyAdmin for automated testing started at http://localhost:${ testsPhpmyadminPort }`, + ] + .filter( Boolean ) + .join( '\n' ); + spinner.prefixText += '\n\n'; spinner.text = 'Done!'; }; +async function getPublicDockerPort( + service, + containerPort, + dockerComposeConfig +) { + const { out: address } = await dockerCompose.port( + service, + containerPort, + dockerComposeConfig + ); + return address.split( ':' ).pop().trim(); +} + /** * Checks for legacy installs and provides * the user the option to delete them. diff --git a/packages/env/lib/config/get-config-from-environment-vars.js b/packages/env/lib/config/get-config-from-environment-vars.js index beaf721ea1c0c1..618b5fff257920 100644 --- a/packages/env/lib/config/get-config-from-environment-vars.js +++ b/packages/env/lib/config/get-config-from-environment-vars.js @@ -20,6 +20,7 @@ const { checkPort, checkVersion, checkString } = require( './validate-config' ); * @property {?number} mysqlPort An override for the development environment's MySQL port. * @property {?number} testsPort An override for the testing environment's port. * @property {?number} testsMysqlPort An override for the testing environment's MySQL port. + * @property {?number} phpmyadminPort An override for the development environment's phpMyAdmin port. * @property {?WPSource} coreSource An override for all environment's coreSource. * @property {?string} phpVersion An override for all environment's PHP version. * @property {?Object.} lifecycleScripts An override for various lifecycle scripts. @@ -40,6 +41,12 @@ module.exports = function getConfigFromEnvironmentVars( cacheDirectoryPath ) { testsMysqlPort: getPortFromEnvironmentVariable( 'WP_ENV_TESTS_MYSQL_PORT' ), + phpmyadminPort: getPortFromEnvironmentVariable( + 'WP_ENV_PHPMYADMIN_PORT' + ), + testsPhpmyadminPort: getPortFromEnvironmentVariable( + 'WP_ENV_TESTS_PHPMYADMIN_PORT' + ), lifecycleScripts: getLifecycleScriptOverrides(), }; diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index 1fc7e949251490..bddd7bc72aaee0 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -46,14 +46,15 @@ const mergeConfigs = require( './merge-configs' ); * The environment-specific configuration options. (development/tests/etc) * * @typedef WPEnvironmentConfig - * @property {WPSource} coreSource The WordPress installation to load in the environment. - * @property {WPSource[]} pluginSources Plugins to load in the environment. - * @property {WPSource[]} themeSources Themes to load in the environment. - * @property {number} port The port to use. - * @property {number} mysqlPort The port to use for MySQL. Random if empty. - * @property {Object} config Mapping of wp-config.php constants to their desired values. - * @property {Object.} mappings Mapping of WordPress directories to local directories which should be mounted. - * @property {string|null} phpVersion Version of PHP to use in the environments, of the format 0.0. + * @property {WPSource} coreSource The WordPress installation to load in the environment. + * @property {WPSource[]} pluginSources Plugins to load in the environment. + * @property {WPSource[]} themeSources Themes to load in the environment. + * @property {number} port The port to use. + * @property {number} mysqlPort The port to use for MySQL. Random if empty. + * @property {number} phpmyadminPort The port to use for phpMyAdmin. If empty, disabled phpMyAdmin. + * @property {Object} config Mapping of wp-config.php constants to their desired values. + * @property {Object.} mappings Mapping of WordPress directories to local directories which should be mounted. + * @property {string|null} phpVersion Version of PHP to use in the environments, of the format 0.0. */ /** @@ -87,6 +88,7 @@ const DEFAULT_ENVIRONMENT_CONFIG = { port: 8888, testsPort: 8889, mysqlPort: null, + phpmyadminPort: null, mappings: {}, config: { FS_METHOD: 'direct', @@ -282,6 +284,11 @@ function getEnvironmentVarOverrides( cacheDirectoryPath ) { overrideConfig.env.development.mysqlPort = overrides.mysqlPort; } + if ( overrides.phpmyadminPort ) { + overrideConfig.env.development.phpmyadminPort = + overrides.phpmyadminPort; + } + if ( overrides.testsPort ) { overrideConfig.testsPort = overrides.testsPort; overrideConfig.env.tests.port = overrides.testsPort; @@ -455,6 +462,10 @@ async function parseEnvironmentConfig( parsedConfig.mysqlPort = config.mysqlPort; } + if ( config.phpmyadminPort !== undefined ) { + parsedConfig.phpmyadminPort = config.phpmyadminPort; + } + if ( config.phpVersion !== undefined ) { // Support null as a valid input. if ( config.phpVersion !== null ) { diff --git a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap index 6c3618f4724cb0..6b671a6bc858eb 100644 --- a/packages/env/lib/config/test/__snapshots__/config-integration.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config-integration.js.snap @@ -31,6 +31,7 @@ exports[`Config Integration should load local and override configuration files 1 "mappings": {}, "mysqlPort": 23306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 999, "themeSources": [], @@ -60,6 +61,7 @@ exports[`Config Integration should load local and override configuration files 1 "mappings": {}, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 456, "themeSources": [], @@ -106,6 +108,7 @@ exports[`Config Integration should load local configuration file 1`] = ` "mappings": {}, "mysqlPort": 13306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 123, "themeSources": [], @@ -135,6 +138,7 @@ exports[`Config Integration should load local configuration file 1`] = ` "mappings": {}, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8889, "themeSources": [], @@ -181,6 +185,7 @@ exports[`Config Integration should use default configuration 1`] = ` "mappings": {}, "mysqlPort": null, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8888, "themeSources": [], @@ -210,6 +215,7 @@ exports[`Config Integration should use default configuration 1`] = ` "mappings": {}, "mysqlPort": null, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 8889, "themeSources": [], @@ -256,6 +262,7 @@ exports[`Config Integration should use environment variables over local and over "mappings": {}, "mysqlPort": 23306, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 12345, "testsPort": 61234, @@ -286,6 +293,7 @@ exports[`Config Integration should use environment variables over local and over "mappings": {}, "mysqlPort": 23307, "phpVersion": null, + "phpmyadminPort": null, "pluginSources": [], "port": 61234, "testsPort": 61234, diff --git a/packages/env/lib/config/test/parse-config.js b/packages/env/lib/config/test/parse-config.js index 38e4db9860cb3c..cc6e2c7a96bbc0 100644 --- a/packages/env/lib/config/test/parse-config.js +++ b/packages/env/lib/config/test/parse-config.js @@ -22,6 +22,7 @@ const DEFAULT_CONFIG = { port: 8888, testsPort: 8889, mysqlPort: null, + phpmyadminPort: null, phpVersion: null, coreSource: { type: 'git', @@ -400,4 +401,57 @@ describe( 'parseConfig', () => { ) ); } ); + + it( 'should parse phpmyadmin configuration for a given environment', async () => { + readRawConfigFile.mockImplementation( async ( configFile ) => { + if ( configFile === '/test/gutenberg/.wp-env.json' ) { + return { + core: 'WordPress/WordPress#Test', + phpVersion: '1.0', + lifecycleScripts: { + afterStart: 'test', + }, + env: { + development: { + phpmyadminPort: 9001, + }, + }, + }; + } + } ); + + const parsed = await parseConfig( '/test/gutenberg', '/cache' ); + + const expected = { + development: { + ...DEFAULT_CONFIG.env.development, + phpmyadminPort: 9001, + }, + tests: DEFAULT_CONFIG.env.tests, + }; + expect( parsed.env ).toEqual( expected ); + } ); + + it( 'should ignore root-level configuration for phpmyadmin', async () => { + readRawConfigFile.mockImplementation( async ( configFile ) => { + if ( configFile === '/test/gutenberg/.wp-env.json' ) { + return { + core: 'WordPress/WordPress#Test', + phpVersion: '1.0', + lifecycleScripts: { + afterStart: 'test', + }, + phpmyadminPort: 9001, + }; + } + } ); + + const parsed = await parseConfig( '/test/gutenberg', '/cache' ); + + const expected = { + development: DEFAULT_CONFIG.env.development, + tests: DEFAULT_CONFIG.env.tests, + }; + expect( parsed.env ).toEqual( expected ); + } ); } ); diff --git a/packages/env/lib/validate-run-container.js b/packages/env/lib/validate-run-container.js index 77e68d2a3a4dc7..fbb6670f6c1500 100644 --- a/packages/env/lib/validate-run-container.js +++ b/packages/env/lib/validate-run-container.js @@ -10,6 +10,7 @@ const RUN_CONTAINERS = [ 'tests-wordpress', 'cli', 'tests-cli', + 'phpmyadmin', ]; /** diff --git a/schemas/json/wp-env.json b/schemas/json/wp-env.json index 491d1f8cf73017..8aa604ed41ed1f 100644 --- a/schemas/json/wp-env.json +++ b/schemas/json/wp-env.json @@ -62,6 +62,10 @@ "description": "Mapping of WordPress directories to local directories to be mounted in the WordPress instance.", "type": "object", "default": {} + }, + "phpmyadminPort": { + "description": "The port number to access phpMyAdmin.", + "type": "integer" } } }, @@ -73,7 +77,8 @@ "themes", "port", "config", - "mappings" + "mappings", + "phpmyadminPort" ] } }, From ad0c1d444ae62e67d6a75ecaaeea8f62d23badba Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 9 Dec 2024 21:15:56 +0400 Subject: [PATCH 08/74] Replace remaining custom deep cloning with 'structuredClone' (#67707) * Replace remaining custom deep cloning with 'structuredClone' * Polyfill structuredClone for jsdom Co-authored-by: Mamaduka Co-authored-by: tyxla Co-authored-by: jsnajdr --- .../src/components/global-styles/use-global-styles-output.js | 2 +- packages/block-editor/src/hooks/style.js | 2 +- test/unit/config/global-mocks.js | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 7bdc95d222142d..fabc65d143d1aa 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -624,7 +624,7 @@ function pickStyleKeys( treeToPickFrom ) { // clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it const clonedEntries = pickedEntries.map( ( [ key, style ] ) => [ key, - JSON.parse( JSON.stringify( style ) ), + structuredClone( style ), ] ); return Object.fromEntries( clonedEntries ); } diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index db2acd01665b60..5be2b1b3fd40a8 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -245,7 +245,7 @@ export function omitStyle( style, paths, preserveReference = false ) { let newStyle = style; if ( ! preserveReference ) { - newStyle = JSON.parse( JSON.stringify( style ) ); + newStyle = structuredClone( style ); } if ( ! Array.isArray( paths ) ) { diff --git a/test/unit/config/global-mocks.js b/test/unit/config/global-mocks.js index ce64f03b514be8..8db2c180fadf3a 100644 --- a/test/unit/config/global-mocks.js +++ b/test/unit/config/global-mocks.js @@ -3,6 +3,7 @@ */ import { TextDecoder, TextEncoder } from 'node:util'; import { Blob as BlobPolyfill, File as FilePolyfill } from 'node:buffer'; +import 'core-js/stable/structured-clone'; jest.mock( '@wordpress/compose', () => { return { @@ -49,3 +50,6 @@ if ( ! global.TextEncoder ) { // Override jsdom built-ins with native node implementation. global.Blob = BlobPolyfill; global.File = FilePolyfill; + +// Polyfill structuredClone for jsdom. +global.structuredClone = structuredClone; From 95cbcdf5daacccccd181dfa7850e706c5a1e009c Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Mon, 9 Dec 2024 11:34:42 -0600 Subject: [PATCH 09/74] Inserter: Should receive focus on open (#67754) * Set default tab to 'blocks' if none passed * Add tests for focusing blocks tab and closing inserter on keypresses --- .../src/components/inserter/menu.js | 2 + .../site-editor/site-editor-inserter.spec.js | 53 +++++++++++++------ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index ef260beb85906b..019a37bffdde26 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -82,6 +82,8 @@ function InserterMenu( if ( isZoomOutMode ) { return 'patterns'; } + + return 'blocks'; } const [ selectedTab, setSelectedTab ] = useState( getInitialTab() ); diff --git a/test/e2e/specs/site-editor/site-editor-inserter.spec.js b/test/e2e/specs/site-editor/site-editor-inserter.spec.js index a730367d841bf8..23646576131082 100644 --- a/test/e2e/specs/site-editor/site-editor-inserter.spec.js +++ b/test/e2e/specs/site-editor/site-editor-inserter.spec.js @@ -28,32 +28,51 @@ test.describe( 'Site Editor Inserter', () => { }, } ); - // eslint-disable-next-line playwright/expect-expect test( 'inserter toggle button should toggle global inserter', async ( { InserterUtils, + page, + editor, } ) => { await InserterUtils.openBlockLibrary(); await InserterUtils.closeBlockLibrary(); - } ); - // A test for https://github.com/WordPress/gutenberg/issues/43090. - test( 'should close the inserter when clicking on the toggle button', async ( { - editor, - InserterUtils, - } ) => { - const beforeBlocks = await editor.getBlocks(); + await test.step( 'should open the inserter via enter keypress on toggle button', async () => { + await InserterUtils.inserterButton.focus(); + await page.keyboard.press( 'Enter' ); + await expect( InserterUtils.blockLibrary ).toBeVisible(); + } ); - await InserterUtils.openBlockLibrary(); - await InserterUtils.expectActiveTab( 'Blocks' ); - await InserterUtils.blockLibrary - .getByRole( 'option', { name: 'Buttons' } ) - .click(); + await test.step( 'should set focus to the blocks tab when opening the inserter', async () => { + await expect( + InserterUtils.getBlockLibraryTab( 'Blocks' ) + ).toBeFocused(); + } ); + + await test.step( 'should close the inserter via escape keypress', async () => { + await page.keyboard.press( 'Escape' ); + await expect( InserterUtils.blockLibrary ).toBeHidden(); + } ); - await expect - .poll( editor.getBlocks ) - .toMatchObject( [ ...beforeBlocks, { name: 'core/buttons' } ] ); + await test.step( 'should focus inserter toggle button after closing the inserter via escape keypress', async () => { + await expect( InserterUtils.inserterButton ).toBeFocused(); + } ); - await InserterUtils.closeBlockLibrary(); + // A test for https://github.com/WordPress/gutenberg/issues/43090. + await test.step( 'should close the inserter when clicking on the toggle button', async () => { + const beforeBlocks = await editor.getBlocks(); + + await InserterUtils.openBlockLibrary(); + await InserterUtils.expectActiveTab( 'Blocks' ); + await InserterUtils.blockLibrary + .getByRole( 'option', { name: 'Buttons' } ) + .click(); + + await expect + .poll( editor.getBlocks ) + .toMatchObject( [ ...beforeBlocks, { name: 'core/buttons' } ] ); + + await InserterUtils.closeBlockLibrary(); + } ); } ); test.describe( 'Inserter Zoom Level UX', () => { From f430cd73ff8eaf58aa9782d50891e8eb50a9ee54 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 9 Dec 2024 18:24:42 +0000 Subject: [PATCH 10/74] Fix: Dataviews remove primary field concept from some classes. (#67689) Co-authored-by: jorgefilipecosta Co-authored-by: youknowriad --- packages/dataviews/src/dataviews-layouts/grid/index.tsx | 8 +++----- packages/dataviews/src/dataviews-layouts/grid/style.scss | 2 +- packages/dataviews/src/dataviews-layouts/list/style.scss | 8 ++++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/dataviews/src/dataviews-layouts/grid/index.tsx b/packages/dataviews/src/dataviews-layouts/grid/index.tsx index 4a016095702026..e10ae7b591af5f 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/grid/index.tsx @@ -82,11 +82,11 @@ function GridItem< Item >( { className: 'dataviews-view-grid__media', } ); - const clickablePrimaryItemProps = getClickableItemProps( { + const clickableTitleItemProps = getClickableItemProps( { item, isItemClickable, onClickItem, - className: 'dataviews-view-grid__primary-field dataviews-title-field', + className: 'dataviews-view-grid__title-field dataviews-title-field', } ); return ( @@ -128,9 +128,7 @@ function GridItem< Item >( { justify="space-between" className="dataviews-view-grid__title-actions" > -
- { renderedTitleField } -
+
{ renderedTitleField }
diff --git a/packages/dataviews/src/dataviews-layouts/grid/style.scss b/packages/dataviews/src/dataviews-layouts/grid/style.scss index 70c94653371d16..e9fcb472dc3186 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/style.scss +++ b/packages/dataviews/src/dataviews-layouts/grid/style.scss @@ -15,7 +15,7 @@ padding: $grid-unit-10 0 $grid-unit-05; } - .dataviews-view-grid__primary-field { + .dataviews-view-grid__title-field { min-height: $grid-unit-30; // Preserve layout when there is no ellipsis button display: flex; align-items: center; diff --git a/packages/dataviews/src/dataviews-layouts/list/style.scss b/packages/dataviews/src/dataviews-layouts/list/style.scss index 8fe048cab77b51..65c28a90608a6b 100644 --- a/packages/dataviews/src/dataviews-layouts/list/style.scss +++ b/packages/dataviews/src/dataviews-layouts/list/style.scss @@ -50,7 +50,7 @@ ul.dataviews-view-list { } &:not(.is-selected) { - .dataviews-view-list__primary-field { + .dataviews-view-list__title-field { color: $gray-900; } &:hover, @@ -59,7 +59,7 @@ ul.dataviews-view-list { color: var(--wp-admin-theme-color); background-color: #f8f8f8; - .dataviews-view-list__primary-field, + .dataviews-view-list__title-field, .dataviews-view-list__fields { color: var(--wp-admin-theme-color); } @@ -74,7 +74,7 @@ ul.dataviews-view-list { background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); color: $gray-900; - .dataviews-view-list__primary-field, + .dataviews-view-list__title-field, .dataviews-view-list__fields { color: var(--wp-admin-theme-color); } @@ -106,7 +106,7 @@ ul.dataviews-view-list { } } } - .dataviews-view-list__primary-field { + .dataviews-view-list__title-field { flex: 1; min-height: $grid-unit-30; line-height: $grid-unit-30; From 7159a7857fb9106c4ecaa299220de91f0fff23c8 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 10 Dec 2024 16:01:26 +1100 Subject: [PATCH 11/74] Add stylebook screen for classic themes (#66851) Unlinked contributors: acketon. Co-authored-by: tellthemachines Co-authored-by: youknowriad Co-authored-by: ramonjd Co-authored-by: t-hamano Co-authored-by: jasmussen Co-authored-by: annezazu Co-authored-by: mtias Co-authored-by: carlomanf Co-authored-by: daveloodts Co-authored-by: cbirdsong Co-authored-by: fabiankaegy Co-authored-by: mrwweb Co-authored-by: ltrihan Co-authored-by: masteradhoc --- backport-changelog/6.8/7865.md | 3 + lib/block-editor-settings.php | 13 ++ lib/compat/wordpress-6.8/site-editor.php | 26 ++- .../src/components/maybe-editor/index.js | 58 +++++++ .../src/components/resizable-frame/index.js | 11 +- .../sidebar-navigation-screen-main/index.js | 105 +++++++----- .../index.js | 7 - .../src/components/site-editor-routes/home.js | 4 +- .../components/site-editor-routes/index.js | 2 + .../site-editor-routes/stylebook.js | 28 ++++ .../site-editor-routes/template-item.js | 6 +- .../site-editor-routes/template-part-item.js | 6 +- .../src/components/style-book/examples.tsx | 4 +- .../src/components/style-book/index.js | 155 ++++++++++++++---- .../src/components/style-book/style.scss | 4 + test/e2e/specs/site-editor/navigation.spec.js | 16 +- test/e2e/specs/site-editor/style-book.spec.js | 24 +++ 17 files changed, 375 insertions(+), 97 deletions(-) create mode 100644 backport-changelog/6.8/7865.md create mode 100644 packages/edit-site/src/components/maybe-editor/index.js create mode 100644 packages/edit-site/src/components/site-editor-routes/stylebook.js diff --git a/backport-changelog/6.8/7865.md b/backport-changelog/6.8/7865.md new file mode 100644 index 00000000000000..f7b23c944dc327 --- /dev/null +++ b/backport-changelog/6.8/7865.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7865 + +* https://github.com/WordPress/gutenberg/pull/66851 \ No newline at end of file diff --git a/lib/block-editor-settings.php b/lib/block-editor-settings.php index defd7cd391b16b..6448eb2e524853 100644 --- a/lib/block-editor-settings.php +++ b/lib/block-editor-settings.php @@ -53,6 +53,13 @@ function gutenberg_get_block_editor_settings( $settings ) { $global_styles[] = $block_classes; } + // Get any additional css from the customizer and add it before global styles custom CSS. + $global_styles[] = array( + 'css' => wp_get_custom_css(), + '__unstableType' => 'user', + 'isGlobalStyles' => false, + ); + /* * Add the custom CSS as a separate stylesheet so any invalid CSS * entered by users does not break other global styles. @@ -74,6 +81,12 @@ function gutenberg_get_block_editor_settings( $settings ) { $block_classes['css'] = $actual_css; $global_styles[] = $block_classes; } + // Get any additional css from the customizer. + $global_styles[] = array( + 'css' => wp_get_custom_css(), + '__unstableType' => 'user', + 'isGlobalStyles' => false, + ); } $settings['styles'] = array_merge( $global_styles, get_block_editor_theme_styles() ); diff --git a/lib/compat/wordpress-6.8/site-editor.php b/lib/compat/wordpress-6.8/site-editor.php index cde108830b1d2c..53d04c2e543f48 100644 --- a/lib/compat/wordpress-6.8/site-editor.php +++ b/lib/compat/wordpress-6.8/site-editor.php @@ -116,9 +116,33 @@ function gutenberg_redirect_site_editor_deprecated_urls() { * @return callable The default handler or a custom handler. */ function gutenberg_styles_wp_die_handler( $default_handler ) { - if ( ! wp_is_block_theme() && str_contains( $_SERVER['REQUEST_URI'], 'site-editor.php' ) && isset( $_GET['p'] ) ) { + if ( ! wp_is_block_theme() && str_contains( $_SERVER['REQUEST_URI'], 'site-editor.php' ) && current_user_can( 'edit_theme_options' ) ) { return '__return_false'; } return $default_handler; } add_filter( 'wp_die_handler', 'gutenberg_styles_wp_die_handler' ); + +/** + * Add a Styles submenu under the Appearance menu + * for Classic themes. + * + * @global array $submenu + */ +function gutenberg_add_styles_submenu_item() { + if ( ! wp_is_block_theme() && ( current_theme_supports( 'editor-styles' ) || wp_theme_has_theme_json() ) ) { + global $submenu; + + $styles_menu_item = array( + __( 'Design', 'gutenberg' ), + 'edit_theme_options', + 'site-editor.php', + ); + // If $submenu exists, insert the Styles submenu item at position 2. + if ( $submenu && isset( $submenu['themes.php'] ) ) { + // This might not work as expected if the submenu has already been modified. + array_splice( $submenu['themes.php'], 1, 1, array( $styles_menu_item ) ); + } + } +} +add_action( 'admin_init', 'gutenberg_add_styles_submenu_item' ); diff --git a/packages/edit-site/src/components/maybe-editor/index.js b/packages/edit-site/src/components/maybe-editor/index.js new file mode 100644 index 00000000000000..bee1c427c87b47 --- /dev/null +++ b/packages/edit-site/src/components/maybe-editor/index.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ + +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ + +import Editor from '../editor'; + +export function MaybeEditor( { showEditor = true } ) { + const { isBlockBasedTheme, siteUrl } = useSelect( ( select ) => { + const { getEntityRecord, getCurrentTheme } = select( coreStore ); + const siteData = getEntityRecord( 'root', '__unstableBase' ); + + return { + isBlockBasedTheme: getCurrentTheme()?.is_block_theme, + siteUrl: siteData?.home, + }; + }, [] ); + + // If theme is block based, return the Editor, otherwise return the site preview. + return isBlockBasedTheme || showEditor ? ( + + ) : ( +