From 9ec599804382de27b527866b54719929b7651511 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:06:55 +0900 Subject: [PATCH 01/52] Fix color of disabled buttons in dark toolbar (#67348) Co-authored-by: t-hamano Co-authored-by: jasmussen --- packages/block-editor/src/components/block-tools/style.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index 0ed9c138519d2..80fe4c420d1e1 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -157,6 +157,11 @@ &:focus::before { box-shadow: inset 0 0 0 1px $gray-900, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); } + + &:disabled, + &[aria-disabled="true"] { + color: $gray-700; + } } .block-editor-block-parent-selector .block-editor-block-parent-selector__button { From f189eab30fc68b71728e17b50a089b2aa42a2277 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 29 Nov 2024 23:02:17 +0900 Subject: [PATCH 02/52] BorderBoxControl: Reduce gap value when unlinked (#67049) * BorderBoxControl: Reduce input field width when unlinked * Update changelog * 108px * Reduce gap value * Fix changelog entry Co-authored-by: t-hamano Co-authored-by: tyxla Co-authored-by: jasmussen Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: jameskoster --- packages/components/CHANGELOG.md | 4 ++++ .../border-box-control-split-controls/component.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 8fc7aff329b03..ce151116df5bf 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -7,6 +7,10 @@ - `BoxControl`: Passive deprecate `onMouseOver`/`onMouseOut`. Pass to the `inputProps` prop instead ([#67332](https://github.com/WordPress/gutenberg/pull/67332)). - `BoxControl`: Deprecate 36px default size ([#66704](https://github.com/WordPress/gutenberg/pull/66704)). +### Enhancements + +- `BorderBoxControl`: Reduce gap value when unlinked ([#67049](https://github.com/WordPress/gutenberg/pull/67049)). + ### 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/border-box-control/border-box-control-split-controls/component.tsx b/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx index 0c887ab5f701c..94e1728076b18 100644 --- a/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx +++ b/packages/components/src/border-box-control/border-box-control-split-controls/component.tsx @@ -73,7 +73,7 @@ const BorderBoxControlSplitControls = ( const mergedRef = useMergeRefs( [ setPopoverAnchor, forwardedRef ] ); return ( - + Date: Fri, 29 Nov 2024 23:48:46 +0900 Subject: [PATCH 03/52] FontFamilyControl: Restore margin bottom (#67424) Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: tyxla Co-authored-by: matiasbenedetto --- .../block-editor/src/components/font-family/index.js | 9 +++++++++ .../block-editor/src/components/font-family/style.scss | 5 +++++ packages/block-editor/src/style.scss | 1 + 3 files changed, 15 insertions(+) create mode 100644 packages/block-editor/src/components/font-family/style.scss diff --git a/packages/block-editor/src/components/font-family/index.js b/packages/block-editor/src/components/font-family/index.js index 1e6c137daedb0..045d4d5c73ed3 100644 --- a/packages/block-editor/src/components/font-family/index.js +++ b/packages/block-editor/src/components/font-family/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; + /** * WordPress dependencies */ @@ -18,6 +23,7 @@ export default function FontFamilyControl( { value = '', onChange, fontFamilies, + className, ...props } ) { const [ blockLevelFontFamilies ] = useSettings( 'typography.fontFamilies' ); @@ -59,6 +65,9 @@ export default function FontFamilyControl( { value={ value } onChange={ ( { selectedItem } ) => onChange( selectedItem.key ) } options={ options } + className={ clsx( 'block-editor-font-family-control', className, { + 'is-next-has-no-margin-bottom': __nextHasNoMarginBottom, + } ) } { ...props } /> ); diff --git a/packages/block-editor/src/components/font-family/style.scss b/packages/block-editor/src/components/font-family/style.scss new file mode 100644 index 0000000000000..7ee181ebb7953 --- /dev/null +++ b/packages/block-editor/src/components/font-family/style.scss @@ -0,0 +1,5 @@ +.block-editor-font-family-control { + &:not(.is-next-has-no-margin-bottom) { + margin-bottom: $grid-unit-10; + } +} diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 213498c797aee..6b2ebf5cd841f 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -28,6 +28,7 @@ @import "./components/date-format-picker/style.scss"; @import "./components/duotone-control/style.scss"; @import "./components/font-appearance-control/style.scss"; +@import "./components/font-family/style.scss"; @import "./components/global-styles/style.scss"; @import "./components/grid/style.scss"; @import "./components/height-control/style.scss"; From 2c88f6a1b674c78bbbea5dc41aac998970ef9c2e Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Fri, 29 Nov 2024 15:16:15 +0000 Subject: [PATCH 04/52] Fix: Caption with Link in Wide-Width and Full-Width Images Appears on two lines (#67392) Co-authored-by: jorgefilipecosta Co-authored-by: youknowriad Co-authored-by: t-hamano --- packages/block-library/src/image/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 1bb19bf29da69..a7fcb8f175e4e 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -42,8 +42,8 @@ text-align: center; } - &.alignfull a, - &.alignwide a { + &.alignfull > a, + &.alignwide > a { width: 100%; } From edd6328b3ff9cefc5550878f90fae885f33c8b27 Mon Sep 17 00:00:00 2001 From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com> Date: Fri, 29 Nov 2024 20:52:27 +0530 Subject: [PATCH 05/52] UnitControl : Deprecate 36px default size (#66791) * Add the console warning for 36px size variation * Add the changelog for the deprecation * Update the unit test for the unitcontrol to use default 40px size * Use __shouldNotWarnDeprecated36pxSize to not throw redundant warning from parent component used * Add the missing prop for __next40pxDefaultSize on the index file and updated readme as well * Add changelog to unreleased section * Add __shouldNotWarnDeprecated36pxSize prop to supress console warning from child component * Update tools panel storybook and docs to use __next40pxDefaultSize for UnitControl * Updated the unit test to minimise the file changes * Revert changes on mobile FontSizePicker --------- Co-authored-by: hbhalodia Co-authored-by: mirka <0mirka00@git.wordpress.org> --- packages/components/CHANGELOG.md | 1 + .../src/border-control/border-control/component.tsx | 2 ++ .../components/src/box-control/all-input-control.tsx | 1 + .../src/box-control/axial-input-controls.tsx | 1 + .../components/src/box-control/input-controls.tsx | 1 + packages/components/src/font-size-picker/index.tsx | 1 + .../src/tools-panel/stories/index.story.tsx | 12 ++++++++++++ .../components/src/tools-panel/tools-panel/README.md | 2 ++ .../src/tools-panel/tools-panel/component.tsx | 2 ++ packages/components/src/unit-control/README.md | 6 +++--- packages/components/src/unit-control/index.tsx | 11 ++++++++++- .../src/unit-control/stories/index.story.tsx | 1 + packages/components/src/unit-control/test/index.tsx | 6 +++++- packages/components/src/unit-control/types.ts | 7 +++++++ 14 files changed, 49 insertions(+), 5 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index ce151116df5bf..7865993d4e995 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,7 @@ - `BoxControl`: Passive deprecate `onMouseOver`/`onMouseOut`. Pass to the `inputProps` prop instead ([#67332](https://github.com/WordPress/gutenberg/pull/67332)). - `BoxControl`: Deprecate 36px default size ([#66704](https://github.com/WordPress/gutenberg/pull/66704)). +- `UnitControl`: Deprecate 36px default size ([#66791](https://github.com/WordPress/gutenberg/pull/66791)). ### Enhancements diff --git a/packages/components/src/border-control/border-control/component.tsx b/packages/components/src/border-control/border-control/component.tsx index 2ba338c2bb30c..f71599b274778 100644 --- a/packages/components/src/border-control/border-control/component.tsx +++ b/packages/components/src/border-control/border-control/component.tsx @@ -75,6 +75,8 @@ const UnconnectedBorderControl = ( /> = ( { isShownByDefault > setWidth( next ) } @@ -86,6 +87,7 @@ export const Default: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > setHeight( next ) } @@ -98,6 +100,7 @@ export const Default: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > setMinHeight( next ) } @@ -167,6 +170,7 @@ export const WithNonToolsPanelItems: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > setWidth( next ) } @@ -179,6 +183,7 @@ export const WithNonToolsPanelItems: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > setHeight( next ) } @@ -237,6 +242,7 @@ export const WithOptionalItemsPlusIcon: StoryFn< typeof ToolsPanel > = ( { } > setMinWidth( next ) } @@ -249,6 +255,7 @@ export const WithOptionalItemsPlusIcon: StoryFn< typeof ToolsPanel > = ( { isShownByDefault={ false } > setWidth( next ) } @@ -261,6 +268,7 @@ export const WithOptionalItemsPlusIcon: StoryFn< typeof ToolsPanel > = ( { isShownByDefault={ false } > setHeight( next ) } @@ -341,6 +349,7 @@ export const WithSlotFillItems: StoryFn< typeof ToolsPanel > = ( { panelId={ panelId } > @@ -356,6 +365,7 @@ export const WithSlotFillItems: StoryFn< typeof ToolsPanel > = ( { panelId={ panelId } > @@ -441,6 +451,7 @@ export const WithConditionalDefaultControl: StoryFn< typeof ToolsPanel > = ( { isShownByDefault > @@ -541,6 +552,7 @@ export const WithConditionallyRenderedControl: StoryFn< isShownByDefault > diff --git a/packages/components/src/tools-panel/tools-panel/README.md b/packages/components/src/tools-panel/tools-panel/README.md index 1895f5ccc843e..b5e6860e2bd07 100644 --- a/packages/components/src/tools-panel/tools-panel/README.md +++ b/packages/components/src/tools-panel/tools-panel/README.md @@ -101,6 +101,7 @@ export function DimensionPanel() { isShownByDefault > setHeight() } * > * setWidth() } * > * { const [ value, setValue ] = useState( '10px' ); - return ; + return ; }; ``` @@ -128,7 +128,7 @@ const Example = () => { ]; return ( - + ); }; ``` @@ -143,7 +143,7 @@ For example, a `value` of `50%` will set the current unit to `%`. Example: ```jsx - + ``` - Required: No diff --git a/packages/components/src/unit-control/index.tsx b/packages/components/src/unit-control/index.tsx index 2dd08cc155225..9845c4eb04ef2 100644 --- a/packages/components/src/unit-control/index.tsx +++ b/packages/components/src/unit-control/index.tsx @@ -27,6 +27,7 @@ import { useControlledState } from '../utils/hooks'; import { escapeRegExp } from '../utils/strings'; import type { UnitControlProps, UnitControlOnChangeCallback } from './types'; import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props'; +import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size'; function UnforwardedUnitControl( unitControlProps: WordPressComponentProps< @@ -55,9 +56,17 @@ function UnforwardedUnitControl( units: unitsProp = CSS_UNITS, value: valueProp, onFocus: onFocusProp, + __shouldNotWarnDeprecated36pxSize, ...props } = useDeprecated36pxDefaultSizeProp( unitControlProps ); + maybeWarnDeprecated36pxSize( { + componentName: 'UnitControl', + __next40pxDefaultSize: props.__next40pxDefaultSize, + size, + __shouldNotWarnDeprecated36pxSize, + } ); + if ( 'unit' in unitControlProps ) { deprecated( 'UnitControl unit prop', { since: '5.6', @@ -246,7 +255,7 @@ function UnforwardedUnitControl( * const Example = () => { * const [ value, setValue ] = useState( '10px' ); * - * return ; + * return ; * }; * ``` */ diff --git a/packages/components/src/unit-control/stories/index.story.tsx b/packages/components/src/unit-control/stories/index.story.tsx index de8f476e26e5c..87628c4644116 100644 --- a/packages/components/src/unit-control/stories/index.story.tsx +++ b/packages/components/src/unit-control/stories/index.story.tsx @@ -59,6 +59,7 @@ export const Default: StoryFn< typeof UnitControl > = DefaultTemplate.bind( ); Default.args = { label: 'Label', + __next40pxDefaultSize: true, }; /** diff --git a/packages/components/src/unit-control/test/index.tsx b/packages/components/src/unit-control/test/index.tsx index d91498d46478b..ad98d57cae640 100644 --- a/packages/components/src/unit-control/test/index.tsx +++ b/packages/components/src/unit-control/test/index.tsx @@ -12,9 +12,13 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import UnitControl from '..'; +import _UnitControl from '..'; import { CSS_UNITS, parseQuantityAndUnitFromRawValue } from '../utils'; +const UnitControl = ( props: React.ComponentProps< typeof _UnitControl > ) => ( + <_UnitControl __next40pxDefaultSize { ...props } /> +); + const getInput = ( { isInputTypeText = false, }: { diff --git a/packages/components/src/unit-control/types.ts b/packages/components/src/unit-control/types.ts index 9164502668a2b..891945b422862 100644 --- a/packages/components/src/unit-control/types.ts +++ b/packages/components/src/unit-control/types.ts @@ -107,4 +107,11 @@ export type UnitControlProps = Pick< InputControlProps, 'size' > & * Callback when either the quantity or the unit inputs gains focus. */ onFocus?: FocusEventHandler< HTMLInputElement | HTMLSelectElement >; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; From 11262acadc190ca5715424728cafb1939d17483e Mon Sep 17 00:00:00 2001 From: Raj Patel <71687258+imrraaj@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:27:08 +0530 Subject: [PATCH 06/52] Pullquote block having design issue when text-decoration is choosen strikethrough (#66707) Co-authored-by: imrraaj Co-authored-by: NidhiDhandhukiya74 Co-authored-by: jorgefilipecosta --- packages/block-library/src/pullquote/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/pullquote/style.scss b/packages/block-library/src/pullquote/style.scss index b9e28b7cdcfaa..ff5fe6068dfac 100644 --- a/packages/block-library/src/pullquote/style.scss +++ b/packages/block-library/src/pullquote/style.scss @@ -72,4 +72,5 @@ .wp-block-pullquote cite { color: inherit; + display: block; } From 9ffbd9c215b2b43e527355d83b8d3f313b68582f Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Fri, 29 Nov 2024 19:19:13 +0000 Subject: [PATCH 07/52] Block editor: try direct drag (outside text editable) (#67305) Co-authored-by: ellatrix Co-authored-by: youknowriad Co-authored-by: jasmussen Co-authored-by: draganescu --- .../components/block-draggable/content.scss | 16 ++- .../src/components/block-list/block.js | 1 + .../src/components/block-list/content.scss | 6 + .../block-list/use-block-props/index.js | 7 + .../use-firefox-draggable-compatibility.js | 25 ++++ .../use-selected-block-event-handlers.js | 120 ++++++++++++++++-- .../src/components/iframe/content.scss | 4 + .../src/components/rich-text/index.js | 5 + .../components/use-block-drop-zone/index.js | 19 ++- .../writing-flow/use-drag-selection.js | 11 ++ packages/components/CHANGELOG.md | 4 + .../components/src/resizable-box/index.tsx | 10 ++ .../components/src/resizable-box/style.scss | 8 ++ test/e2e/specs/editor/blocks/spacer.spec.js | 4 +- .../block-bindings/custom-sources.spec.js | 10 +- .../editor/various/draggable-blocks.spec.js | 100 ++++++++++----- 16 files changed, 300 insertions(+), 50 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js diff --git a/packages/block-editor/src/components/block-draggable/content.scss b/packages/block-editor/src/components/block-draggable/content.scss index 102230168e213..25a0f5c256595 100644 --- a/packages/block-editor/src/components/block-draggable/content.scss +++ b/packages/block-editor/src/components/block-draggable/content.scss @@ -1,13 +1,12 @@ // This creates a "slot" where the block you're dragging appeared. // We use !important as one of the rules are meant to be overridden. .block-editor-block-list__layout .is-dragging { - background-color: currentColor !important; - opacity: 0.05 !important; + opacity: 0.1 !important; border-radius: $radius-small !important; - // Disabling pointer events during the drag event is necessary, - // lest the block might affect your drag operation. - pointer-events: none !important; + iframe { + pointer-events: none; + } // Hide the multi selection indicator when dragging. &::selection { @@ -18,3 +17,10 @@ content: none !important; } } + +// Images are draggable by default, so disable drag for them if not explicitly +// set. This is done so that the block can capture the drag event instead. +.wp-block img:not([draggable]), +.wp-block svg:not([draggable]) { + pointer-events: none; +} diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 6d4655189d972..0e3a5be5150de 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -797,6 +797,7 @@ function BlockListBlockProvider( props ) { mayDisplayParentControls, originalBlockClientId, themeSupportsLayout, + canMove, }; // Here we separate between the props passed to BlockListBlock and any other diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index 3d3b8517ca09c..cd517fced833e 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -427,3 +427,9 @@ _::-webkit-full-page-media, _:future, :root [data-has-multi-selection="true"] .b // Additional -1px is required to avoid sub pixel rounding errors allowing background to show. margin: 0 calc(-1 * var(--wp--style--root--padding-right) - 1px) 0 calc(-1 * var(--wp--style--root--padding-left) - 1px) !important; } + +// This only works in Firefox, Chrome and Safari don't accept a custom cursor +// during drag. +.is-dragging { + cursor: grabbing; +} diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 25b9a21f0d286..4696149dc3875 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -30,6 +30,7 @@ import { useIntersectionObserver } from './use-intersection-observer'; import { useScrollIntoView } from './use-scroll-into-view'; import { useFlashEditableBlocks } from '../../use-flash-editable-blocks'; import { canBindBlock } from '../../../hooks/use-bindings-attributes'; +import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility'; /** * This hook is used to lightly mark an element as a block element. The element @@ -100,11 +101,15 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isTemporarilyEditingAsBlocks, defaultClassName, isSectionBlock, + canMove, } = useContext( PrivateBlockContext ); + const canDrag = canMove && ! hasChildSelected; + // translators: %s: Type of block (i.e. Text, Image etc) const blockLabel = sprintf( __( 'Block: %s' ), blockTitle ); const htmlSuffix = mode === 'html' && ! __unstableIsHtml ? '-visual' : ''; + const ffDragRef = useFirefoxDraggableCompatibility(); const mergedRefs = useMergeRefs( [ props.ref, useFocusFirstElement( { clientId, initialPosition } ), @@ -120,6 +125,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isEnabled: isSectionBlock, } ), useScrollIntoView( { isSelected } ), + canDrag ? ffDragRef : undefined, ] ); const blockEditContext = useBlockEditContext(); @@ -152,6 +158,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { return { tabIndex: blockEditingMode === 'disabled' ? -1 : 0, + draggable: canDrag ? true : undefined, ...wrapperProps, ...props, ref: mergedRefs, diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js new file mode 100644 index 0000000000000..5fa07fb9be604 --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useRefEffect } from '@wordpress/compose'; + +/** + * In Firefox, the `draggable` and `contenteditable` attributes don't play well + * together. When `contenteditable` is within a `draggable` element, selection + * doesn't get set in the right place. The only solution is to temporarily + * remove the `draggable` attribute clicking inside `contenteditable` elements. + * + * @return {Function} Cleanup function. + */ +export function useFirefoxDraggableCompatibility() { + return useRefEffect( ( node ) => { + function onDown( event ) { + node.draggable = ! event.target.isContentEditable; + } + const { ownerDocument } = node; + ownerDocument.addEventListener( 'pointerdown', onDown ); + return () => { + ownerDocument.removeEventListener( 'pointerdown', onDown ); + }; + }, [] ); +} diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js index 68f8a671adbe9..0a13ce6700b8e 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js @@ -5,12 +5,15 @@ import { isTextField } from '@wordpress/dom'; import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { useRefEffect } from '@wordpress/compose'; +import { createRoot } from '@wordpress/element'; +import { store as blocksStore } from '@wordpress/blocks'; /** * Internal dependencies */ import { store as blockEditorStore } from '../../../store'; import { unlock } from '../../../lock-unlock'; +import BlockDraggableChip from '../../../components/block-draggable/draggable-chip'; /** * Adds block behaviour: @@ -21,12 +24,16 @@ import { unlock } from '../../../lock-unlock'; * @param {string} clientId Block client ID. */ export function useEventHandlers( { clientId, isSelected } ) { - const { getBlockRootClientId, getBlockIndex, isZoomOut } = unlock( - useSelect( blockEditorStore ) - ); - const { insertAfterBlock, removeBlock, resetZoomLevel } = unlock( - useDispatch( blockEditorStore ) - ); + const { getBlockType } = useSelect( blocksStore ); + const { getBlockRootClientId, isZoomOut, hasMultiSelection, getBlockName } = + unlock( useSelect( blockEditorStore ) ); + const { + insertAfterBlock, + removeBlock, + resetZoomLevel, + startDraggingBlocks, + stopDraggingBlocks, + } = unlock( useDispatch( blockEditorStore ) ); return useRefEffect( ( node ) => { @@ -76,7 +83,102 @@ export function useEventHandlers( { clientId, isSelected } ) { * @param {DragEvent} event Drag event. */ function onDragStart( event ) { - event.preventDefault(); + if ( + node !== event.target || + node.isContentEditable || + node.ownerDocument.activeElement !== node || + hasMultiSelection() + ) { + event.preventDefault(); + return; + } + const data = JSON.stringify( { + type: 'block', + srcClientIds: [ clientId ], + srcRootClientId: getBlockRootClientId( clientId ), + } ); + event.dataTransfer.effectAllowed = 'move'; // remove "+" cursor + event.dataTransfer.clearData(); + event.dataTransfer.setData( 'wp-blocks', data ); + const { ownerDocument } = node; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); + selection.removeAllRanges(); + + const domNode = document.createElement( 'div' ); + const root = createRoot( domNode ); + root.render( + + ); + document.body.appendChild( domNode ); + domNode.style.position = 'absolute'; + domNode.style.top = '0'; + domNode.style.left = '0'; + domNode.style.zIndex = '1000'; + domNode.style.pointerEvents = 'none'; + + // Setting the drag chip as the drag image actually works, but + // the behaviour is slightly different in every browser. In + // Safari, it animates, in Firefox it's slightly transparent... + // So we set a fake drag image and have to reposition it + // ourselves. + const dragElement = ownerDocument.createElement( 'div' ); + // Chrome will show a globe icon if the drag element does not + // have dimensions. + dragElement.style.width = '1px'; + dragElement.style.height = '1px'; + dragElement.style.position = 'fixed'; + dragElement.style.visibility = 'hidden'; + ownerDocument.body.appendChild( dragElement ); + event.dataTransfer.setDragImage( dragElement, 0, 0 ); + + let offset = { x: 0, y: 0 }; + + if ( document !== ownerDocument ) { + const frame = defaultView.frameElement; + if ( frame ) { + const rect = frame.getBoundingClientRect(); + offset = { x: rect.left, y: rect.top }; + } + } + + // chip handle offset + offset.x -= 58; + + function over( e ) { + domNode.style.transform = `translate( ${ + e.clientX + offset.x + }px, ${ e.clientY + offset.y }px )`; + } + + over( event ); + + function end() { + ownerDocument.removeEventListener( 'dragover', over ); + ownerDocument.removeEventListener( 'dragend', end ); + domNode.remove(); + dragElement.remove(); + stopDraggingBlocks(); + document.body.classList.remove( + 'is-dragging-components-draggable' + ); + ownerDocument.documentElement.classList.remove( + 'is-dragging' + ); + } + + ownerDocument.addEventListener( 'dragover', over ); + ownerDocument.addEventListener( 'dragend', end ); + ownerDocument.addEventListener( 'drop', end ); + + startDraggingBlocks( [ clientId ] ); + // Important because it hides the block toolbar. + document.body.classList.add( + 'is-dragging-components-draggable' + ); + ownerDocument.documentElement.classList.add( 'is-dragging' ); } node.addEventListener( 'keydown', onKeyDown ); @@ -91,11 +193,13 @@ export function useEventHandlers( { clientId, isSelected } ) { clientId, isSelected, getBlockRootClientId, - getBlockIndex, insertAfterBlock, removeBlock, isZoomOut, resetZoomLevel, + hasMultiSelection, + startDraggingBlocks, + stopDraggingBlocks, ] ); } diff --git a/packages/block-editor/src/components/iframe/content.scss b/packages/block-editor/src/components/iframe/content.scss index 9b02716671de7..74efb63c0e077 100644 --- a/packages/block-editor/src/components/iframe/content.scss +++ b/packages/block-editor/src/components/iframe/content.scss @@ -60,5 +60,9 @@ } } } + + .wp-block[draggable] { + cursor: grab; + } } } diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 8f179d08570ad..bc8eca6ea94d0 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -431,6 +431,11 @@ export function RichTextWrapper( aria-multiline={ ! disableLineBreaks } aria-readonly={ shouldDisableEditing } { ...props } + // Unset draggable (coming from block props) for contentEditable + // elements because it will interfere with multi block selection + // when the contentEditable and draggable elements are the same + // element. + draggable={ undefined } aria-label={ bindingsLabel || props[ 'aria-label' ] || placeholder } diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index 2a3e4948d40b3..221e5ab74ebb2 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -332,6 +332,7 @@ export default function useBlockDropZone( { isGroupable, isZoomOut, getSectionRootClientId, + getBlockParents, } = unlock( useSelect( blockEditorStore ) ); const { showInsertionPoint, @@ -358,13 +359,29 @@ export default function useBlockDropZone( { // So, ensure that the drag state is set when the user drags over a drop zone. startDragging(); } + + const draggedBlockClientIds = getDraggedBlockClientIds(); + const targetParents = [ + targetRootClientId, + ...getBlockParents( targetRootClientId, true ), + ]; + + // Check if the target is within any of the dragged blocks. + const isTargetWithinDraggedBlocks = draggedBlockClientIds.some( + ( clientId ) => targetParents.includes( clientId ) + ); + + if ( isTargetWithinDraggedBlocks ) { + return; + } + const allowedBlocks = getAllowedBlocks( targetRootClientId ); const targetBlockName = getBlockNamesByClientId( [ targetRootClientId, ] )[ 0 ]; const draggedBlockNames = getBlockNamesByClientId( - getDraggedBlockClientIds() + draggedBlockClientIds ); const isBlockDroppingAllowed = isDropTargetValid( getBlockType, diff --git a/packages/block-editor/src/components/writing-flow/use-drag-selection.js b/packages/block-editor/src/components/writing-flow/use-drag-selection.js index 1569c45a7c676..ea4c09b3dc957 100644 --- a/packages/block-editor/src/components/writing-flow/use-drag-selection.js +++ b/packages/block-editor/src/components/writing-flow/use-drag-selection.js @@ -80,7 +80,17 @@ export default function useDragSelection() { } ); } + let lastMouseDownTarget; + + function onMouseDown( { target } ) { + lastMouseDownTarget = target; + } + function onMouseLeave( { buttons, target, relatedTarget } ) { + if ( ! target.contains( lastMouseDownTarget ) ) { + return; + } + // If we're moving into a child element, ignore. We're tracking // the mouse leaving the element to a parent, no a child. if ( target.contains( relatedTarget ) ) { @@ -141,6 +151,7 @@ export default function useDragSelection() { } node.addEventListener( 'mouseout', onMouseLeave ); + node.addEventListener( 'mousedown', onMouseDown ); return () => { node.removeEventListener( 'mouseout', onMouseLeave ); diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 7865993d4e995..2785447227416 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -21,6 +21,10 @@ - Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)). - Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)). +### Bug Fixes + +- `ResizableBox`: Make drag handles focusable ([#67305](https://github.com/WordPress/gutenberg/pull/67305)). + ## 28.13.0 (2024-11-27) ### Deprecations diff --git a/packages/components/src/resizable-box/index.tsx b/packages/components/src/resizable-box/index.tsx index 1b05270ea0bf2..3bf3d36aa0d5c 100644 --- a/packages/components/src/resizable-box/index.tsx +++ b/packages/components/src/resizable-box/index.tsx @@ -112,6 +112,16 @@ function UnforwardedResizableBox( showHandle && 'has-show-handle', className ) } + // Add a focusable element within the drag handle. Unfortunately, + // `re-resizable` does not make them properly focusable by default, + // causing focus to move the the block wrapper which triggers block + // drag. + handleComponent={ Object.fromEntries( + Object.keys( HANDLE_CLASSES ).map( ( key ) => [ + key, +
, + ] ) + ) } handleClasses={ HANDLE_CLASSES } handleStyles={ HANDLE_STYLES } ref={ ref } diff --git a/packages/components/src/resizable-box/style.scss b/packages/components/src/resizable-box/style.scss index 3c9efd2713646..4db3d27b5fab6 100644 --- a/packages/components/src/resizable-box/style.scss +++ b/packages/components/src/resizable-box/style.scss @@ -15,6 +15,14 @@ $resize-handler-container-size: $resize-handler-size + ($grid-unit-05 * 2); // M .components-resizable-box__container.has-show-handle & { display: block; } + + > div { + position: relative; + width: 100%; + height: 100%; + z-index: z-index(".components-resizable-box__handle"); + outline: none; + } } // Make the image inside the resize to get the full width diff --git a/test/e2e/specs/editor/blocks/spacer.spec.js b/test/e2e/specs/editor/blocks/spacer.spec.js index f089402514623..da262c9b4e26d 100644 --- a/test/e2e/specs/editor/blocks/spacer.spec.js +++ b/test/e2e/specs/editor/blocks/spacer.spec.js @@ -43,7 +43,9 @@ test.describe( 'Spacer', () => { expect( await editor.getEditedPostContent() ).toMatchSnapshot(); await expect( - editor.canvas.locator( 'role=document[name="Block: Spacer"i]' ) + editor.canvas.locator( + 'role=document[name="Block: Spacer"i] >> css=.components-resizable-box__handle >> [tabindex]' + ) ).toBeFocused(); } ); } ); diff --git a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js b/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js index d6563ce9cb5f5..033a69e2d6170 100644 --- a/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js +++ b/test/e2e/specs/editor/various/block-bindings/custom-sources.spec.js @@ -168,7 +168,10 @@ test.describe( 'Registered sources', () => { name: 'Block: Image', } ) .locator( 'img' ); - await imageBlockImg.click(); + // Playwright will complain that the pointer events are captured by + // the parent, but that's fine. + // eslint-disable-next-line playwright/no-force-option + await imageBlockImg.click( { force: true } ); // Image src is the custom field value. await expect( imageBlockImg ).toHaveAttribute( @@ -735,7 +738,10 @@ test.describe( 'Registered sources', () => { name: 'Block: Image', } ) .locator( 'img' ); - await imageBlockImg.click(); + // Playwright will complain that the pointer events are captured by + // the parent, but that's fine. + // eslint-disable-next-line playwright/no-force-option + await imageBlockImg.click( { force: true } ); // Edit the custom field value in the alt textarea. const altInputArea = page diff --git a/test/e2e/specs/editor/various/draggable-blocks.spec.js b/test/e2e/specs/editor/various/draggable-blocks.spec.js index e08030191dd60..704817f4a2c38 100644 --- a/test/e2e/specs/editor/various/draggable-blocks.spec.js +++ b/test/e2e/specs/editor/various/draggable-blocks.spec.js @@ -18,6 +18,14 @@ test.use( { }, } ); +async function dragTo( page, x, y ) { + // Call the move function twice to make sure the `dragOver` event is sent. + // @see https://github.com/microsoft/playwright/issues/17153 + for ( let i = 0; i < 2; i += 1 ) { + await page.mouse.move( x, y ); + } +} + test.describe( 'Draggable block', () => { test.beforeEach( async ( { admin } ) => { await admin.createNewPost(); @@ -60,14 +68,7 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=1' ); const firstParagraphBound = await firstParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - firstParagraphBound.x, - firstParagraphBound.y - ); - } + await dragTo( page, firstParagraphBound.x, firstParagraphBound.y ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -132,15 +133,11 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=2' ); const secondParagraphBound = await secondParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - // Make sure mouse is > 30px within the block for bottom drop indicator to appear. - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - secondParagraphBound.x + 32, - secondParagraphBound.y + secondParagraphBound.height * 0.75 - ); - } + await dragTo( + page, + secondParagraphBound.x + 32, + secondParagraphBound.y + secondParagraphBound.height * 0.75 + ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -216,14 +213,11 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=1' ); const firstParagraphBound = await firstParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - firstParagraphBound.x + firstParagraphBound.width * 0.25, - firstParagraphBound.y - ); - } + await dragTo( + page, + firstParagraphBound.x + firstParagraphBound.width * 0.25, + firstParagraphBound.y + ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -297,14 +291,11 @@ test.describe( 'Draggable block', () => { 'role=document[name="Block: Paragraph"i] >> text=2' ); const secondParagraphBound = await secondParagraph.boundingBox(); - // Call the move function twice to make sure the `dragOver` event is sent. - // @see https://github.com/microsoft/playwright/issues/17153 - for ( let i = 0; i < 2; i += 1 ) { - await page.mouse.move( - secondParagraphBound.x + secondParagraphBound.width * 0.75, - secondParagraphBound.y - ); - } + await dragTo( + page, + secondParagraphBound.x + secondParagraphBound.width * 0.75, + secondParagraphBound.y + ); await expect( page.locator( 'data-testid=block-draggable-chip >> visible=true' ) @@ -465,4 +456,47 @@ test.describe( 'Draggable block', () => { ] ); } } ); + + test( 'can directly drag an image', async ( { page, editor } ) => { + await editor.insertBlock( { name: 'core/image' } ); + await editor.insertBlock( { + name: 'core/group', + attributes: { layout: { type: 'constrained' } }, + innerBlocks: [ { name: 'core/paragraph' } ], + } ); + + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + + const groupBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Group', + } ); + + await imageBlock.hover(); + await page.mouse.down(); + const groupBlockBox = await groupBlock.boundingBox(); + await dragTo( + page, + groupBlockBox.x + groupBlockBox.width * 0.5, + groupBlockBox.y + groupBlockBox.height * 0.5 + ); + await page.mouse.up(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/group', + attributes: { + tagName: 'div', + layout: { type: 'constrained' }, + }, + innerBlocks: [ + { + name: 'core/image', + attributes: { alt: '', caption: '' }, + }, + ], + }, + ] ); + } ); } ); From 9defe9f7b6f7147756d1dba26dc0a8f8c07052d0 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sat, 30 Nov 2024 08:24:55 +1100 Subject: [PATCH 08/52] Fix misc type compilation errors in editor and block editor packages (#67410) Co-authored-by: ramonjd Co-authored-by: Mamaduka --- packages/components/CHANGELOG.md | 1 + packages/components/src/index.ts | 1 + packages/editor/README.md | 7 ++++--- packages/editor/src/components/post-trash/check.js | 6 +++--- packages/editor/src/components/post-url/index.js | 3 ++- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2785447227416..c9295e7ef861e 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -20,6 +20,7 @@ - Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)). - Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)). +- Exported the `WPCompleter` type as it was being used in block-editor/autocompleters ([#67410](https://github.com/WordPress/gutenberg/pull/67410)). ### Bug Fixes diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index e82d6da70279e..0558584fe5418 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -108,6 +108,7 @@ export { Heading as __experimentalHeading } from './heading'; export { HStack as __experimentalHStack } from './h-stack'; export { default as Icon } from './icon'; export type { IconType } from './icon'; +export type { WPCompleter } from './autocomplete/types.ts'; export { default as IconButton } from './button/deprecated'; export { ItemGroup as __experimentalItemGroup, diff --git a/packages/editor/README.md b/packages/editor/README.md index 36126cb8eaee3..8b48d773acb26 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -1462,11 +1462,11 @@ Wrapper component that renders its children only if the post can trashed. _Parameters_ - _props_ `Object`: - The component props. -- _props.children_ `React.ReactEl`: - The child components to render. +- _props.children_ `React.ReactNode`: - The child components to render. _Returns_ -- `React.ReactElement`: The rendered child components or null if the post can not trashed. +- `React.ReactNode`: The rendered child components or null if the post can not trashed. ### PostTypeSupportCheck @@ -1494,7 +1494,8 @@ _Usage_ _Parameters_ -- _onClose_ `Function`: Callback function to be executed when the popover is closed. +- _props_ `{ onClose: () => void }`: The props for the component. +- _props.onClose_ `() => void`: Callback function to be executed when the popover is closed. _Returns_ diff --git a/packages/editor/src/components/post-trash/check.js b/packages/editor/src/components/post-trash/check.js index 893d46ef9a086..d4a9139bfee05 100644 --- a/packages/editor/src/components/post-trash/check.js +++ b/packages/editor/src/components/post-trash/check.js @@ -13,10 +13,10 @@ import { GLOBAL_POST_TYPES } from '../../store/constants'; /** * Wrapper component that renders its children only if the post can trashed. * - * @param {Object} props - The component props. - * @param {React.ReactEl} props.children - The child components to render. + * @param {Object} props - The component props. + * @param {React.ReactNode} props.children - The child components to render. * - * @return {React.ReactElement} The rendered child components or null if the post can not trashed. + * @return {React.ReactNode} The rendered child components or null if the post can not trashed. */ export default function PostTrashCheck( { children } ) { const { canTrashPost } = useSelect( ( select ) => { diff --git a/packages/editor/src/components/post-url/index.js b/packages/editor/src/components/post-url/index.js index c72ca5825f6fe..f55ac973be50e 100644 --- a/packages/editor/src/components/post-url/index.js +++ b/packages/editor/src/components/post-url/index.js @@ -32,7 +32,8 @@ import { store as editorStore } from '../../store'; * * ``` * - * @param {Function} onClose Callback function to be executed when the popover is closed. + * @param {{ onClose: () => void }} props The props for the component. + * @param {() => void} props.onClose Callback function to be executed when the popover is closed. * * @return {React.ReactNode} The rendered PostURL component. */ From 961000076e74a6eea419d79ff8e16ae54a73463b Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Fri, 29 Nov 2024 23:55:22 -0800 Subject: [PATCH 09/52] Add types for shortcode package (#67416) * Add types for shortcode package * Remove WP Prefix * Update CHANGELOG.md Co-authored-by: manzoorwanijk Co-authored-by: gziolo --- packages/shortcode/CHANGELOG.md | 4 + packages/shortcode/README.md | 18 ++- packages/shortcode/package.json | 1 + packages/shortcode/src/index.js | 51 ++------ packages/shortcode/src/types.ts | 210 +++++++++++++++++++++++++++++++ packages/shortcode/tsconfig.json | 11 ++ tsconfig.json | 1 + 7 files changed, 245 insertions(+), 51 deletions(-) create mode 100644 packages/shortcode/src/types.ts create mode 100644 packages/shortcode/tsconfig.json diff --git a/packages/shortcode/CHANGELOG.md b/packages/shortcode/CHANGELOG.md index 8ee4e9aa3168d..2e461f1bc85c1 100644 --- a/packages/shortcode/CHANGELOG.md +++ b/packages/shortcode/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## Enhancements + +- The package now has built-in TypeScript definitions 🎉 ([#67416](https://github.com/WordPress/gutenberg/pull/67416)) + ## 4.13.0 (2024-11-27) ## 4.12.0 (2024-11-16) diff --git a/packages/shortcode/README.md b/packages/shortcode/README.md index a1a016e75e755..b6042ab284fe4 100644 --- a/packages/shortcode/README.md +++ b/packages/shortcode/README.md @@ -32,7 +32,7 @@ _Parameters_ _Returns_ -- `WPShortcodeAttrs`: Parsed shortcode attributes. +- `import('./types').ShortcodeAttrs`: Parsed shortcode attributes. ### default @@ -40,13 +40,9 @@ Creates a shortcode instance. To access a raw representation of a shortcode, pass an `options` object, containing a `tag` string, a string or object of `attrs`, a string indicating the `type` of the shortcode ('single', 'self-closing', or 'closed'), and a `content` string. -_Parameters_ - -- _options_ `Object`: Options as described. - -_Returns_ +_Type_ -- `WPShortcode`: Shortcode instance. +- `import('./types').shortcode`Shortcode instance. ### fromMatch @@ -56,11 +52,11 @@ Accepts a `match` object from calling `regexp.exec()` on a `RegExp` generated by _Parameters_ -- _match_ `Array`: Match array. +- _match_ `import('./types').Match`: Match array. _Returns_ -- `WPShortcode`: Shortcode instance. +- `InstanceType`: Shortcode instance. ### next @@ -74,7 +70,7 @@ _Parameters_ _Returns_ -- `WPShortcodeMatch | undefined`: Matched information. +- `import('./types').ShortcodeMatch | undefined`: Matched information. ### regexp @@ -108,7 +104,7 @@ _Parameters_ - _tag_ `string`: Shortcode tag. - _text_ `string`: Text to search. -- _callback_ `Function`: Function to process the match and return replacement string. +- _callback_ `import('./types').ReplaceCallback`: Function to process the match and return replacement string. _Returns_ diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index f87fe06d9bb42..c5495364a03c4 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -26,6 +26,7 @@ "module": "build-module/index.js", "react-native": "src/index", "wpScript": true, + "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", "memize": "^2.0.1" diff --git a/packages/shortcode/src/index.js b/packages/shortcode/src/index.js index 04e69c272378b..4d99086033e95 100644 --- a/packages/shortcode/src/index.js +++ b/packages/shortcode/src/index.js @@ -3,34 +3,7 @@ */ import memize from 'memize'; -/** - * Shortcode attributes object. - * - * @typedef {Object} WPShortcodeAttrs - * - * @property {Object} named Object with named attributes. - * @property {Array} numeric Array with numeric attributes. - */ - -/** - * Shortcode object. - * - * @typedef {Object} WPShortcode - * - * @property {string} tag Shortcode tag. - * @property {WPShortcodeAttrs} attrs Shortcode attributes. - * @property {string} content Shortcode content. - * @property {string} type Shortcode type: `self-closing`, - * `closed`, or `single`. - */ - -/** - * @typedef {Object} WPShortcodeMatch - * - * @property {number} index Index the shortcode is found at. - * @property {string} content Matched content. - * @property {WPShortcode} shortcode Shortcode instance of the match. - */ +export * from './types'; /** * Find the next matching shortcode. @@ -39,7 +12,7 @@ import memize from 'memize'; * @param {string} text Text to search. * @param {number} index Index to start search from. * - * @return {WPShortcodeMatch | undefined} Matched information. + * @return {import('./types').ShortcodeMatch | undefined} Matched information. */ export function next( tag, text, index = 0 ) { const re = regexp( tag ); @@ -81,10 +54,10 @@ export function next( tag, text, index = 0 ) { /** * Replace matching shortcodes in a block of text. * - * @param {string} tag Shortcode tag. - * @param {string} text Text to search. - * @param {Function} callback Function to process the match and return - * replacement string. + * @param {string} tag Shortcode tag. + * @param {string} text Text to search. + * @param {import('./types').ReplaceCallback} callback Function to process the match and return + * replacement string. * * @return {string} Text with shortcodes replaced. */ @@ -169,7 +142,7 @@ export function regexp( tag ) { * * @param {string} text Serialised shortcode attributes. * - * @return {WPShortcodeAttrs} Parsed shortcode attributes. + * @return {import('./types').ShortcodeAttrs} Parsed shortcode attributes. */ export const attrs = memize( ( text ) => { const named = {}; @@ -224,9 +197,9 @@ export const attrs = memize( ( text ) => { * by `regexp()`. `match` can also be set to the `arguments` from a callback * passed to `regexp.replace()`. * - * @param {Array} match Match array. + * @param {import('./types').Match} match Match array. * - * @return {WPShortcode} Shortcode instance. + * @return {InstanceType} Shortcode instance. */ export function fromMatch( match ) { let type; @@ -255,9 +228,7 @@ export function fromMatch( match ) { * the `type` of the shortcode ('single', 'self-closing', or 'closed'), and a * `content` string. * - * @param {Object} options Options as described. - * - * @return {WPShortcode} Shortcode instance. + * @type {import('./types').shortcode} Shortcode instance. */ const shortcode = Object.assign( function ( options ) { @@ -328,7 +299,7 @@ Object.assign( shortcode.prototype, { * @param {(number|string)} attr Attribute key. * @param {string} value Attribute value. * - * @return {WPShortcode} Shortcode instance. + * @return {InstanceType< import('./types').shortcode >} Shortcode instance. */ set( attr, value ) { this.attrs[ typeof attr === 'number' ? 'numeric' : 'named' ][ attr ] = diff --git a/packages/shortcode/src/types.ts b/packages/shortcode/src/types.ts new file mode 100644 index 0000000000000..2b9ae084cc31a --- /dev/null +++ b/packages/shortcode/src/types.ts @@ -0,0 +1,210 @@ +/** + * Shortcode attributes object. + */ +export type ShortcodeAttrs = { + /** + * Object with named attributes. + */ + named: Record< string, string | undefined >; + + /** + * Array with numeric attributes. + */ + numeric: string[]; +}; + +export type ShortcodeMatch = { + /** + * Index the shortcode is found at. + */ + index: number; + + /** + * Matched content. + */ + content: string; + + /** + * Shortcode instance of the match. + */ + shortcode: Shortcode; +}; + +/** + * Shortcode options. + */ +export interface ShortcodeOptions { + /** + * Shortcode tag. + */ + tag: string; + + /** + * Shortcode attributes. + */ + attrs?: Partial< ShortcodeAttrs > | string; + + /** + * Shortcode content. + */ + content?: string; + + /** + * Shortcode type: `self-closing`, `closed`, or `single`. + */ + type?: 'self-closing' | 'closed' | 'single'; +} + +/** + * Shortcode object. + */ +export interface Shortcode extends ShortcodeOptions { + /** + * Shortcode attributes. + */ + attrs: ShortcodeAttrs; +} + +export type Match = + | NonNullable< ReturnType< RegExp[ 'exec' ] > > + | Array< string >; + +export type ReplaceCallback = ( shortcode: Shortcode ) => string; + +/** + * WordPress Shortcode instance. + */ +export interface shortcode { + new ( options: Partial< ShortcodeOptions > ): Shortcode & { + /** + * Transform the shortcode into a string. + * + * @return {string} String representation of the shortcode. + */ + string: () => string; + + /** + * Get a shortcode attribute. + * + * Automatically detects whether `attr` is named or numeric and routes it + * accordingly. + * + * @param {(number|string)} attr Attribute key. + * + * @return {string} Attribute value. + */ + get: ( attr: string | number ) => string | undefined; + + /** + * Set a shortcode attribute. + * + * Automatically detects whether `attr` is named or numeric and routes it + * accordingly. + * + * @param {(number|string)} attr Attribute key. + * @param {string} value Attribute value. + * + * @return {InstanceType< shortcode >} Shortcode instance. + */ + set: ( + attr: string | number, + value: string + ) => InstanceType< shortcode >; + }; + + /** + * Parse shortcode attributes. + * + * Shortcodes accept many types of attributes. These can chiefly be divided into + * named and numeric attributes: + * + * Named attributes are assigned on a key/value basis, while numeric attributes + * are treated as an array. + * + * Named attributes can be formatted as either `name="value"`, `name='value'`, + * or `name=value`. Numeric attributes can be formatted as `"value"` or just + * `value`. + * + * @param text Serialised shortcode attributes. + * + * @return Parsed shortcode attributes. + */ + attrs: ( text: string ) => ShortcodeAttrs; + + /** + * Generate a Shortcode Object from a RegExp match. + * + * Accepts a `match` object from calling `regexp.exec()` on a `RegExp` generated + * by `regexp()`. `match` can also be set to the `arguments` from a callback + * passed to `regexp.replace()`. + * + * @param match Match array. + * + * @return Shortcode instance. + */ + fromMatch: ( match: Match ) => InstanceType< shortcode >; + + /** + * Find the next matching shortcode. + * + * @param tag Shortcode tag. + * @param text Text to search. + * @param index Index to start search from. + * + * @return Matched information. + */ + next: ( + tag: string, + text: string, + index?: number + ) => ShortcodeMatch | undefined; + + /** + * Generate a RegExp to identify a shortcode. + * + * The base regex is functionally equivalent to the one found in + * `get_shortcode_regex()` in `wp-includes/shortcodes.php`. + * + * Capture groups: + * + * 1. An extra `[` to allow for escaping shortcodes with double `[[]]` + * 2. The shortcode name + * 3. The shortcode argument list + * 4. The self closing `/` + * 5. The content of a shortcode when it wraps some content. + * 6. The closing tag. + * 7. An extra `]` to allow for escaping shortcodes with double `[[]]` + * + * @param tag Shortcode tag. + * + * @return Shortcode RegExp. + */ + regexp: ( tag: string ) => RegExp; + + /** + * Replace matching shortcodes in a block of text. + * + * @param tag Shortcode tag. + * @param text Text to search. + * @param callback Function to process the match and return + * replacement string. + * + * @return Text with shortcodes replaced. + */ + replace: ( tag: string, text: string, callback: ReplaceCallback ) => string; + + /** + * Generate a string from shortcode parameters. + * + * Creates a shortcode instance and returns a string. + * + * Accepts the same `options` as the `shortcode()` constructor, containing a + * `tag` string, a string or object of `attrs`, a boolean indicating whether to + * format the shortcode using a `single` tag, and a `content` string. + * + * @param options + * + * @return String representation of the shortcode. + */ + string: ( options: ShortcodeOptions ) => string; +} diff --git a/packages/shortcode/tsconfig.json b/packages/shortcode/tsconfig.json new file mode 100644 index 0000000000000..79aa09d0ad56e --- /dev/null +++ b/packages/shortcode/tsconfig.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types", + "checkJs": false + }, + "references": [], + "include": [ "src" ] +} diff --git a/tsconfig.json b/tsconfig.json index 51bb7f2d68924..1010054ea512e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -50,6 +50,7 @@ { "path": "packages/report-flaky-tests" }, { "path": "packages/rich-text" }, { "path": "packages/router" }, + { "path": "packages/shortcode" }, { "path": "packages/style-engine" }, { "path": "packages/sync" }, { "path": "packages/token-list" }, From d0383fef7532779a27afbd226c51b535232201d4 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Sun, 1 Dec 2024 02:04:43 +0000 Subject: [PATCH 10/52] Fix: Styles section does not moves stylebook to typography. (#67423) Co-authored-by: jorgefilipecosta Co-authored-by: ramonjd --- packages/edit-site/src/components/style-book/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js index de4c38bd40c05..6a044d8055300 100644 --- a/packages/edit-site/src/components/style-book/index.js +++ b/packages/edit-site/src/components/style-book/index.js @@ -89,6 +89,11 @@ const scrollToSection = ( anchorId, iframe ) => { */ const getStyleBookNavigationFromPath = ( path ) => { if ( path && typeof path === 'string' ) { + if ( path.startsWith( '/typography' ) ) { + return { + block: 'typography', + }; + } let block = path.includes( '/blocks/' ) ? decodeURIComponent( path.split( '/blocks/' )[ 1 ] ) : null; From 988f259c5c32d312bd09a25789c6b658069ccb90 Mon Sep 17 00:00:00 2001 From: Shail Mehta Date: Sun, 1 Dec 2024 19:38:30 +0530 Subject: [PATCH 11/52] Updated old URL in Documentation (#67446) * Updated Old URL Co-authored-by: shail-mehta Co-authored-by: Mamaduka --- docs/reference-guides/block-api/block-transforms.md | 12 ++++++------ platform-docs/docs/create-block/transforms.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference-guides/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md index c2c5ed49d1b19..9055ed0a3b45b 100644 --- a/docs/reference-guides/block-api/block-transforms.md +++ b/docs/reference-guides/block-api/block-transforms.md @@ -44,7 +44,7 @@ A transformation of type `block` is an object that takes the following parameter - **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects. - **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user. - **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If true, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. False by default. -- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from Paragraph block to Heading block** @@ -97,7 +97,7 @@ A transformation of type `enter` is an object that takes the following parameter - **type** _(string)_: the value `enter`. - **regExp** _(RegExp)_: the Regular Expression to use as a matcher. If the value matches, the transformation will be applied. - **transform** _(function)_: a callback that receives an object with a `content` field containing the value that has been entered. It should return a block object or an array of block objects. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from --- to Separator block** @@ -124,7 +124,7 @@ A transformation of type `files` is an object that takes the following parameter - **type** _(string)_: the value `files`. - **transform** _(function)_: a callback that receives the array of files being processed. It should return a block object or an array of block objects. - **isMatch** _(function, optional)_: a callback that receives the array of files being processed and should return a boolean. Returning `false` from this function will prevent the transform from being applied. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from file to File block** @@ -164,7 +164,7 @@ A transformation of type `prefix` is an object that takes the following paramete - **type** _(string)_: the value `prefix`. - **prefix** _(string)_: the character or sequence of characters that match this transform. - **transform** _(function)_: a callback that receives the content introduced. It should return a block object or an array of block objects. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from text to custom block** @@ -197,7 +197,7 @@ A transformation of type `raw` is an object that takes the following parameters: - **schema** _(object|function, optional)_: defines an [HTML content model](https://html.spec.whatwg.org/multipage/dom.html#content-models) used to detect and process pasted contents. See [below](#schemas-and-content-models). - **selector** _(string, optional)_: a CSS selector string to determine whether the element matches according to the [element.matches](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) method. The transform won't be executed if the element doesn't match. This is a shorthand and alternative to using `isMatch`, which, if present, will take precedence. - **isMatch** _(function, optional)_: a callback that receives the node being processed and should return a boolean. Returning `false` from this function will prevent the transform from being applied. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from URLs to Embed block** @@ -273,7 +273,7 @@ A transformation of type `shortcode` is an object that takes the following param - **transform** _(function, optional)_: a callback that receives the shortcode attributes as the first argument and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as the second. It should return a block object or an array of block objects. When this parameter is defined, it will take precedence over the `attributes` parameter. - **attributes** _(object, optional)_: object representing where the block attributes should be sourced from, according to the attributes shape defined by the [block configuration object](./block-registration.md). If a particular attribute contains a `shortcode` key, it should be a function that receives the shortcode attributes as the first arguments and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as second, and returns a value for the attribute that will be sourced in the block's comment. - **isMatch** _(function, optional)_: a callback that receives the shortcode attributes per the [Shortcode API](https://codex.wordpress.org/Shortcode_API) and should return a boolean. Returning `false` from this function will prevent the shortcode to be transformed into this block. -- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: from shortcode to block using `transform`** diff --git a/platform-docs/docs/create-block/transforms.md b/platform-docs/docs/create-block/transforms.md index fd235b669cd72..4a4118d6850b9 100644 --- a/platform-docs/docs/create-block/transforms.md +++ b/platform-docs/docs/create-block/transforms.md @@ -37,7 +37,7 @@ A transformation of type `block` is an object that takes the following parameter - **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects. - **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user. - **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If `true`, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. Returns `false` by default. -- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. +- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set. **Example: Let's declare a transform from our Gutenpride block to Heading block** From 4775e7052b9e2ed7df46429e6e738de3faf2fb18 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Sun, 1 Dec 2024 20:00:10 +0000 Subject: [PATCH 12/52] Preload: fix settings fields order (#67450) Co-authored-by: ellatrix Co-authored-by: Mamaduka --- lib/compat/wordpress-6.8/preload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php index ae6c738c6627c..6d92913b41411 100644 --- a/lib/compat/wordpress-6.8/preload.php +++ b/lib/compat/wordpress-6.8/preload.php @@ -31,9 +31,9 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { 'site_icon_url', 'site_logo', 'timezone_string', - 'url', 'default_template_part_areas', 'default_template_types', + 'url', ) ); $paths[] = '/wp/v2/templates/lookup?slug=front-page'; From c5b33b86f27dd6b6e3b4450b7c72cb949e4bd95b Mon Sep 17 00:00:00 2001 From: Sunil Prajapati <61308756+akasunil@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:59:13 +0530 Subject: [PATCH 13/52] Implement image size option for featured image in cover block (#67273) Co-authored-by: akasunil Co-authored-by: aaronrobertshaw Co-authored-by: carolinan --- packages/block-library/src/cover/edit/index.js | 5 ++++- .../src/cover/edit/inspector-controls.js | 13 ++++++++++--- packages/block-library/src/cover/index.php | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js index 3ad7039b55892..ced3097320329 100644 --- a/packages/block-library/src/cover/edit/index.js +++ b/packages/block-library/src/cover/edit/index.js @@ -120,7 +120,9 @@ function CoverEdit( { select( coreStore ).getMedia( featuredImage, { context: 'view' } ), [ featuredImage ] ); - const mediaUrl = media?.source_url; + const mediaUrl = + media?.media_details?.sizes?.[ sizeSlug ]?.source_url ?? + media?.source_url; // User can change the featured image outside of the block, but we still // need to update the block when that happens. This effect should only @@ -451,6 +453,7 @@ function CoverEdit( { toggleUseFeaturedImage={ toggleUseFeaturedImage } updateDimRatio={ onUpdateDimRatio } onClearMedia={ onClearMedia } + featuredImage={ media } /> ); diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js index c0807869ee1a5..b0d4b435163b7 100644 --- a/packages/block-library/src/cover/edit/inspector-controls.js +++ b/packages/block-library/src/cover/edit/inspector-controls.js @@ -96,6 +96,7 @@ export default function CoverInspectorControls( { coverRef, currentSettings, updateDimRatio, + featuredImage, } ) { const { useFeaturedImage, @@ -132,8 +133,12 @@ export default function CoverInspectorControls( { [ id, isImageBackground ] ); + const currentBackgroundImage = useFeaturedImage ? featuredImage : image; + function updateImage( newSizeSlug ) { - const newUrl = image?.media_details?.sizes?.[ newSizeSlug ]?.source_url; + const newUrl = + currentBackgroundImage?.media_details?.sizes?.[ newSizeSlug ] + ?.source_url; if ( ! newUrl ) { return null; } @@ -146,7 +151,9 @@ export default function CoverInspectorControls( { const imageSizeOptions = imageSizes ?.filter( - ( { slug } ) => image?.media_details?.sizes?.[ slug ]?.source_url + ( { slug } ) => + currentBackgroundImage?.media_details?.sizes?.[ slug ] + ?.source_url ) ?.map( ( { name, slug } ) => ( { value: slug, label: name } ) ); @@ -321,7 +328,7 @@ export default function CoverInspectorControls( { /> ) } - { ! useFeaturedImage && !! imageSizeOptions?.length && ( + { !! imageSizeOptions?.length && ( Date: Mon, 2 Dec 2024 14:43:15 +0800 Subject: [PATCH 14/52] Fix List View not updating when switching editor modes (#67379) * Add back the editorTool dependency to getEnabledClientIdsTree * Add e2e test * Switch to getEditorMode selector ---- Co-authored-by: talldan Co-authored-by: aaronrobertshaw --- .../src/store/private-selectors.js | 8 +-- .../editor/various/write-design-mode.spec.js | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 9779ae1300fb5..61b17a3625d15 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -109,16 +109,16 @@ function getEnabledClientIdsTreeUnmemoized( state, rootClientId ) { * * @return {Object[]} Tree of block objects with only clientID and innerBlocks set. */ -export const getEnabledClientIdsTree = createSelector( - getEnabledClientIdsTreeUnmemoized, - ( state ) => [ +export const getEnabledClientIdsTree = createRegistrySelector( ( select ) => + createSelector( getEnabledClientIdsTreeUnmemoized, ( state ) => [ state.blocks.order, state.derivedBlockEditingModes, state.derivedNavModeBlockEditingModes, state.blockEditingModes, state.settings.templateLock, state.blockListSettings, - ] + select( STORE_NAME ).__unstableGetEditorMode( state ), + ] ) ); /** diff --git a/test/e2e/specs/editor/various/write-design-mode.spec.js b/test/e2e/specs/editor/various/write-design-mode.spec.js index 053f4cb8ff092..2892b4aea89e9 100644 --- a/test/e2e/specs/editor/various/write-design-mode.spec.js +++ b/test/e2e/specs/editor/various/write-design-mode.spec.js @@ -121,4 +121,59 @@ test.describe( 'Write/Design mode', () => { editorSettings.getByRole( 'button', { name: 'Content' } ) ).toBeVisible(); } ); + + test( 'hides the blocks that cannot be interacted with in List View', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.setContent( '' ); + + // Insert a section with a nested block and an editable block. + await editor.insertBlock( { + name: 'core/group', + attributes: {}, + innerBlocks: [ + { + name: 'core/group', + attributes: { + metadata: { + name: 'Non-content block', + }, + }, + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + content: 'Something', + }, + }, + ], + }, + ], + } ); + + // Select the inner paragraph block so that List View is expanded. + await editor.canvas + .getByRole( 'document', { + name: 'Block: Paragraph', + } ) + .click(); + + // Open List View. + await pageUtils.pressKeys( 'access+o' ); + const listView = page.getByRole( 'treegrid', { + name: 'Block navigation structure', + } ); + const nonContentBlock = listView.getByRole( 'link', { + name: 'Non-content block', + } ); + + await expect( nonContentBlock ).toBeVisible(); + + // Switch to write mode. + await editor.switchEditorTool( 'Write' ); + + await expect( nonContentBlock ).toBeHidden(); + } ); } ); From d251f755481bdfe6eac99ab85f6410e3334622cc Mon Sep 17 00:00:00 2001 From: Eshaan Dabasiya <76681468+im3dabasia@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:10:03 +0530 Subject: [PATCH 15/52] Experiments: Remove trailing space in Color randomizer (#67457) Co-authored-by: im3dabasia Co-authored-by: ramonjd --- lib/experiments-page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 9033e3c2d0c1f..256a185a3af69 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -69,7 +69,7 @@ function gutenberg_initialize_experiments_settings() { add_settings_field( 'gutenberg-color-randomizer', - __( 'Color randomizer ', 'gutenberg' ), + __( 'Color randomizer', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', From 2f670d56405a6c43800e5a5068bb8ee75b9205d9 Mon Sep 17 00:00:00 2001 From: Yogesh Bhutkar Date: Mon, 2 Dec 2024 15:30:08 +0530 Subject: [PATCH 16/52] Remove inline-block display from image anchor in style.scss (#67368) * Remove inline-block display from image anchor in style.scss * Refactor: Set image anchor display to inline-block in style.scss Co-authored-by: yogeshbhutkar Co-authored-by: t-hamano Co-authored-by: Infinite-Null Co-authored-by: hanneslsm Co-authored-by: frkly --- packages/block-library/src/image/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index a7fcb8f175e4e..8ca5795cfd911 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -1,6 +1,7 @@ .wp-block-image { - a { + > a, + > figure > a { display: inline-block; } From 358fb8e04445ebf29140ba875e273bba8fd43913 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:07:39 +0000 Subject: [PATCH 17/52] Inserter: Patterns: remove loading indicator (#67072) --- .../inserter/block-patterns-tab/index.js | 18 +----------------- .../src/store/private-selectors.js | 15 --------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js index 01e41111b7c89..45db4732aa9c6 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js @@ -3,9 +3,8 @@ */ import { useState } from '@wordpress/element'; import { useViewportMatch } from '@wordpress/compose'; -import { Button, Spinner } from '@wordpress/components'; +import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -16,8 +15,6 @@ import { PatternCategoryPreviews } from './pattern-category-previews'; import { usePatternCategories } from './use-pattern-categories'; import CategoryTabs from '../category-tabs'; import InserterNoResults from '../no-results'; -import { store as blockEditorStore } from '../../../store'; -import { unlock } from '../../../lock-unlock'; function BlockPatternsTab( { onSelectCategory, @@ -31,19 +28,6 @@ function BlockPatternsTab( { const categories = usePatternCategories( rootClientId ); const isMobile = useViewportMatch( 'medium', '<' ); - const isResolvingPatterns = useSelect( - ( select ) => - unlock( select( blockEditorStore ) ).isResolvingPatterns(), - [] - ); - - if ( isResolvingPatterns ) { - return ( -
- -
- ); - } if ( ! categories.length ) { return ; diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 61b17a3625d15..c46778d889b3e 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -406,21 +406,6 @@ export const getAllPatterns = createRegistrySelector( ( select ) => }, getAllPatternsDependants( select ) ) ); -export const isResolvingPatterns = createRegistrySelector( ( select ) => - createSelector( ( state ) => { - const blockPatternsSelect = state.settings[ selectBlockPatternsKey ]; - const reusableBlocksSelect = state.settings[ reusableBlocksSelectKey ]; - return ( - ( blockPatternsSelect - ? blockPatternsSelect( select ) === undefined - : false ) || - ( reusableBlocksSelect - ? reusableBlocksSelect( select ) === undefined - : false ) - ); - }, getAllPatternsDependants( select ) ) -); - const EMPTY_ARRAY = []; export const getReusableBlocks = createRegistrySelector( From 22c43ff9e687eb5752b790316f2b1eeb99976dbb Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 2 Dec 2024 10:40:27 +0000 Subject: [PATCH 18/52] Disable Zoom Out if no section root to allow for Theme opt in (#67232) * Disable toolbar and auto inserter behaviour if no section root * Remove useless coercion Co-authored-by: Ramon * Remove more coercion copy/paste Co-authored-by: Ramon * Add section root to Zoom Out e2e test * Add test coverage * Add some test coverage * Try e2e test fix by reverting all template part changes in Theme * Remove need to exit Zoom Out * Ensure a main tag * Update tests to expect the click-through behavior * Simplify selection --------- Co-authored-by: getdave Co-authored-by: talldan Co-authored-by: jeryj Co-authored-by: ramonjd Co-authored-by: draganescu --- .../src/components/inserter/menu.js | 9 ++- .../editor/src/components/header/index.js | 16 ++++- .../editor/various/parsing-patterns.spec.js | 3 +- .../editor/various/pattern-overrides.spec.js | 70 ++++++++++++++++--- test/e2e/specs/site-editor/zoom-out.spec.js | 45 +++++++++++- 5 files changed, 127 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 8dc2f64063c8e..ef260beb85906 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -58,6 +58,11 @@ function InserterMenu( ( select ) => unlock( select( blockEditorStore ) ).isZoomOut(), [] ); + const hasSectionRootClientId = useSelect( + ( select ) => + !! unlock( select( blockEditorStore ) ).getSectionRootClientId(), + [] + ); const [ filterValue, setFilterValue, delayedFilterValue ] = useDebouncedInput( __experimentalFilterValue ); const [ hoveredItem, setHoveredItem ] = useState( null ); @@ -81,7 +86,9 @@ function InserterMenu( const [ selectedTab, setSelectedTab ] = useState( getInitialTab() ); const shouldUseZoomOut = - selectedTab === 'patterns' || selectedTab === 'media'; + hasSectionRootClientId && + ( selectedTab === 'patterns' || selectedTab === 'media' ); + useZoomOut( shouldUseZoomOut && isLargeViewport ); const [ destinationRootClientId, onInsertBlocks, onToggleInsertionPoint ] = diff --git a/packages/editor/src/components/header/index.js b/packages/editor/src/components/header/index.js index 51c341f2c1bd1..79199b15b1ad1 100644 --- a/packages/editor/src/components/header/index.js +++ b/packages/editor/src/components/header/index.js @@ -30,6 +30,7 @@ import { PATTERN_POST_TYPE, NAVIGATION_POST_TYPE, } from '../../store/constants'; +import { unlock } from '../../lock-unlock'; const toolbarVariations = { distractionFreeDisabled: { y: '-50px' }, @@ -102,6 +103,13 @@ function Header( { ( hasFixedToolbar && ( ! hasBlockSelection || isBlockToolsCollapsed ) ) ); const hasBackButton = useHasBackButton(); + + const hasSectionRootClientId = useSelect( + ( select ) => + !! unlock( select( blockEditorStore ) ).getSectionRootClientId(), + [] + ); + /* * The edit-post-header classname is only kept for backward compatability * as some plugins might be relying on its presence. @@ -169,9 +177,11 @@ function Header( { forceIsAutosaveable={ forceIsDirty } /> - { canBeZoomedOut && isWideViewport && ( - - ) } + { canBeZoomedOut && + isWideViewport && + hasSectionRootClientId && ( + + ) } { ( isWideViewport || ! showIconLabels ) && ( diff --git a/test/e2e/specs/editor/various/parsing-patterns.spec.js b/test/e2e/specs/editor/various/parsing-patterns.spec.js index d8edc544ffa03..98261804acb58 100644 --- a/test/e2e/specs/editor/various/parsing-patterns.spec.js +++ b/test/e2e/specs/editor/various/parsing-patterns.spec.js @@ -37,9 +37,8 @@ test.describe( 'Parsing patterns', () => { } ); } ); - // Exit zoom out mode and select the inner buttons block to ensure + // Select the inner buttons block to ensure // the correct insertion point is selected. - await page.getByRole( 'button', { name: 'Zoom Out' } ).click(); await editor.selectBlocks( editor.canvas.locator( 'role=document[name="Block: Button"i]' ) ); diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 7069b4cec258a..7d2be84187ef6 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -268,12 +268,25 @@ test.describe( 'Pattern Overrides', () => { } ); await editor.setContent( '' ); + await editor.switchEditorTool( 'Design' ); + // Insert a `
` group block. + // In zoomed out and write mode it acts as the section root. + // Inside is a pattern that acts as a section. await editor.insertBlock( { - name: 'core/block', - attributes: { ref: id }, + name: 'core/group', + attributes: { tagName: 'main' }, + innerBlocks: [ + { + name: 'core/block', + attributes: { ref: id }, + }, + ], } ); + const groupBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Group', + } ); const patternBlock = editor.canvas.getByRole( 'document', { name: 'Block: Pattern', } ); @@ -290,14 +303,35 @@ test.describe( 'Pattern Overrides', () => { hasText: 'No Overrides or Binding', } ); - await test.step( 'Zoomed in / Design mode', async () => { - await editor.switchEditorTool( 'Design' ); - // In zoomed in and design mode the pattern block and child blocks - // with bindings are editable. + await test.step( 'Click-through behavior', async () => { + // With the group block selected, all the inner blocks of the pattern + // are inert due to the 'click-through' behavior, that requires the + // pattern block be selected first before its inner blocks are selectable. + await editor.selectBlocks( groupBlock ); await expect( patternBlock ).not.toHaveAttribute( 'inert', 'true' ); + await expect( blockWithOverrides ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithBindings ).toHaveAttribute( + 'inert', + 'true' + ); + await expect( blockWithoutOverridesOrBindings ).toHaveAttribute( + 'inert', + 'true' + ); + } ); + + await test.step( 'Zoomed in / Design mode', async () => { + await editor.selectBlocks( patternBlock ); + + // Once selected and in zoomed in/design mode the child blocks + // of the pattern with bindings are editable, but unbound + // blocks are inert. await expect( blockWithOverrides ).not.toHaveAttribute( 'inert', 'true' @@ -314,11 +348,16 @@ test.describe( 'Pattern Overrides', () => { await test.step( 'Zoomed in / Write mode - pattern as a section', async () => { await editor.switchEditorTool( 'Write' ); + // The pattern block is still editable as a section. await expect( patternBlock ).not.toHaveAttribute( 'inert', 'true' ); + + // Ensure the pattern block is selected. + await editor.selectBlocks( patternBlock ); + // Child blocks of the pattern with bindings are editable. await expect( blockWithOverrides ).not.toHaveAttribute( 'inert', @@ -336,11 +375,18 @@ test.describe( 'Pattern Overrides', () => { await test.step( 'Zoomed out / Write mode - pattern as a section', async () => { await page.getByLabel( 'Zoom Out' ).click(); - // In zoomed out only the pattern block is editable, as in this scenario it's a section. + // In zoomed out only the pattern block is editable, + // as in this scenario it's a section. await expect( patternBlock ).not.toHaveAttribute( 'inert', 'true' ); + + // Ensure the pattern block is selected before checking the child blocks + // to ensure the click-through behavior isn't interfering. + await editor.selectBlocks( patternBlock ); + + // None of the child blocks are editable in zoomed out mode. await expect( blockWithOverrides ).toHaveAttribute( 'inert', 'true' @@ -357,11 +403,17 @@ test.describe( 'Pattern Overrides', () => { await test.step( 'Zoomed out / Design mode - pattern as a section', async () => { await editor.switchEditorTool( 'Design' ); - // In zoomed out only the pattern block is editable, as in this scenario it's a section. + // In zoomed out only the pattern block is editable, + // as in this scenario it's a section. await expect( patternBlock ).not.toHaveAttribute( 'inert', 'true' ); + + // Ensure the pattern block is selected before checking the child blocks + // to ensure the click-through behavior isn't interfering. + await editor.selectBlocks( patternBlock ); + await expect( blockWithOverrides ).toHaveAttribute( 'inert', 'true' @@ -376,7 +428,7 @@ test.describe( 'Pattern Overrides', () => { ); } ); - // Zoom out and group the pattern. + // Zoom out and group the pattern so that it's no longer a section. await page.getByLabel( 'Zoom Out' ).click(); await editor.selectBlocks( patternBlock ); await editor.clickBlockOptionsMenuItem( 'Group' ); diff --git a/test/e2e/specs/site-editor/zoom-out.spec.js b/test/e2e/specs/site-editor/zoom-out.spec.js index e698a94b7cf0d..77d121e199939 100644 --- a/test/e2e/specs/site-editor/zoom-out.spec.js +++ b/test/e2e/specs/site-editor/zoom-out.spec.js @@ -4,7 +4,8 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); const EDITOR_ZOOM_OUT_CONTENT = ` - + +

First Section Start

@@ -58,6 +59,21 @@ const EDITOR_ZOOM_OUT_CONTENT = `

Fourth Section End

+
+`; + +const EDITOR_ZOOM_OUT_CONTENT_NO_SECTION_ROOT = ` +
+

First Section Start

+ + + +

First Section Center

+ + + +

First Section End

+
`; test.describe( 'Zoom Out', () => { @@ -67,6 +83,8 @@ test.describe( 'Zoom Out', () => { test.afterAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'twentytwentyone' ); + await requestUtils.deleteAllTemplates( 'wp_template' ); + await requestUtils.deleteAllTemplates( 'wp_template_part' ); } ); test.beforeEach( async ( { admin } ) => { @@ -215,4 +233,29 @@ test.describe( 'Zoom Out', () => { await expect( thirdSectionEnd ).toBeInViewport(); await expect( fourthSectionStart ).not.toBeInViewport(); } ); + + test( 'Zoom Out cannot be activated when the section root is missing', async ( { + page, + editor, + } ) => { + await editor.setContent( EDITOR_ZOOM_OUT_CONTENT_NO_SECTION_ROOT ); + + // Check that the Zoom Out toggle button is not visible. + await expect( + page.getByRole( 'button', { name: 'Zoom Out' } ) + ).toBeHidden(); + + // Check that activating the Patterns tab in the Inserter does not activate + // Zoom Out. + await page + .getByRole( 'button', { + name: 'Block Inserter', + exact: true, + } ) + .click(); + + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + + await expect( page.locator( '.is-zoomed-out' ) ).toBeHidden(); + } ); } ); From e92d57743b68753ee47f9c21645524e2a5b86ea4 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Mon, 2 Dec 2024 11:47:20 +0100 Subject: [PATCH 19/52] Remove one occurrence of incorrect usage of ItemGroup. (#67427) * Remove one occurrence of incorret usage of ItemGroup. * Fix toggling visibility of the help text. Co-authored-by: afercia Co-authored-by: gziolo Co-authored-by: Mamaduka Co-authored-by: SantosGuillamot --- packages/block-editor/src/hooks/block-bindings.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js index 615804a311c0f..e10696cc1257d 100644 --- a/packages/block-editor/src/hooks/block-bindings.js +++ b/packages/block-editor/src/hooks/block-bindings.js @@ -300,13 +300,17 @@ export const BlockBindingsPanel = ( { name: blockName, metadata } ) => { /> ) } - - + { /* + Use a div element to make the ToolsPanelHiddenInnerWrapper + toggle the visibility of this help text automatically. + */ } + +

{ __( 'Attributes connected to custom fields or other dynamic data.' ) } - - +

+
); From aef323a70e74fcf676036139bd25f657ad2a0b02 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:59:01 +0000 Subject: [PATCH 20/52] PR template: add before/after table (#62739) Co-authored-by: ellatrix Co-authored-by: t-hamano --- .github/PULL_REQUEST_TEMPLATE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fd63e5e2e5312..69fd34d709bdc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,3 +20,9 @@ https://github.com/WordPress/gutenberg/blob/trunk/CONTRIBUTING.md --> ## Screenshots or screencast + + + +|Before|After| +|-|-| +||| From 8d343d155c7577d46aea33e708dceb39c571cc80 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 2 Dec 2024 12:26:27 +0100 Subject: [PATCH 21/52] Site Editor: Fix focus mode navigation (#67458) Co-authored-by: youknowriad Co-authored-by: Mamaduka Co-authored-by: carolinan --- .../use-navigate-to-entity-record.js | 2 +- packages/router/src/router.tsx | 1 + .../template-part-focus-mode.spec.js | 50 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/e2e/specs/site-editor/template-part-focus-mode.spec.js diff --git a/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js b/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js index 8cc7fdaefe2d9..66be70fcd4e2e 100644 --- a/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js +++ b/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js @@ -17,7 +17,7 @@ export default function useNavigateToEntityRecord() { const onNavigateToEntityRecord = useCallback( ( params ) => { history.navigate( - `/${ params.postType }/${ params.id }?canvas=edit&focusMode=true` + `/${ params.postType }/${ params.postId }?canvas=edit&focusMode=true` ); }, [ history ] diff --git a/packages/router/src/router.tsx b/packages/router/src/router.tsx index 34cc542c7b573..2ac7974b4dfbc 100644 --- a/packages/router/src/router.tsx +++ b/packages/router/src/router.tsx @@ -146,6 +146,7 @@ export function useHistory() { return useMemo( () => ( { navigate, + back: history.back, } ), [ navigate ] ); diff --git a/test/e2e/specs/site-editor/template-part-focus-mode.spec.js b/test/e2e/specs/site-editor/template-part-focus-mode.spec.js new file mode 100644 index 0000000000000..29e6788779ed9 --- /dev/null +++ b/test/e2e/specs/site-editor/template-part-focus-mode.spec.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Template Part Focus mode', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyfour' ); + } ); + + test.afterEach( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyone' ); + } ); + + test( 'Should navigate to template part and back.', async ( { + admin, + page, + editor, + } ) => { + await admin.visitAdminPage( 'site-editor.php?canvas=edit' ); + await editor.setPreferences( 'core/edit-site', { + welcomeGuide: false, + } ); + + // Check that we're editing the template + await expect( page.locator( 'h1' ) ).toContainText( 'Blog Home' ); + await expect( page.locator( 'h1' ) ).toContainText( 'Template' ); + + // Click Template Part + await editor.canvas + .getByRole( 'document', { + name: 'Header', + } ) + .click(); + + // Navigate to Focus mode + await editor.clickBlockToolbarButton( 'Edit' ); + + // Check if focus mode is active + await expect( page.locator( 'h1' ) ).toContainText( 'Header' ); + await expect( page.locator( 'h1' ) ).toContainText( 'Template Part' ); + + // Go back + await page.getByRole( 'button', { name: 'Back' } ).click(); + + // Check that we're editing the template + await expect( page.locator( 'h1' ) ).toContainText( 'Blog Home' ); + await expect( page.locator( 'h1' ) ).toContainText( 'Template' ); + } ); +} ); From 141e9cd884053db9baec26e43297162841f9e7a7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 2 Dec 2024 12:51:58 +0100 Subject: [PATCH 22/52] Site editor: Allow access to quick edit (#67469) Co-authored-by: youknowriad Co-authored-by: jsnajdr --- packages/edit-site/src/components/site-editor-routes/pages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/site-editor-routes/pages.js b/packages/edit-site/src/components/site-editor-routes/pages.js index e8c55cd10307e..5f2a4e341e0dc 100644 --- a/packages/edit-site/src/components/site-editor-routes/pages.js +++ b/packages/edit-site/src/components/site-editor-routes/pages.js @@ -44,7 +44,7 @@ export const pagesRoute = { mobile: , edit( { query } ) { const hasQuickEdit = - ( query.layout ?? 'list' ) === 'list' && !! query.quickEdit; + ( query.layout ?? 'list' ) !== 'list' && !! query.quickEdit; return hasQuickEdit ? ( ) : undefined; @@ -59,7 +59,7 @@ export const pagesRoute = { }, edit( { query } ) { const hasQuickEdit = - ( query.layout ?? 'list' ) === 'list' && !! query.quickEdit; + ( query.layout ?? 'list' ) !== 'list' && !! query.quickEdit; return hasQuickEdit ? 380 : undefined; }, }, From e07fe5cd9c17702e242df8ab547bddd1c4d79f52 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:01:28 +0000 Subject: [PATCH 23/52] Preload: parse post ID from p (path) (#67465) Co-authored-by: ellatrix Co-authored-by: youknowriad --- backport-changelog/6.8/7695.md | 1 + lib/compat/wordpress-6.8/preload.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backport-changelog/6.8/7695.md b/backport-changelog/6.8/7695.md index 095c058e6fd10..f45b2039e3027 100644 --- a/backport-changelog/6.8/7695.md +++ b/backport-changelog/6.8/7695.md @@ -1,3 +1,4 @@ https://github.com/WordPress/wordpress-develop/pull/7695 * https://github.com/WordPress/gutenberg/pull/66631 +* https://github.com/WordPress/gutenberg/pull/67465 diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php index 6d92913b41411..494e3ad32ec02 100644 --- a/lib/compat/wordpress-6.8/preload.php +++ b/lib/compat/wordpress-6.8/preload.php @@ -10,8 +10,16 @@ */ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { if ( 'core/edit-site' === $context->name ) { - if ( ! empty( $_GET['postId'] ) ) { - $route_for_post = rest_get_route_for_post( $_GET['postId'] ); + $post_id = null; + if ( isset( $_GET['postId'] ) && is_numeric( $_GET['postId'] ) ) { + $post_id = (int) $_GET['postId']; + } + if ( isset( $_GET['p'] ) && preg_match( '/^\/page\/(\d+)$/', $_GET['p'], $matches ) ) { + $post_id = (int) $matches[1]; + } + + if ( $post_id ) { + $route_for_post = rest_get_route_for_post( $post_id ); if ( $route_for_post ) { $paths[] = add_query_arg( 'context', 'edit', $route_for_post ); } From 39a4d1c93fd3f9bee19db3566e92ce4a03e67544 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 2 Dec 2024 13:14:14 +0100 Subject: [PATCH 24/52] DataViews: Better handling of missing onClickItem prop (#67402) Co-authored-by: youknowriad Co-authored-by: jsnajdr --- .../src/components/dataviews-context/index.ts | 5 ++--- .../src/components/dataviews/index.tsx | 5 ++--- .../src/dataviews-layouts/grid/index.tsx | 14 +++++++------- .../src/dataviews-layouts/table/index.tsx | 16 ++++++++-------- .../utils/get-clickable-item-props.ts | 19 ++++++++++++------- packages/dataviews/src/types.ts | 2 +- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/dataviews/src/components/dataviews-context/index.ts b/packages/dataviews/src/components/dataviews-context/index.ts index 19f6b4178b7b5..4bef3ecdbcbb4 100644 --- a/packages/dataviews/src/components/dataviews-context/index.ts +++ b/packages/dataviews/src/components/dataviews-context/index.ts @@ -26,7 +26,7 @@ type DataViewsContextType< Item > = { openedFilter: string | null; setOpenedFilter: ( openedFilter: string | null ) => void; getItemId: ( item: Item ) => string; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; isItemClickable: ( item: Item ) => boolean; }; @@ -44,8 +44,7 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( { setOpenedFilter: () => {}, openedFilter: null, getItemId: ( item ) => item.id, - onClickItem: () => {}, - isItemClickable: () => false, + isItemClickable: () => true, } ); export default DataViewsContext; diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx index ee6073f40bf3d..99d9b6d684b08 100644 --- a/packages/dataviews/src/components/dataviews/index.tsx +++ b/packages/dataviews/src/components/dataviews/index.tsx @@ -52,8 +52,7 @@ type DataViewsProps< Item > = { : { getItemId: ( item: Item ) => string } ); const defaultGetItemId = ( item: ItemWithId ) => item.id; -const defaultIsItemClickable = () => false; -const defaultOnClickItem = () => {}; +const defaultIsItemClickable = () => true; const EMPTY_ARRAY: any[] = []; export default function DataViews< Item >( { @@ -70,7 +69,7 @@ export default function DataViews< Item >( { defaultLayouts, selection: selectionProperty, onChangeSelection, - onClickItem = defaultOnClickItem, + onClickItem, isItemClickable = defaultIsItemClickable, header, }: DataViewsProps< Item > ) { diff --git a/packages/dataviews/src/dataviews-layouts/grid/index.tsx b/packages/dataviews/src/dataviews-layouts/grid/index.tsx index 2a09fb68efab8..17053e01604a5 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/grid/index.tsx @@ -31,7 +31,7 @@ interface GridItemProps< Item > { selection: string[]; onChangeSelection: SetSelection; getItemId: ( item: Item ) => string; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; isItemClickable: ( item: Item ) => boolean; item: Item; actions: Action< Item >[]; @@ -66,19 +66,19 @@ function GridItem< Item >( { ) : null; - const clickableMediaItemProps = getClickableItemProps( + const clickableMediaItemProps = getClickableItemProps( { item, isItemClickable, onClickItem, - 'dataviews-view-grid__media' - ); + className: 'dataviews-view-grid__media', + } ); - const clickablePrimaryItemProps = getClickableItemProps( + const clickablePrimaryItemProps = getClickableItemProps( { item, isItemClickable, onClickItem, - 'dataviews-view-grid__primary-field' - ); + className: 'dataviews-view-grid__primary-field', + } ); return ( { field: NormalizedField< Item >; item: Item; isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; } interface TableColumnCombinedProps< Item > { @@ -52,7 +52,7 @@ interface TableColumnCombinedProps< Item > { item: Item; view: ViewTableType; isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; } interface TableColumnProps< Item > { @@ -62,7 +62,7 @@ interface TableColumnProps< Item > { column: string; view: ViewTableType; isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; } interface TableRowProps< Item > { @@ -77,7 +77,7 @@ interface TableRowProps< Item > { getItemId: ( item: Item ) => string; onChangeSelection: SetSelection; isItemClickable: ( item: Item ) => boolean; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; } function TableColumn< Item >( { @@ -118,12 +118,12 @@ function TableColumnField< Item >( { const isItemClickableField = ( i: Item ) => isItemClickable( i ) && isPrimaryField; - const clickableProps = getClickableItemProps( + const clickableProps = getClickableItemProps( { item, - isItemClickableField, + isItemClickable: isItemClickableField, onClickItem, - 'dataviews-view-table__cell-content' - ); + className: 'dataviews-view-table__cell-content', + } ); return (
( - item: Item, - isItemClickable: ( item: Item ) => boolean, - onClickItem: ( item: Item ) => void, - className: string -) { - if ( ! isItemClickable( item ) ) { +export default function getClickableItemProps< Item >( { + item, + isItemClickable, + onClickItem, + className, +}: { + item: Item; + isItemClickable: ( item: Item ) => boolean; + onClickItem?: ( item: Item ) => void; + className: string; +} ) { + if ( ! isItemClickable( item ) || ! onClickItem ) { return { className }; } diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 861dc53404f91..0bce8b8cf2c08 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -510,7 +510,7 @@ export interface ViewBaseProps< Item > { onChangeSelection: SetSelection; selection: string[]; setOpenedFilter: ( fieldId: string ) => void; - onClickItem: ( item: Item ) => void; + onClickItem?: ( item: Item ) => void; isItemClickable: ( item: Item ) => boolean; view: View; } From 340d617fa5cdab9b9402960de32c6496ef563d1d Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:31:54 +0100 Subject: [PATCH 25/52] [mini] drag and drop: fix misplaced drop indicator (#67434) Co-authored-by: ellatrix Co-authored-by: youknowriad Co-authored-by: Mamaduka Co-authored-by: ntsekouras --- .../block-editor/src/components/block-popover/inbetween.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-editor/src/components/block-popover/inbetween.js b/packages/block-editor/src/components/block-popover/inbetween.js index 2ed9ee0bcb284..1d7c176673240 100644 --- a/packages/block-editor/src/components/block-popover/inbetween.js +++ b/packages/block-editor/src/components/block-popover/inbetween.js @@ -148,6 +148,10 @@ function BlockPopoverInbetween( { ? nextRect.left - previousRect.right : 0; } + + // Avoid a negative width which happens when the next rect + // is on the next line. + width = Math.max( width, 0 ); } return new window.DOMRect( left, top, width, height ); From c517e410017f4d65a7c6f03a31c5c2fa15cbbd65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 2 Dec 2024 13:32:53 +0100 Subject: [PATCH 26/52] Extensibility: Make Block Bindings work with `editor.BlockEdit` hook (#67370) * Block Bindings: Move the place when the attributes get overriden * Fix failing unit tests * Wrap Edit with bindings logic only when the block supports it * Extend the test plugin with `editor.BlockEdit` filter * Add a new test covering the extensibility inside inspector controls * Fix the issue with missing context established by sources Co-authored-by: gziolo Co-authored-by: SantosGuillamot --- .../src/components/block-edit/edit.js | 12 +- .../with-block-bindings-support.js} | 103 ++++-------------- .../block-list/use-block-props/index.js | 2 +- .../src/components/rich-text/index.js | 2 +- .../block-editor/src/hooks/block-bindings.js | 10 +- packages/block-editor/src/hooks/index.js | 1 - .../block-editor/src/utils/block-bindings.js | 37 +++++++ packages/e2e-tests/plugins/block-bindings.php | 6 +- .../e2e-tests/plugins/block-bindings/index.js | 45 ++++++++ .../various/block-bindings/post-meta.spec.js | 41 +++++++ 10 files changed, 165 insertions(+), 94 deletions(-) rename packages/block-editor/src/{hooks/use-bindings-attributes.js => components/block-edit/with-block-bindings-support.js} (73%) diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 83d0e3f406f82..6b1ddd86f4c76 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -18,6 +18,8 @@ import { useContext, useMemo } from '@wordpress/element'; * Internal dependencies */ import BlockContext from '../block-context'; +import { withBlockBindingsSupport } from './with-block-bindings-support'; +import { canBindBlock } from '../../utils/block-bindings'; /** * Default value used for blocks which do not define their own context needs, @@ -47,6 +49,8 @@ const Edit = ( props ) => { const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit ); +const EditWithFiltersAndBindings = withBlockBindingsSupport( EditWithFilters ); + const EditWithGeneratedProps = ( props ) => { const { attributes = {}, name } = props; const blockType = getBlockType( name ); @@ -67,8 +71,12 @@ const EditWithGeneratedProps = ( props ) => { return null; } + const EditComponent = canBindBlock( name ) + ? EditWithFiltersAndBindings + : EditWithFilters; + if ( blockType.apiVersion > 1 ) { - return ; + return ; } // Generate a class name for the block's editable form. @@ -82,7 +90,7 @@ const EditWithGeneratedProps = ( props ) => { ); return ( - ( props ) => { const registry = useRegistry(); const blockContext = useContext( BlockContext ); @@ -108,9 +72,9 @@ export const withBlockBindingSupport = createHigherOrderComponent( () => replacePatternOverrideDefaultBindings( name, - props.attributes.metadata?.bindings + props.attributes?.metadata?.bindings ), - [ props.attributes.metadata?.bindings, name ] + [ props.attributes?.metadata?.bindings, name ] ); // While this hook doesn't directly call any selectors, `useSelect` is @@ -196,7 +160,7 @@ export const withBlockBindingSupport = createHigherOrderComponent( const hasParentPattern = !! updatedContext[ 'pattern/overrides' ]; const hasPatternOverridesDefaultBinding = - props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ] + props.attributes?.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ] ?.source === 'core/pattern-overrides'; const _setAttributes = useCallback( @@ -283,40 +247,13 @@ export const withBlockBindingSupport = createHigherOrderComponent( ); return ( - <> - - + ); }, 'withBlockBindingSupport' ); - -/** - * Filters a registered block's settings to enhance a block's `edit` component - * to upgrade bound attributes. - * - * @param {WPBlockSettings} settings - Registered block settings. - * @param {string} name - Block name. - * @return {WPBlockSettings} Filtered block settings. - */ -function shimAttributeSource( settings, name ) { - if ( ! canBindBlock( name ) ) { - return settings; - } - - return { - ...settings, - edit: withBlockBindingSupport( settings.edit ), - }; -} - -addFilter( - 'blocks.registerBlockType', - 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', - shimAttributeSource -); diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 4696149dc3875..7e50b75e1b956 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -29,7 +29,7 @@ import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; import { useScrollIntoView } from './use-scroll-into-view'; import { useFlashEditableBlocks } from '../../use-flash-editable-blocks'; -import { canBindBlock } from '../../../hooks/use-bindings-attributes'; +import { canBindBlock } from '../../../utils/block-bindings'; import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility'; /** diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index bc8eca6ea94d0..768ffbb0cdd2d 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -39,7 +39,7 @@ import FormatEdit from './format-edit'; import { getAllowedFormats } from './utils'; import { Content, valueToHTMLString } from './content'; import { withDeprecations } from './with-deprecations'; -import { canBindBlock } from '../../hooks/use-bindings-attributes'; +import { canBindBlock } from '../../utils/block-bindings'; import BlockContext from '../block-context'; export const keyboardShortcutContext = createContext(); diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js index e10696cc1257d..cec80dffaeaa1 100644 --- a/packages/block-editor/src/hooks/block-bindings.js +++ b/packages/block-editor/src/hooks/block-bindings.js @@ -23,15 +23,15 @@ import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies */ -import { - canBindAttribute, - getBindableAttributes, -} from '../hooks/use-bindings-attributes'; import { unlock } from '../lock-unlock'; import InspectorControls from '../components/inspector-controls'; import BlockContext from '../components/block-context'; import { useBlockEditContext } from '../components/block-edit'; -import { useBlockBindingsUtils } from '../utils/block-bindings'; +import { + canBindAttribute, + getBindableAttributes, + useBlockBindingsUtils, +} from '../utils/block-bindings'; import { store as blockEditorStore } from '../store'; const { Menu } = unlock( componentsPrivateApis ); diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 66ff60b691b66..7f9b29376ad1f 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -32,7 +32,6 @@ import './metadata'; import blockHooks from './block-hooks'; import blockBindingsPanel from './block-bindings'; import './block-renaming'; -import './use-bindings-attributes'; import './grid-visualizer'; createBlockEditFilter( diff --git a/packages/block-editor/src/utils/block-bindings.js b/packages/block-editor/src/utils/block-bindings.js index dcf80d985473b..82f0dff53531a 100644 --- a/packages/block-editor/src/utils/block-bindings.js +++ b/packages/block-editor/src/utils/block-bindings.js @@ -13,6 +13,43 @@ function isObjectEmpty( object ) { return ! object || Object.keys( object ).length === 0; } +export const BLOCK_BINDINGS_ALLOWED_BLOCKS = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'id', 'url', 'title', 'alt' ], + 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], +}; + +/** + * Based on the given block name, + * check if it is possible to bind the block. + * + * @param {string} blockName - The block name. + * @return {boolean} Whether it is possible to bind the block to sources. + */ +export function canBindBlock( blockName ) { + return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; +} + +/** + * Based on the given block name and attribute name, + * check if it is possible to bind the block attribute. + * + * @param {string} blockName - The block name. + * @param {string} attributeName - The attribute name. + * @return {boolean} Whether it is possible to bind the block attribute. + */ +export function canBindAttribute( blockName, attributeName ) { + return ( + canBindBlock( blockName ) && + BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName ) + ); +} + +export function getBindableAttributes( blockName ) { + return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; +} + /** * Contains utils to update the block `bindings` metadata. * diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php index b86673c2c523d..1fd6d8468c77d 100644 --- a/packages/e2e-tests/plugins/block-bindings.php +++ b/packages/e2e-tests/plugins/block-bindings.php @@ -41,7 +41,11 @@ function gutenberg_test_block_bindings_registration() { plugins_url( 'block-bindings/index.js', __FILE__ ), array( 'wp-blocks', - 'wp-private-apis', + 'wp-block-editor', + 'wp-components', + 'wp-compose', + 'wp-element', + 'wp-hooks', ), filemtime( plugin_dir_path( __FILE__ ) . 'block-bindings/index.js' ), true diff --git a/packages/e2e-tests/plugins/block-bindings/index.js b/packages/e2e-tests/plugins/block-bindings/index.js index 5c364257caed1..63c463e197fa8 100644 --- a/packages/e2e-tests/plugins/block-bindings/index.js +++ b/packages/e2e-tests/plugins/block-bindings/index.js @@ -1,4 +1,9 @@ const { registerBlockBindingsSource } = wp.blocks; +const { InspectorControls } = wp.blockEditor; +const { PanelBody, TextControl } = wp.components; +const { createHigherOrderComponent } = wp.compose; +const { createElement: el, Fragment } = wp.element; +const { addFilter } = wp.hooks; const { fieldsList } = window.testingBindings || {}; const getValues = ( { bindings } ) => { @@ -46,3 +51,43 @@ registerBlockBindingsSource( { getValues, canUserEditValue: () => true, } ); + +const withBlockBindingsInspectorControl = createHigherOrderComponent( + ( BlockEdit ) => { + return ( props ) => { + if ( ! props.attributes?.metadata?.bindings?.content ) { + return el( BlockEdit, props ); + } + + return el( + Fragment, + {}, + el( BlockEdit, props ), + el( + InspectorControls, + {}, + el( + PanelBody, + { title: 'Bindings' }, + el( TextControl, { + __next40pxDefaultSize: true, + __nextHasNoMarginBottom: true, + label: 'Content', + value: props.attributes.content, + onChange: ( content ) => + props.setAttributes( { + content, + } ), + } ) + ) + ) + ); + }; + } +); + +addFilter( + 'editor.BlockEdit', + 'testing/bindings-inspector-control', + withBlockBindingsInspectorControl +); diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js index 32334bfc777f2..318707e22f098 100644 --- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js +++ b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js @@ -524,6 +524,47 @@ test.describe( 'Post Meta source', () => { previewPage.locator( '#connected-paragraph' ) ).toHaveText( 'new value' ); } ); + + test( 'should be possible to edit the value of the connected custom fields in the inspector control registered by the plugin', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'connected-paragraph', + content: 'fallback content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'movie_field', + }, + }, + }, + }, + }, + } ); + const contentInput = page.getByRole( 'textbox', { + name: 'Content', + } ); + await expect( contentInput ).toHaveValue( + 'Movie field default value' + ); + await contentInput.fill( 'new value' ); + // Check that the paragraph content attribute didn't change. + const [ paragraphBlockObject ] = await editor.getBlocks(); + expect( paragraphBlockObject.attributes.content ).toBe( + 'fallback content' + ); + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#connected-paragraph' ) + ).toHaveText( 'new value' ); + } ); + test( 'should be possible to connect movie fields through the attributes panel', async ( { editor, page, From d3f344fe6193d7cd86e03e7a00770032e7919ab4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 2 Dec 2024 13:17:45 +0000 Subject: [PATCH 27/52] Prefer exact matches in Link Search results sorting (#67367) * Weight towards exact matches * Add additional test coverage Co-authored-by: getdave Co-authored-by: draganescu Co-authored-by: talldan Co-authored-by: jasmussen Co-authored-by: kevin940726 Co-authored-by: ironprogrammer Co-authored-by: annezazu --- .../__experimental-fetch-link-suggestions.ts | 25 +++++++++++-- .../__experimental-fetch-link-suggestions.js | 37 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts index e1a166ee272db..2901219758903 100644 --- a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts +++ b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts @@ -270,12 +270,29 @@ export function sortResults( results: SearchResult[], search: string ) { for ( const result of results ) { if ( result.title ) { const titleTokens = tokenize( result.title ); - const matchingTokens = titleTokens.filter( ( titleToken ) => - searchTokens.some( ( searchToken ) => - titleToken.includes( searchToken ) + const exactMatchingTokens = titleTokens.filter( ( titleToken ) => + searchTokens.some( + ( searchToken ) => titleToken === searchToken ) ); - scores[ result.id ] = matchingTokens.length / titleTokens.length; + const subMatchingTokens = titleTokens.filter( ( titleToken ) => + searchTokens.some( + ( searchToken ) => + titleToken !== searchToken && + titleToken.includes( searchToken ) + ) + ); + + // The score is a combination of exact matches and sub-matches. + // More weight is given to exact matches, as they are more relevant (e.g. "cat" vs "caterpillar"). + // Diving by the total number of tokens in the title normalizes the score and skews + // the results towards shorter titles. + const exactMatchScore = + ( exactMatchingTokens.length / titleTokens.length ) * 10; + + const subMatchScore = subMatchingTokens.length / titleTokens.length; + + scores[ result.id ] = exactMatchScore + subMatchScore; } else { scores[ result.id ] = 0; } diff --git a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js index 6878c74332c3d..ad0014ff86ecb 100644 --- a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js +++ b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js @@ -393,6 +393,43 @@ describe( 'sortResults', () => { 6, ] ); } ); + + it( 'orders results to prefer direct matches over sub matches', () => { + const results = [ + { + id: 1, + title: 'News', + url: 'http://wordpress.local/news/', + type: 'page', + kind: 'post-type', + }, + { + id: 2, + title: 'Newspaper', + url: 'http://wordpress.local/newspaper/', + type: 'page', + kind: 'post-type', + }, + { + id: 3, + title: 'News Flash News', + url: 'http://wordpress.local/news-flash-news/', + type: 'page', + kind: 'post-type', + }, + { + id: 4, + title: 'News', + url: 'http://wordpress.local/news-2/', + type: 'page', + kind: 'post-type', + }, + ]; + const order = sortResults( results, 'News' ).map( + ( result ) => result.id + ); + expect( order ).toEqual( [ 1, 4, 3, 2 ] ); + } ); } ); describe( 'tokenize', () => { From cd26001761b5f93143671ce1505b25732c51692e Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Mon, 2 Dec 2024 05:51:31 -0800 Subject: [PATCH 28/52] =?UTF-8?q?Fix=20Meta=20boxes=20saving=20when=20they?= =?UTF-8?q?=E2=80=99re=20not=20present=20(#67254)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initialize meta boxes whether or not they’re visible * Add hook for initialization of meta boxes * Minimize hook for meta boxes initialization * Name the export Co-authored-by: stokesman Co-authored-by: afercia Co-authored-by: t-hamano Co-authored-by: Mamaduka Co-authored-by: enricobattocchi --- .../edit-post/src/components/layout/index.js | 13 ++++--- .../src/components/meta-boxes/index.js | 37 ++----------------- .../meta-boxes/use-meta-box-initialization.js | 32 ++++++++++++++++ 3 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 5dcbfa2c82cea..b8061571ec66c 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -74,6 +74,7 @@ import useEditPostCommands from '../../commands/use-commands'; import { usePaddingAppender } from './use-padding-appender'; import { useShouldIframe } from './use-should-iframe'; import useNavigateToEntityRecord from '../../hooks/use-navigate-to-entity-record'; +import { useMetaBoxInitialization } from '../meta-boxes/use-meta-box-initialization'; const { getLayoutStyles } = unlock( blockEditorPrivateApis ); const { useCommands } = unlock( coreCommandsPrivateApis ); @@ -413,6 +414,8 @@ function Layout( { const { isZoomOut } = unlock( select( blockEditorStore ) ); const { getEditorMode, getRenderingMode } = select( editorStore ); const isRenderingPostOnly = getRenderingMode() === 'post-only'; + const isNotDesignPostType = + ! DESIGN_POST_TYPES.includes( currentPostType ); return { mode: getEditorMode(), @@ -423,9 +426,7 @@ function Layout( { !! select( blockEditorStore ).getBlockSelectionStart(), showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), - showMetaBoxes: - ! DESIGN_POST_TYPES.includes( currentPostType ) && - ! isZoomOut(), + showMetaBoxes: isNotDesignPostType && ! isZoomOut(), isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ), templateId: supportsTemplateMode && @@ -435,9 +436,7 @@ function Layout( { ? getTemplateId( currentPostType, currentPostId ) : null, enablePaddingAppender: - ! isZoomOut() && - isRenderingPostOnly && - ! DESIGN_POST_TYPES.includes( currentPostType ), + ! isZoomOut() && isRenderingPostOnly && isNotDesignPostType, }; }, [ @@ -447,6 +446,8 @@ function Layout( { settings.supportsTemplateMode, ] ); + useMetaBoxInitialization( hasActiveMetaboxes ); + const [ paddingAppenderRef, paddingStyle ] = usePaddingAppender( enablePaddingAppender ); diff --git a/packages/edit-post/src/components/meta-boxes/index.js b/packages/edit-post/src/components/meta-boxes/index.js index 14728c97cf6b6..fdc74a5df4ce9 100644 --- a/packages/edit-post/src/components/meta-boxes/index.js +++ b/packages/edit-post/src/components/meta-boxes/index.js @@ -1,9 +1,7 @@ /** * WordPress dependencies */ -import { useSelect, useRegistry } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; -import { store as editorStore } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -13,38 +11,11 @@ import MetaBoxVisibility from './meta-box-visibility'; import { store as editPostStore } from '../../store'; export default function MetaBoxes( { location } ) { - const registry = useRegistry(); - const { metaBoxes, areMetaBoxesInitialized, isEditorReady } = useSelect( - ( select ) => { - const { __unstableIsEditorReady } = select( editorStore ); - const { - getMetaBoxesPerLocation, - areMetaBoxesInitialized: _areMetaBoxesInitialized, - } = select( editPostStore ); - return { - metaBoxes: getMetaBoxesPerLocation( location ), - areMetaBoxesInitialized: _areMetaBoxesInitialized(), - isEditorReady: __unstableIsEditorReady(), - }; - }, - [ location ] + const metaBoxes = useSelect( + ( select ) => + select( editPostStore ).getMetaBoxesPerLocation[ location ] ); - const hasMetaBoxes = !! metaBoxes?.length; - - // When editor is ready, initialize postboxes (wp core script) and metabox - // saving. This initializes all meta box locations, not just this specific - // one. - useEffect( () => { - if ( isEditorReady && hasMetaBoxes && ! areMetaBoxesInitialized ) { - registry.dispatch( editPostStore ).initializeMetaBoxes(); - } - }, [ isEditorReady, hasMetaBoxes, areMetaBoxesInitialized ] ); - - if ( ! areMetaBoxesInitialized ) { - return null; - } - return ( <> { ( metaBoxes ?? [] ).map( ( { id } ) => ( diff --git a/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js b/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js new file mode 100644 index 0000000000000..4309d85e3c22b --- /dev/null +++ b/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { store as editPostStore } from '../../store'; + +/** + * Initializes WordPress `postboxes` script and the logic for saving meta boxes. + * + * @param { boolean } enabled + */ +export const useMetaBoxInitialization = ( enabled ) => { + const isEnabledAndEditorReady = useSelect( + ( select ) => + enabled && select( editorStore ).__unstableIsEditorReady(), + [ enabled ] + ); + const { initializeMetaBoxes } = useDispatch( editPostStore ); + // The effect has to rerun when the editor is ready because initializeMetaBoxes + // will noop until then. + useEffect( () => { + if ( isEnabledAndEditorReady ) { + initializeMetaBoxes(); + } + }, [ isEnabledAndEditorReady, initializeMetaBoxes ] ); +}; From d0c372c881cb7d68bf56703df58f057614817c02 Mon Sep 17 00:00:00 2001 From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:06:19 +0100 Subject: [PATCH 29/52] Fix write mode persisting after disabling the experiment Co-authored-by: SantosGuillamot Co-authored-by: getdave --- packages/block-editor/src/store/selectors.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 75c43770f7e17..dc90f35173252 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -3041,9 +3041,7 @@ export const getBlockEditingMode = createRegistrySelector( clientId = ''; } - const isNavMode = - select( preferencesStore )?.get( 'core', 'editorTool' ) === - 'navigation'; + const isNavMode = isNavigationMode( state ); // If the editor is currently not in navigation mode, check if the clientId // has an editing mode set in the regular derived map. From 65fa4f3b0273afd7a7b578614a6e9ffd80d05de5 Mon Sep 17 00:00:00 2001 From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:11:50 +0530 Subject: [PATCH 30/52] NumberControl: Deprecate 36px default size (#66730) * Add the deprecation for 36px default size to number control * Add the changelog for the deprecation * Update unit test and supress warning for 40px default size warning from child component * Add the deperection changelog to unreleased section and updated the component to use __next40pxDefaultSize for NumberControl * Refactor the test for NumberControl component to reduce changes * Update box control files to use supress warning prop before default 40px prop * Supress the console warning for deprecation message from child component * Addressed the feedbacks on the PR and updated the component based on that --------- Co-authored-by: hbhalodia Co-authored-by: mirka <0mirka00@git.wordpress.org> --- .../src/components/line-height-control/index.js | 1 + packages/components/CHANGELOG.md | 1 + packages/components/src/angle-picker-control/index.tsx | 2 +- .../components/src/color-picker/input-with-slider.tsx | 2 +- packages/components/src/number-control/README.md | 3 ++- packages/components/src/number-control/index.tsx | 9 +++++++++ .../src/number-control/stories/index.story.tsx | 1 + packages/components/src/number-control/test/index.tsx | 6 +++++- packages/components/src/number-control/types.ts | 7 +++++++ packages/components/src/range-control/index.tsx | 1 + packages/components/src/unit-control/index.tsx | 1 + 11 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/line-height-control/index.js b/packages/block-editor/src/components/line-height-control/index.js index b2c99c03f8784..e6af602c2875a 100644 --- a/packages/block-editor/src/components/line-height-control/index.js +++ b/packages/block-editor/src/components/line-height-control/index.js @@ -93,6 +93,7 @@ const LineHeightControl = ( {
} spinControls="none" - size="__unstable-large" /> { return ( {}; @@ -53,9 +54,17 @@ function UnforwardedNumberControl( size = 'default', suffix, onChange = noop, + __shouldNotWarnDeprecated36pxSize, ...restProps } = useDeprecated36pxDefaultSizeProp< NumberControlProps >( props ); + maybeWarnDeprecated36pxSize( { + componentName: 'NumberControl', + size, + __next40pxDefaultSize: restProps.__next40pxDefaultSize, + __shouldNotWarnDeprecated36pxSize, + } ); + if ( hideHTMLArrows ) { deprecated( 'wp.components.NumberControl hideHTMLArrows prop ', { alternative: 'spinControls="none"', diff --git a/packages/components/src/number-control/stories/index.story.tsx b/packages/components/src/number-control/stories/index.story.tsx index 3feb0d63eadc2..8710839fea6ea 100644 --- a/packages/components/src/number-control/stories/index.story.tsx +++ b/packages/components/src/number-control/stories/index.story.tsx @@ -62,4 +62,5 @@ const Template: StoryFn< typeof NumberControl > = ( { export const Default = Template.bind( {} ); Default.args = { label: 'Value', + __next40pxDefaultSize: true, }; diff --git a/packages/components/src/number-control/test/index.tsx b/packages/components/src/number-control/test/index.tsx index 3cf3368f1636b..bf97b520673ea 100644 --- a/packages/components/src/number-control/test/index.tsx +++ b/packages/components/src/number-control/test/index.tsx @@ -12,9 +12,13 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import NumberControl from '..'; +import _NumberControl from '..'; import type { NumberControlProps } from '../types'; +const NumberControl = ( + props: React.ComponentProps< typeof _NumberControl > +) => <_NumberControl __next40pxDefaultSize { ...props } />; + function StatefulNumberControl( props: NumberControlProps ) { const [ value, setValue ] = useState( props.value ); const handleOnChange = ( v: string | undefined ) => setValue( v ); diff --git a/packages/components/src/number-control/types.ts b/packages/components/src/number-control/types.ts index 8d198e777bd55..2a0fbf402d356 100644 --- a/packages/components/src/number-control/types.ts +++ b/packages/components/src/number-control/types.ts @@ -91,4 +91,11 @@ export type NumberControlProps = Omit< * The value of the input. */ value?: number | string; + /** + * Do not throw a warning for the deprecated 36px default size. + * For internal components of other components that already throw the warning. + * + * @ignore + */ + __shouldNotWarnDeprecated36pxSize?: boolean; }; diff --git a/packages/components/src/range-control/index.tsx b/packages/components/src/range-control/index.tsx index 916571c3ee0e0..89dd8248a1e61 100644 --- a/packages/components/src/range-control/index.tsx +++ b/packages/components/src/range-control/index.tsx @@ -350,6 +350,7 @@ function UnforwardedRangeControl( step={ step } // @ts-expect-error TODO: Investigate if the `null` value is necessary value={ inputSliderValue } + __shouldNotWarnDeprecated36pxSize /> ) } { allowReset && ( diff --git a/packages/components/src/unit-control/index.tsx b/packages/components/src/unit-control/index.tsx index 9845c4eb04ef2..65e1e56cda3b3 100644 --- a/packages/components/src/unit-control/index.tsx +++ b/packages/components/src/unit-control/index.tsx @@ -224,6 +224,7 @@ function UnforwardedUnitControl( return ( Date: Mon, 2 Dec 2024 16:07:57 +0100 Subject: [PATCH 31/52] useEditorTitle: fix wrong request without ID (#67475) Co-authored-by: ellatrix Co-authored-by: youknowriad --- .../src/components/editor/use-editor-title.js | 4 ++ test/e2e/specs/site-editor/preload.spec.js | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 test/e2e/specs/site-editor/preload.spec.js diff --git a/packages/edit-site/src/components/editor/use-editor-title.js b/packages/edit-site/src/components/editor/use-editor-title.js index 727b190117e02..6f0b36f8e3b8b 100644 --- a/packages/edit-site/src/components/editor/use-editor-title.js +++ b/packages/edit-site/src/components/editor/use-editor-title.js @@ -22,6 +22,10 @@ function useEditorTitle( postType, postId ) { const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); + if ( ! postId ) { + return { isLoaded: false }; + } + const _record = getEditedEntityRecord( 'postType', postType, diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js new file mode 100644 index 0000000000000..1e93f783a8a91 --- /dev/null +++ b/test/e2e/specs/site-editor/preload.spec.js @@ -0,0 +1,41 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Preload', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyone' ); + } ); + + test( 'Should make no requests before the iframe is loaded', async ( { + page, + admin, + } ) => { + // Do not use `visitSiteEditor` because it waits for the iframe to load. + await admin.visitAdminPage( 'site-editor.php' ); + + const requests = []; + let isLoaded = false; + + page.on( 'request', ( request ) => { + if ( request.resourceType() === 'document' ) { + // The iframe also "requests" a blob document. This is the most + // reliable way to wait for the iframe to start loading. + // `waitForSelector` is always a bit delayed, and we don't want + // to detect requests after the iframe mounts. + isLoaded = true; + } else if ( ! isLoaded && request.resourceType() === 'fetch' ) { + requests.push( request.url() ); + } + } ); + + await page.waitForFunction( ( _isLoaded ) => _isLoaded, [ isLoaded ] ); + + expect( requests ).toEqual( [] ); + } ); +} ); From 6689c778e6bbb9774dd4487c7f1c29e1ffd207f5 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 2 Dec 2024 17:02:52 +0100 Subject: [PATCH 32/52] SlotFill: remove explicit rerender from portal version (#67471) * SlotFill: remove explicit rerender from portal version * Add changelog entry Co-authored-by: jsnajdr Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 1 + .../src/slot-fill/bubbles-virtually/fill.tsx | 18 +++++++----------- .../bubbles-virtually/slot-fill-provider.tsx | 15 ++------------- .../src/slot-fill/bubbles-virtually/slot.tsx | 11 ++++------- packages/components/src/slot-fill/types.ts | 7 ++++--- 5 files changed, 18 insertions(+), 34 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 748525d6d9c15..94dfd1b3c3811 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -22,6 +22,7 @@ - Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)). - Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)). - Exported the `WPCompleter` type as it was being used in block-editor/autocompleters ([#67410](https://github.com/WordPress/gutenberg/pull/67410)). +- `SlotFill`: remove manual rerenders from the portal `Fill` component ([#67471](https://github.com/WordPress/gutenberg/pull/67471)). ### Bug Fixes diff --git a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx index d5287adfab417..ef7bc94ff540b 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx @@ -4,7 +4,6 @@ import { useObservableValue } from '@wordpress/compose'; import { useContext, - useReducer, useRef, useEffect, createPortal, @@ -20,18 +19,15 @@ import type { FillComponentProps } from '../types'; export default function Fill( { name, children }: FillComponentProps ) { const registry = useContext( SlotFillContext ); const slot = useObservableValue( registry.slots, name ); - const [ , rerender ] = useReducer( () => [], [] ); - const ref = useRef( { rerender } ); + const instanceRef = useRef( {} ); + // We register fills so we can keep track of their existence. + // Slots can use the `useSlotFills` hook to know if there're already fills + // registered so they can choose to render themselves or not. useEffect( () => { - // We register fills so we can keep track of their existence. - // Some Slot implementations need to know if there're already fills - // registered so they can choose to render themselves or not. - const refValue = ref.current; - registry.registerFill( name, refValue ); - return () => { - registry.unregisterFill( name, refValue ); - }; + const instance = instanceRef.current; + registry.registerFill( name, instance ); + return () => registry.unregisterFill( name, instance ); }, [ registry, name ] ); if ( ! slot || ! slot.ref.current ) { diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx index 1dc5ef35ceccf..cf692700eef79 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx @@ -23,13 +23,7 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext { ref, fillProps ) => { - const slot = slots.get( name ); - - slots.set( name, { - ...slot, - ref: ref || slot?.ref, - fillProps: fillProps || slot?.fillProps || {}, - } ); + slots.set( name, { ref, fillProps } ); }; const unregisterSlot: SlotFillBubblesVirtuallyContext[ 'unregisterSlot' ] = @@ -66,12 +60,7 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext { return; } - slot.fillProps = fillProps; - const slotFills = fills.get( name ); - if ( slotFills ) { - // Force update fills. - slotFills.forEach( ( fill ) => fill.rerender() ); - } + slots.set( name, { ref, fillProps } ); }; const registerFill: SlotFillBubblesVirtuallyContext[ 'registerFill' ] = ( diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx index b8ead7fc7ea8b..e65c055c410a6 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx @@ -39,8 +39,7 @@ function Slot( ...restProps } = props; - const { registerSlot, unregisterSlot, ...registry } = - useContext( SlotFillContext ); + const registry = useContext( SlotFillContext ); const ref = useRef< HTMLElement >( null ); @@ -54,11 +53,9 @@ function Slot( }, [ fillProps ] ); useLayoutEffect( () => { - registerSlot( name, ref, fillPropsRef.current ); - return () => { - unregisterSlot( name, ref ); - }; - }, [ registerSlot, unregisterSlot, name ] ); + registry.registerSlot( name, ref, fillPropsRef.current ); + return () => registry.unregisterSlot( name, ref ); + }, [ registry, name ] ); useLayoutEffect( () => { registry.updateSlot( name, ref, fillPropsRef.current ); diff --git a/packages/components/src/slot-fill/types.ts b/packages/components/src/slot-fill/types.ts index 15f082cf3f755..6668057323edd 100644 --- a/packages/components/src/slot-fill/types.ts +++ b/packages/components/src/slot-fill/types.ts @@ -110,15 +110,16 @@ export type SlotFillProviderProps = { export type SlotRef = RefObject< HTMLElement >; export type Rerenderable = { rerender: () => void }; +export type FillInstance = {}; export type SlotFillBubblesVirtuallyContext = { slots: ObservableMap< SlotKey, { ref: SlotRef; fillProps: FillProps } >; - fills: ObservableMap< SlotKey, Rerenderable[] >; + fills: ObservableMap< SlotKey, FillInstance[] >; registerSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void; unregisterSlot: ( name: SlotKey, ref: SlotRef ) => void; updateSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void; - registerFill: ( name: SlotKey, ref: Rerenderable ) => void; - unregisterFill: ( name: SlotKey, ref: Rerenderable ) => void; + registerFill: ( name: SlotKey, instance: FillInstance ) => void; + unregisterFill: ( name: SlotKey, instance: FillInstance ) => void; /** * This helps the provider know if it's using the default context value or not. From fa10d2fbd3faea76835374d6c850864d103a98c9 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Mon, 2 Dec 2024 17:07:55 +0100 Subject: [PATCH 33/52] Fix EntitiesSavedStates panel dialog props. (#67351) * Fix EntitiesSavedStates panel dialog props. * Remove renderDialog default value. Co-authored-by: afercia Co-authored-by: ntsekouras Co-authored-by: Mayank-Tripathi32 Co-authored-by: jameskoster --- .../edit-site/src/components/save-panel/index.js | 12 +++++++++--- packages/editor/README.md | 2 +- .../src/components/entities-saved-states/index.js | 15 ++++++--------- .../src/components/post-publish-button/index.js | 2 ++ .../src/components/save-publish-panels/index.js | 6 +++++- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/edit-site/src/components/save-panel/index.js b/packages/edit-site/src/components/save-panel/index.js index 81a0f99557df0..95ec9b9ffc8c4 100644 --- a/packages/edit-site/src/components/save-panel/index.js +++ b/packages/edit-site/src/components/save-panel/index.js @@ -31,7 +31,7 @@ const { EntitiesSavedStatesExtensible, NavigableRegion } = unlock( privateApis ); const { useLocation } = unlock( routerPrivateApis ); -const EntitiesSavedStatesForPreview = ( { onClose } ) => { +const EntitiesSavedStatesForPreview = ( { onClose, renderDialog } ) => { const isDirtyProps = useEntitiesSavedStatesIsDirty(); let activateSaveLabel; if ( isDirtyProps.isDirty ) { @@ -75,14 +75,20 @@ const EntitiesSavedStatesForPreview = ( { onClose } ) => { onSave, saveEnabled: true, saveLabel: activateSaveLabel, + renderDialog, } } /> ); }; -const _EntitiesSavedStates = ( { onClose, renderDialog = undefined } ) => { +const _EntitiesSavedStates = ( { onClose, renderDialog } ) => { if ( isPreviewingTheme() ) { - return ; + return ( + + ); } return ( diff --git a/packages/editor/README.md b/packages/editor/README.md index 8b48d773acb26..dd7b53f421a1d 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -401,7 +401,7 @@ _Parameters_ - _props_ `Object`: The component props. - _props.close_ `Function`: The function to close the dialog. -- _props.renderDialog_ `Function`: The function to render the dialog. +- _props.renderDialog_ `boolean`: Whether to render the component with modal dialog behavior. _Returns_ diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js index ea05bca522941..ad584b0df7557 100644 --- a/packages/editor/src/components/entities-saved-states/index.js +++ b/packages/editor/src/components/entities-saved-states/index.js @@ -31,14 +31,11 @@ function identity( values ) { * * @param {Object} props The component props. * @param {Function} props.close The function to close the dialog. - * @param {Function} props.renderDialog The function to render the dialog. + * @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior. * * @return {React.ReactNode} The rendered component. */ -export default function EntitiesSavedStates( { - close, - renderDialog = undefined, -} ) { +export default function EntitiesSavedStates( { close, renderDialog } ) { const isDirtyProps = useIsDirty(); return ( @@ -102,7 +103,10 @@ export default function SavePublishPanels( { return ( <> { isEntitiesSavedStatesOpen && ( - + ) } { ! isEntitiesSavedStatesOpen && unmountableContent } From 656110814c85d346669dd4fa2c9d3de670d35cbb Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 2 Dec 2024 16:39:38 +0000 Subject: [PATCH 34/52] Correctly apply current-menu-ancestor class to
  • in Nav block #67169 Co-authored-by: getdave Co-authored-by: MaggieCabrera Co-authored-by: carolinan Co-authored-by: mrwweb Co-authored-by: juanfra Co-authored-by: draganescu Co-authored-by: bph Co-authored-by: jordesign Co-authored-by: webmandesign --- packages/block-library/src/navigation-submenu/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation-submenu/index.php b/packages/block-library/src/navigation-submenu/index.php index 92b55e291606e..d61dbb2426c24 100644 --- a/packages/block-library/src/navigation-submenu/index.php +++ b/packages/block-library/src/navigation-submenu/index.php @@ -222,7 +222,7 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) { if ( strpos( $inner_blocks_html, 'current-menu-item' ) ) { $tag_processor = new WP_HTML_Tag_Processor( $html ); - while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item__content' ) ) ) { + while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item' ) ) ) { $tag_processor->add_class( 'current-menu-ancestor' ); } $html = $tag_processor->get_updated_html(); From 1d06b35940ef1d255b08f51cf242ed776d0077b0 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:55:55 +0100 Subject: [PATCH 35/52] Site Editor: Pages: Preload template lookup (#66654) Co-authored-by: ellatrix Co-authored-by: Mamaduka Co-authored-by: jorgefilipecosta --- backport-changelog/6.8/7695.md | 2 ++ lib/compat/wordpress-6.8/preload.php | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/backport-changelog/6.8/7695.md b/backport-changelog/6.8/7695.md index f45b2039e3027..d69a59f2d67d1 100644 --- a/backport-changelog/6.8/7695.md +++ b/backport-changelog/6.8/7695.md @@ -2,3 +2,5 @@ https://github.com/WordPress/wordpress-develop/pull/7695 * https://github.com/WordPress/gutenberg/pull/66631 * https://github.com/WordPress/gutenberg/pull/67465 +* https://github.com/WordPress/gutenberg/pull/66579 +* https://github.com/WordPress/gutenberg/pull/66654 diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php index 494e3ad32ec02..879e263f5a189 100644 --- a/lib/compat/wordpress-6.8/preload.php +++ b/lib/compat/wordpress-6.8/preload.php @@ -10,18 +10,26 @@ */ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { if ( 'core/edit-site' === $context->name ) { - $post_id = null; + $post = null; if ( isset( $_GET['postId'] ) && is_numeric( $_GET['postId'] ) ) { - $post_id = (int) $_GET['postId']; + $post = get_post( (int) $_GET['postId'] ); } if ( isset( $_GET['p'] ) && preg_match( '/^\/page\/(\d+)$/', $_GET['p'], $matches ) ) { - $post_id = (int) $matches[1]; + $post = get_post( (int) $matches[1] ); } - if ( $post_id ) { - $route_for_post = rest_get_route_for_post( $post_id ); + if ( $post ) { + $route_for_post = rest_get_route_for_post( $post ); if ( $route_for_post ) { $paths[] = add_query_arg( 'context', 'edit', $route_for_post ); + if ( 'page' === $post->post_type ) { + $paths[] = add_query_arg( + 'slug', + // @see https://github.com/WordPress/gutenberg/blob/489f6067c623926bce7151a76755bb68d8e22ea7/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js#L139-L140 + 'page-' . $post->post_name, + '/wp/v2/templates/lookup' + ); + } } } From d8a457b56b6671fa25dedf2b40841a13bdabe6f1 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:02:59 +0200 Subject: [PATCH 36/52] Block Editor: Animate useScaleCanvas() only when toggling zoomed out mode (#67481) Co-authored-by: tyxla Co-authored-by: ellatrix --- .../src/components/iframe/use-scale-canvas.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/iframe/use-scale-canvas.js b/packages/block-editor/src/components/iframe/use-scale-canvas.js index c72266e82e2b0..2d8cb217a3255 100644 --- a/packages/block-editor/src/components/iframe/use-scale-canvas.js +++ b/packages/block-editor/src/components/iframe/use-scale-canvas.js @@ -2,7 +2,11 @@ * WordPress dependencies */ import { useEffect, useRef, useCallback } from '@wordpress/element'; -import { useReducedMotion, useResizeObserver } from '@wordpress/compose'; +import { + usePrevious, + useReducedMotion, + useResizeObserver, +} from '@wordpress/compose'; /** * @typedef {Object} TransitionState @@ -280,6 +284,7 @@ export function useScaleCanvas( { transitionFromRef.current = transitionToRef.current; }, [ iframeDocument ] ); + const previousIsZoomedOut = usePrevious( isZoomedOut ); /** * Runs when zoom out mode is toggled, and sets the startAnimation flag * so the animation will start when the next useEffect runs. We _only_ @@ -287,7 +292,7 @@ export function useScaleCanvas( { * changes due to the container resizing. */ useEffect( () => { - if ( ! iframeDocument ) { + if ( ! iframeDocument || previousIsZoomedOut === isZoomedOut ) { return; } @@ -300,7 +305,7 @@ export function useScaleCanvas( { return () => { iframeDocument.documentElement.classList.remove( 'is-zoomed-out' ); }; - }, [ iframeDocument, isZoomedOut ] ); + }, [ iframeDocument, isZoomedOut, previousIsZoomedOut ] ); /** * This handles: From 232d14f33f57fcf6f0c07e52bdb5c52ba8f3dcae Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Tue, 3 Dec 2024 02:32:19 +0900 Subject: [PATCH 37/52] DropdownMenu: Increase option height to 40px (#67435) * DropdownMenu: Increase option height to 40px * Add changelog Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 3 +++ packages/components/src/dropdown-menu/index.tsx | 1 + packages/components/src/dropdown-menu/style.scss | 2 +- packages/components/src/menu-items-choice/style.scss | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 94dfd1b3c3811..b482a4801c2ea 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,9 @@ ### 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 diff --git a/packages/components/src/dropdown-menu/index.tsx b/packages/components/src/dropdown-menu/index.tsx index 0e4501be4839c..de83695978c2d 100644 --- a/packages/components/src/dropdown-menu/index.tsx +++ b/packages/components/src/dropdown-menu/index.tsx @@ -164,6 +164,7 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) { { controlSets?.flatMap( ( controlSet, indexOfSet ) => controlSet.map( ( control, indexOfControl ) => ( ' @@ -15,8 +22,10 @@ function DeletedNavigationWarning( { onCreateNew, isNotice = false } ) { button: (