From c08b5d748510c20ce4f1f9fd8cd7a752dcb57709 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 8 Mar 2024 13:17:07 +0800 Subject: [PATCH 01/60] I'm not really sure if this is right, but this commit will show the inherit toggle regardless of whether there is a `defaultThemeLayout` value. (#59580) Presently, an empty object will pass the test so perhaps it's okay to remove the check? Also a bit of ESlint stuff for redundant var `css` --- packages/block-editor/src/hooks/layout.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 76a5557850a60..5f9017fce5bf6 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -125,14 +125,13 @@ export function useLayoutStyles( blockAttributes = {}, blockName, selector ) { const fullLayoutType = getLayoutType( usedLayout?.type || 'default' ); const [ blockGapSupport ] = useSettings( 'spacing.blockGap' ); const hasBlockGapSupport = blockGapSupport !== null; - const css = fullLayoutType?.getLayoutStyle?.( { + return fullLayoutType?.getLayoutStyle?.( { blockName, selector, layout, style, hasBlockGapSupport, } ); - return css; } function LayoutPanelPure( { @@ -144,8 +143,6 @@ function LayoutPanelPure( { const settings = useBlockSettings( blockName ); // Block settings come from theme.json under settings.[blockName]. const { layout: layoutSettings } = settings; - // Layout comes from block attributes. - const [ defaultThemeLayout ] = useSettings( 'layout' ); const { themeSupportsLayout } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); return { @@ -180,11 +177,9 @@ function LayoutPanelPure( { } // Only show the inherit toggle if it's supported, - // a default theme layout is set (e.g. one that provides `contentSize` and/or `wideSize` values), // and either the default / flow or the constrained layout type is in use, as the toggle switches from one to the other. const showInheritToggle = !! ( allowInheriting && - !! defaultThemeLayout && ( ! layout?.type || layout?.type === 'default' || layout?.type === 'constrained' || From ed7edbcc3db3155831239092830557df21a73b3a Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Fri, 8 Mar 2024 08:10:53 +0200 Subject: [PATCH 02/60] Site editor: Fix opening of save panel when using the `save` shortcut (#59647) Co-authored-by: ntsekouras Co-authored-by: jasmussen Co-authored-by: youknowriad Co-authored-by: SaxonF Co-authored-by: annezazu Co-authored-by: colorful-tones Co-authored-by: sunil25393 --- .../components/keyboard-shortcuts/global.js | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/edit-site/src/components/keyboard-shortcuts/global.js b/packages/edit-site/src/components/keyboard-shortcuts/global.js index cee406e4b2a40..2e71cf8820206 100644 --- a/packages/edit-site/src/components/keyboard-shortcuts/global.js +++ b/packages/edit-site/src/components/keyboard-shortcuts/global.js @@ -4,29 +4,41 @@ import { useShortcut } from '@wordpress/keyboard-shortcuts'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies */ import { store as editSiteStore } from '../../store'; +import { unlock } from '../../lock-unlock'; function KeyboardShortcutsGlobal() { const { __experimentalGetDirtyEntityRecords, isSavingEntityRecord } = useSelect( coreStore ); + const { hasNonPostEntityChanges } = useSelect( editorStore ); + const { getCanvasMode } = unlock( useSelect( editSiteStore ) ); const { setIsSaveViewOpened } = useDispatch( editSiteStore ); useShortcut( 'core/edit-site/save', ( event ) => { event.preventDefault(); const dirtyEntityRecords = __experimentalGetDirtyEntityRecords(); - const isDirty = !! dirtyEntityRecords.length; + const hasDirtyEntities = !! dirtyEntityRecords.length; const isSaving = dirtyEntityRecords.some( ( record ) => isSavingEntityRecord( record.kind, record.name, record.key ) ); - - if ( ! isSaving && isDirty ) { - setIsSaveViewOpened( true ); + const _hasNonPostEntityChanges = hasNonPostEntityChanges(); + const isViewMode = getCanvasMode() === 'view'; + if ( + ( ! hasDirtyEntities || ! _hasNonPostEntityChanges || isSaving ) && + ! isViewMode + ) { + return; } + // At this point, we know that there are dirty entities, other than + // the edited post, and we're not in the process of saving, so open + // save view. + setIsSaveViewOpened( true ); } ); return null; From 985ea06a811c5ab5d1db31e28d8aba8f57622b02 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Fri, 8 Mar 2024 10:13:48 +0100 Subject: [PATCH 03/60] synchronizeBlocksWithTemplate: extract common functions (#59682) Co-authored-by: jsnajdr Co-authored-by: tyxla --- packages/blocks/src/api/templates.js | 72 ++++++++++++++-------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index bc76218892688..71231121362a4 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -31,6 +31,42 @@ export function doBlocksMatchTemplate( blocks = [], template = [] ) { ); } +const isHTMLAttribute = ( attributeDefinition ) => + attributeDefinition?.source === 'html'; + +const isQueryAttribute = ( attributeDefinition ) => + attributeDefinition?.source === 'query'; + +function normalizeAttributes( schema, values ) { + if ( ! values ) { + return {}; + } + + return Object.fromEntries( + Object.entries( values ).map( ( [ key, value ] ) => [ + key, + normalizeAttribute( schema[ key ], value ), + ] ) + ); +} + +function normalizeAttribute( definition, value ) { + if ( isHTMLAttribute( definition ) && Array.isArray( value ) ) { + // Introduce a deprecated call at this point + // When we're confident that "children" format should be removed from the templates. + + return renderToString( value ); + } + + if ( isQueryAttribute( definition ) && value ) { + return value.map( ( subValues ) => { + return normalizeAttributes( definition.query, subValues ); + } ); + } + + return value; +} + /** * Synchronize a block list with a block template. * @@ -67,42 +103,6 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { // before creating the blocks. const blockType = getBlockType( name ); - const isHTMLAttribute = ( attributeDefinition ) => - attributeDefinition?.source === 'html'; - const isQueryAttribute = ( attributeDefinition ) => - attributeDefinition?.source === 'query'; - - const normalizeAttributes = ( schema, values ) => { - if ( ! values ) { - return {}; - } - - return Object.fromEntries( - Object.entries( values ).map( ( [ key, value ] ) => [ - key, - normalizeAttribute( schema[ key ], value ), - ] ) - ); - }; - const normalizeAttribute = ( definition, value ) => { - if ( isHTMLAttribute( definition ) && Array.isArray( value ) ) { - // Introduce a deprecated call at this point - // When we're confident that "children" format should be removed from the templates. - - return renderToString( value ); - } - - if ( isQueryAttribute( definition ) && value ) { - return value.map( ( subValues ) => { - return normalizeAttributes( - definition.query, - subValues - ); - } ); - } - - return value; - }; const normalizedAttributes = normalizeAttributes( blockType?.attributes ?? {}, From 47c22e75eb7a303ab3fab0955e07b95606927906 Mon Sep 17 00:00:00 2001 From: James Koster Date: Fri, 8 Mar 2024 10:39:41 +0000 Subject: [PATCH 04/60] Reduce visual prominence of primary actions in table data views, and consolidate primary + secondary actions in ellipsis menu (#59128) Co-authored-by: jameskoster Co-authored-by: andrewhayward Co-authored-by: carolinan Co-authored-by: afercia Co-authored-by: t-hamano Co-authored-by: jasmussen --- packages/dataviews/src/item-actions.js | 8 ++++++++ packages/dataviews/src/style.scss | 11 ++++++++--- packages/dataviews/src/view-table.js | 18 +++++++++++++++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/dataviews/src/item-actions.js b/packages/dataviews/src/item-actions.js index ce4dd6abef056..be19046d60ae3 100644 --- a/packages/dataviews/src/item-actions.js +++ b/packages/dataviews/src/item-actions.js @@ -21,6 +21,7 @@ const { DropdownMenuGroupV2: DropdownMenuGroup, DropdownMenuItemV2: DropdownMenuItem, DropdownMenuItemLabelV2: DropdownMenuItemLabel, + DropdownMenuSeparatorV2: DropdownMenuSeparator, kebabCase, } = unlock( componentsPrivateApis ); @@ -136,6 +137,7 @@ export default function ItemActions( { item, actions, isCompact } ) { } placement="bottom-end" > + + { + setIsHovered( true ); + }; + + const handleMouseLeave = () => { + setIsHovered( false ); + }; + return ( { if ( event.ctrlKey || event.metaKey ) { event.stopPropagation(); From a9a57c433a97e3e355eebd05094c217628c98038 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Fri, 8 Mar 2024 12:02:34 +0100 Subject: [PATCH 05/60] Add featured image to Media & Text block (#51491) * Add featured image to Media & Text block * Try adding the featured image option to the toolbar (replace flow) * fix JS warning in templates where there is no featured image. * Add deprecation, update fixtures * Try adding index.php * Index.php: use the $content and only replace the image source and the image background position. * Update index.php * Update index.php * Add alt and class names to the image tag * Fix white space issue * Add a placeholder for the featured image * Remove the deprecation * Add condition with useFeaturedImage, remove alt text option from placeholder * Set a minimum height for the placeholder image * Remove set_attribute 'alt' from index.php. * Update editor.scss * Update packages/block-library/src/media-text/index.php Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> * Update packages/block-library/src/media-text/media-container.js Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> * Try removing the image mediaType and inserting the img tag in index.php. * CS fix Adjust spacing. * Add condition to save.js. * CS: Remove the truthy default value for withIllustration in media-text/media-container.js * Update the condition that outputs the img tag Only output the img tag if there is a mediaURL. The mediaURL is required because img tags without a src attribute is invalid HTML. --------- Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> --- docs/reference-guides/core-blocks.md | 2 +- lib/blocks.php | 2 +- .../block-library/src/media-text/block.json | 5 + packages/block-library/src/media-text/edit.js | 99 +++++++++++++++---- .../block-library/src/media-text/editor.scss | 8 +- .../block-library/src/media-text/index.php | 66 +++++++++++++ .../src/media-text/media-container.js | 58 +++++++++-- packages/block-library/src/media-text/save.js | 4 +- .../fixtures/blocks/core__media-text.json | 3 +- .../core__media-text__image-alt-no-align.json | 3 +- 10 files changed, 215 insertions(+), 35 deletions(-) create mode 100644 packages/block-library/src/media-text/index.php diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 3ca3ffddaa9c7..cf70a7d8193ef 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -445,7 +445,7 @@ Set media and words side-by-side for a richer layout. ([Source](https://github.c - **Name:** core/media-text - **Category:** media - **Supports:** align (full, wide), anchor, color (background, gradients, heading, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** align, allowedBlocks, focalPoint, href, imageFill, isStackedOnMobile, linkClass, linkDestination, linkTarget, mediaAlt, mediaId, mediaLink, mediaPosition, mediaSizeSlug, mediaType, mediaUrl, mediaWidth, rel, verticalAlignment +- **Attributes:** align, allowedBlocks, focalPoint, href, imageFill, isStackedOnMobile, linkClass, linkDestination, linkTarget, mediaAlt, mediaId, mediaLink, mediaPosition, mediaSizeSlug, mediaType, mediaUrl, mediaWidth, rel, useFeaturedImage, verticalAlignment ## Unsupported diff --git a/lib/blocks.php b/lib/blocks.php index e1d4622a0f23d..572edf576b1cf 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -28,7 +28,6 @@ function gutenberg_reregister_core_block_types() { 'html', 'list', 'list-item', - 'media-text', 'missing', 'more', 'nextpage', @@ -78,6 +77,7 @@ function gutenberg_reregister_core_block_types() { 'latest-comments.php' => 'core/latest-comments', 'latest-posts.php' => 'core/latest-posts', 'loginout.php' => 'core/loginout', + 'media-text.php' => 'core/media-text', 'navigation.php' => 'core/navigation', 'navigation-link.php' => 'core/navigation-link', 'navigation-submenu.php' => 'core/navigation-submenu', diff --git a/packages/block-library/src/media-text/block.json b/packages/block-library/src/media-text/block.json index e320101bbd847..0f3519acb5d53 100644 --- a/packages/block-library/src/media-text/block.json +++ b/packages/block-library/src/media-text/block.json @@ -92,8 +92,13 @@ }, "allowedBlocks": { "type": "array" + }, + "useFeaturedImage": { + "type": "boolean", + "default": false } }, + "usesContext": [ "postId", "postType" ], "supports": { "anchor": true, "align": [ "wide", "full" ], diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index b96a0fa764823..9dfc4201d0c10 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -31,7 +31,7 @@ import { } from '@wordpress/components'; import { isBlobURL, getBlobTypeByURL } from '@wordpress/blob'; import { pullLeft, pullRight } from '@wordpress/icons'; -import { store as coreStore } from '@wordpress/core-data'; +import { useEntityProp, store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -127,7 +127,12 @@ function attributesFromMedia( { }; } -function MediaTextEdit( { attributes, isSelected, setAttributes } ) { +function MediaTextEdit( { + attributes, + isSelected, + setAttributes, + context: { postId, postType }, +} ) { const { focalPoint, href, @@ -145,9 +150,42 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { rel, verticalAlignment, allowedBlocks, + useFeaturedImage, } = attributes; const mediaSizeSlug = attributes.mediaSizeSlug || DEFAULT_MEDIA_SIZE_SLUG; + const [ featuredImage ] = useEntityProp( + 'postType', + postType, + 'featured_media', + postId + ); + + const featuredImageMedia = useSelect( + ( select ) => + featuredImage && + select( coreStore ).getMedia( featuredImage, { context: 'view' } ), + [ featuredImage ] + ); + + const featuredImageURL = useFeaturedImage + ? featuredImageMedia?.source_url + : ''; + const featuredImageAlt = useFeaturedImage + ? featuredImageMedia?.alt_text + : ''; + + const toggleUseFeaturedImage = () => { + setAttributes( { + imageFill: false, + mediaType: 'image', + mediaId: undefined, + mediaUrl: undefined, + mediaAlt: undefined, + useFeaturedImage: ! useFeaturedImage, + } ); + }; + const { imageSizes, image } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); @@ -261,25 +299,30 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { } /> ) } - { imageFill && mediaUrl && mediaType === 'image' && ( - - setAttributes( { focalPoint: value } ) - } - onDragStart={ imperativeFocalPointPreview } - onDrag={ imperativeFocalPointPreview } - /> - ) } - { mediaType === 'image' && ( + { imageFill && + ( mediaUrl || featuredImageURL ) && + mediaType === 'image' && ( + + setAttributes( { focalPoint: value } ) + } + onDragStart={ imperativeFocalPointPreview } + onDrag={ imperativeFocalPointPreview } + /> + ) } + { mediaType === 'image' && ( mediaUrl || featuredImageURL ) && ( @@ -303,6 +346,16 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { ) } /> ) } + { ( mediaUrl || featuredImageURL ) && ( + + ) } ); @@ -353,7 +406,11 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { onChangeUrl={ onSetHref } linkDestination={ linkDestination } mediaType={ mediaType } - mediaUrl={ image && image.source_url } + mediaUrl={ + useFeaturedImage && featuredImageURL + ? featuredImageURL + : image && image.source_url + } mediaLink={ image && image.link } linkTarget={ linkTarget } linkClass={ linkClass } @@ -370,6 +427,7 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { commitWidthChange={ commitWidthChange } ref={ refMediaContainer } enableResize={ blockEditingMode === 'default' } + toggleUseFeaturedImage={ toggleUseFeaturedImage } { ...{ focalPoint, imageFill, @@ -381,6 +439,9 @@ function MediaTextEdit( { attributes, isSelected, setAttributes } ) { mediaType, mediaUrl, mediaWidth, + useFeaturedImage, + featuredImageURL, + featuredImageAlt, } } /> { mediaPosition !== 'right' &&
} diff --git a/packages/block-library/src/media-text/editor.scss b/packages/block-library/src/media-text/editor.scss index 895de65511c13..b417fb951e99d 100644 --- a/packages/block-library/src/media-text/editor.scss +++ b/packages/block-library/src/media-text/editor.scss @@ -26,7 +26,8 @@ width: 100% !important; } -.wp-block-media-text.is-image-fill .editor-media-container__resizer { +.wp-block-media-text.is-image-fill .editor-media-container__resizer, +.wp-block-media-text.is-image-fill .components-placeholder.has-illustration { // The resizer sets an inline height but for the image fill we set it to full height. height: 100% !important; } @@ -34,3 +35,8 @@ .wp-block-media-text > .block-editor-block-list__layout > .block-editor-block-list__block { max-width: unset; } + +/* Make the featured image placeholder the same height as the media selection area. */ +.wp-block-media-text--placeholder-image { + min-height: 205px; +} diff --git a/packages/block-library/src/media-text/index.php b/packages/block-library/src/media-text/index.php new file mode 100644 index 0000000000000..9f5e334d14571 --- /dev/null +++ b/packages/block-library/src/media-text/index.php @@ -0,0 +1,66 @@ +'; + $content = preg_replace( '//', $image_tag, $content ); + + $processor = new WP_HTML_Tag_Processor( $content ); + if ( isset( $attributes['imageFill'] ) && $attributes['imageFill'] ) { + $position = '50% 50%'; + if ( isset( $attributes['focalPoint'] ) ) { + $position = round( $attributes['focalPoint']['x'] * 100 ) . '% ' . round( $attributes['focalPoint']['y'] * 100 ) . '%'; + } + $processor->next_tag( 'figure' ); + $processor->set_attribute( 'style', 'background-image:url(' . esc_url( $current_featured_image ) . ');background-position:' . $position . ';' ); + } + $processor->next_tag( 'img' ); + $media_size_slug = 'full'; + if ( isset( $attributes['mediaSizeSlug'] ) ) { + $media_size_slug = $attributes['mediaSizeSlug']; + } + $processor->set_attribute( 'src', esc_url( $current_featured_image ) ); + $processor->set_attribute( 'class', 'wp-image-' . get_post_thumbnail_id() . ' size-' . $media_size_slug ); + + $content = $processor->get_updated_html(); + + return $content; +} + +/** + * Registers the `core/media-text` block renderer on server. + */ +function register_block_core_media_text() { + register_block_type_from_metadata( + __DIR__ . '/media-text', + array( + 'render_callback' => 'render_block_core_media_text', + ) + ); +} +add_action( 'init', 'register_block_core_media_text' ); diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index 951c0013b76eb..72b38c43d16a9 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { ResizableBox, Spinner } from '@wordpress/components'; +import { ResizableBox, Spinner, Placeholder } from '@wordpress/components'; import { BlockControls, BlockIcon, @@ -56,21 +56,39 @@ const ResizableBoxContainer = forwardRef( } ); -function ToolbarEditButton( { mediaId, mediaUrl, onSelectMedia } ) { +function ToolbarEditButton( { + mediaId, + mediaUrl, + onSelectMedia, + toggleUseFeaturedImage, + useFeaturedImage, + featuredImageURL, +} ) { return ( ); } -function PlaceholderContainer( { className, mediaUrl, onSelectMedia } ) { +function PlaceholderContainer( { + className, + mediaUrl, + onSelectMedia, + toggleUseFeaturedImage, +} ) { const { createErrorNotice } = useDispatch( noticesStore ); const onUploadError = ( message ) => { @@ -86,6 +104,7 @@ function PlaceholderContainer( { className, mediaUrl, onSelectMedia } ) { className={ className } onSelect={ onSelectMedia } accept="image/*,video/*" + onToggleFeaturedImage={ toggleUseFeaturedImage } allowedTypes={ ALLOWED_MEDIA_TYPES } onError={ onUploadError } disableMediaButtons={ mediaUrl } @@ -110,13 +129,17 @@ function MediaContainer( props, ref ) { onSelectMedia, onWidthChange, enableResize, + toggleUseFeaturedImage, + useFeaturedImage, + featuredImageURL, + featuredImageAlt, } = props; const isTemporaryMedia = ! mediaId && isBlobURL( mediaUrl ); const { toggleSelection } = useDispatch( blockEditorStore ); - if ( mediaUrl ) { + if ( mediaUrl || featuredImageURL || useFeaturedImage ) { const onResizeStart = () => { toggleSelection( false ); }; @@ -134,11 +157,16 @@ function MediaContainer( props, ref ) { const backgroundStyles = mediaType === 'image' && imageFill - ? imageFillStyles( mediaUrl, focalPoint ) + ? imageFillStyles( mediaUrl || featuredImageURL, focalPoint ) : {}; const mediaTypeRenderers = { - image: () => {, + image: () => + useFeaturedImage && featuredImageURL ? ( + { + ) : ( + mediaUrl && { + ), video: () =>