From d449e376a1f445daccb4a21ddfeee40baef3a981 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 30 Mar 2023 13:14:11 +1100 Subject: [PATCH 1/4] Add basic implementation of internal block attributes --- .../src/components/block-list/block.js | 32 +++++++++++++++++-- packages/blocks/src/api/serializer.js | 5 +++ packages/blocks/src/api/utils.js | 4 +++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 3980dd7b2aead3..d02501a054a8e0 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -315,22 +315,48 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { __unstableMarkLastChangeAsPersistent, moveBlocksToPosition, removeBlock, + __unstableMarkNextChangeAsNotPersistent, } = dispatch( blockEditorStore ); // Do not add new properties here, use `useDispatch` instead to avoid // leaking new props to the public API (editor.BlockListBlock filter). return { - setAttributes( newAttributes ) { + setAttributes( attributes ) { const { getMultiSelectedBlockClientIds } = registry.select( blockEditorStore ); const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(); - const { clientId } = ownProps; + const { clientId, name } = ownProps; const clientIds = multiSelectedBlockClientIds.length ? multiSelectedBlockClientIds : [ clientId ]; - updateBlockAttributes( clientIds, newAttributes ); + const blockType = getBlockType( name ); + + const internalKeys = new Set(); + for ( const key in blockType.attributes ) { + if ( blockType.attributes[ key ].internal ) { + internalKeys.add( key ); + } + } + + const internalAttributes = {}; + const nonInternalAttributes = {}; + for ( const key in attributes ) { + if ( internalKeys.has( key ) ) { + internalAttributes[ key ] = attributes[ key ]; + } else { + nonInternalAttributes[ key ] = attributes[ key ]; + } + } + + if ( Object.keys( internalAttributes ).length ) { + __unstableMarkNextChangeAsNotPersistent(); + updateBlockAttributes( clientId, internalAttributes ); + } + if ( Object.keys( nonInternalAttributes ).length ) { + updateBlockAttributes( clientIds, nonInternalAttributes ); + } }, onInsertBlocks( blocks, index ) { const { rootClientId } = ownProps; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 3300e0893d2459..1c45583c4b769e 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -225,6 +225,11 @@ export function getCommentAttributes( blockType, attributes ) { return accumulator; } + // Ignore internal attributes. + if ( attributeSchema.internal ) { + return accumulator; + } + // Ignore default value. if ( 'default' in attributeSchema && diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index c43445c6272264..da1cddf22bdf1e 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -261,6 +261,10 @@ export function __experimentalSanitizeBlockAttributes( name, attributes ) { return Object.entries( blockType.attributes ).reduce( ( accumulator, [ key, schema ] ) => { + if ( schema.internal ) { + return accumulator; + } + const value = attributes[ key ]; if ( undefined !== value ) { From 7236635b25dbd25d915b76ca1ee68fffca718b52 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 30 Mar 2023 13:19:25 +1100 Subject: [PATCH 2/4] Use internal block attribute to store file block's image blob --- bin/api-docs/gen-block-lib-list.js | 1 + packages/block-library/src/file/block.json | 4 ++ packages/block-library/src/file/edit.js | 37 +++++++++++++------ packages/block-library/src/file/transforms.js | 8 +--- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/bin/api-docs/gen-block-lib-list.js b/bin/api-docs/gen-block-lib-list.js index 60b0a99b096ab8..1b98efc7cced63 100644 --- a/bin/api-docs/gen-block-lib-list.js +++ b/bin/api-docs/gen-block-lib-list.js @@ -73,6 +73,7 @@ function getTruthyKeys( obj ) { return Object.keys( obj ) .filter( ( key ) => ! key.startsWith( '__exp' ) ) + .filter( ( key ) => ! obj[ key ].internal ) .map( ( key ) => ( obj[ key ] ? key : `~~${ key }~~` ) ); } diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index 08a78f3a94ce42..b2d16eb9dc07fe 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -52,6 +52,10 @@ "previewHeight": { "type": "number", "default": 600 + }, + "_blobURL": { + "type": "string", + "internal": true } }, "supports": { diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 733cdf9a9351fc..c23a8fc9cfbc76 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -23,7 +23,7 @@ import { store as blockEditorStore, __experimentalGetElementClassName, } from '@wordpress/block-editor'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useState } from '@wordpress/element'; import { useCopyToClipboard } from '@wordpress/compose'; import { __, _x } from '@wordpress/i18n'; import { file as icon } from '@wordpress/icons'; @@ -71,7 +71,9 @@ function FileEdit( { attributes, isSelected, setAttributes, clientId } ) { downloadButtonText, displayPreview, previewHeight, + _blobURL: blobURL, } = attributes; + const { media, mediaUpload } = useSelect( ( select ) => ( { media: @@ -87,21 +89,31 @@ function FileEdit( { attributes, isSelected, setAttributes, clientId } ) { const { toggleSelection, __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); + const [ isUploadingBlob, setIsUploadingBlob ] = useState( false ); + useEffect( () => { // Upload a file drag-and-dropped into the editor. - if ( isBlobURL( href ) ) { - const file = getBlobByURL( href ); + const file = getBlobByURL( blobURL ); + if ( file ) { + setIsUploadingBlob( true ); mediaUpload( { filesList: [ file ], - onFileChange: ( [ newMedia ] ) => onSelectFile( newMedia ), - onError: onUploadError, + onFileChange: ( [ newMedia ] ) => { + onSelectFile( newMedia, { isPersistent: false } ); + setIsUploadingBlob( false ); + }, + onError: ( message ) => { + onUploadError( message, { isPersistent: false } ); + setIsUploadingBlob( false ); + }, } ); - revokeBlobURL( href ); + revokeBlobURL( blobURL ); } if ( downloadButtonText === undefined ) { + __unstableMarkNextChangeAsNotPersistent(); changeDownloadButtonText( _x( 'Download', 'button label' ) ); } }, [] ); @@ -114,9 +126,12 @@ function FileEdit( { attributes, isSelected, setAttributes, clientId } ) { } }, [ href, fileId, clientId ] ); - function onSelectFile( newMedia ) { - if ( newMedia && newMedia.url ) { + function onSelectFile( newMedia, { isPersistent = true } = {} ) { + if ( newMedia && newMedia.url && ! isBlobURL( newMedia.url ) ) { const isPdf = newMedia.url.endsWith( '.pdf' ); + if ( ! isPersistent ) { + __unstableMarkNextChangeAsNotPersistent(); + } setAttributes( { href: newMedia.url, fileName: newMedia.title, @@ -178,9 +193,9 @@ function FileEdit( { attributes, isSelected, setAttributes, clientId } ) { const blockProps = useBlockProps( { className: classnames( - isBlobURL( href ) && getAnimateClassName( { type: 'loading' } ), + isUploadingBlob && getAnimateClassName( { type: 'loading' } ), { - 'is-transient': isBlobURL( href ), + 'is-transient': isUploadingBlob, } ), } ); @@ -232,7 +247,7 @@ function FileEdit( { attributes, isSelected, setAttributes, clientId } ) { />
diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index 35dd9807daddd7..bdcf4b80aefaf7 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -21,14 +21,10 @@ const transforms = { const blocks = []; files.forEach( ( file ) => { - const blobURL = createBlobURL( file ); - - // File will be uploaded in componentDidMount() blocks.push( createBlock( 'core/file', { - href: blobURL, - fileName: file.name, - textLinkHref: blobURL, + // File will be uploaded when block mounts. + _blobURL: createBlobURL( file ), } ) ); } ); From 38d840d6b267201f187a5228a49e96f6b2aa1912 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 28 Mar 2023 16:10:39 +1100 Subject: [PATCH 3/4] Use internal block attribute to implement Legacy Widget -> Navigation transformation --- .../block-library/src/navigation/block.json | 4 + .../src/navigation/edit/index.js | 39 ++-- .../src/blocks/legacy-widget/transforms.js | 170 +++++++++--------- 3 files changed, 116 insertions(+), 97 deletions(-) diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index ce2bed0d8837f6..ce4b9f609765ed 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -71,6 +71,10 @@ "templateLock": { "type": [ "string", "boolean" ], "enum": [ "all", "insert", "contentOnly", false ] + }, + "_classicMenuId": { + "type": "number", + "internal": true } }, "providesContext": { diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 16cc4c014cf90f..448aaa2ba669ae 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -102,6 +102,7 @@ function Navigation( { } = {}, hasIcon, icon = 'handle', + _classicMenuId: classicMenuId, } = attributes; const ref = attributes.ref; @@ -402,18 +403,34 @@ function Navigation( { ] = useState(); const [ detectedOverlayColor, setDetectedOverlayColor ] = useState(); - const onSelectClassicMenu = async ( classicMenu ) => { - const navMenu = await convertClassicMenu( - classicMenu.id, - classicMenu.name, - 'draft' - ); - if ( navMenu ) { - handleUpdateMenu( navMenu.id, { - focusNavigationBlock: true, - } ); + const onSelectClassicMenu = useCallback( + async ( classicMenu ) => { + const navMenu = await convertClassicMenu( + classicMenu.id, + classicMenu.name, + 'draft' + ); + if ( navMenu ) { + handleUpdateMenu( navMenu.id, { + focusNavigationBlock: true, + } ); + } + }, + [ convertClassicMenu, handleUpdateMenu ] + ); + + // Convert the classic menu provided by the Legacy Widget block transform if + // it exists. + useEffect( () => { + if ( classicMenuId ) { + const classicMenu = classicMenus?.find( + ( menu ) => menu.id === classicMenuId + ); + if ( classicMenu ) { + onSelectClassicMenu( classicMenu ); + } } - }; + }, [ classicMenuId, classicMenus, onSelectClassicMenu ] ); const onSelectNavigationMenu = ( menuId ) => { handleUpdateMenu( menuId ); diff --git a/packages/widgets/src/blocks/legacy-widget/transforms.js b/packages/widgets/src/blocks/legacy-widget/transforms.js index 3e4aab3cc0b5b1..73bf1b9a805a5f 100644 --- a/packages/widgets/src/blocks/legacy-widget/transforms.js +++ b/packages/widgets/src/blocks/legacy-widget/transforms.js @@ -3,100 +3,96 @@ */ import { createBlock } from '@wordpress/blocks'; -const legacyWidgetTransforms = [ +const toTransforms = [ { - block: 'core/calendar', - widget: 'calendar', + idBase: 'calendar', + blockName: 'core/calendar', + convert: () => createBlock( 'core/calendar' ), }, { - block: 'core/search', - widget: 'search', + idBase: 'search', + blockName: 'core/search', + convert: () => createBlock( 'core/search' ), }, { - block: 'core/html', - widget: 'custom_html', - transform: ( { content } ) => ( { - content, - } ), + idBase: 'custom_html', + blockName: 'core/html', + convert: ( { content } ) => + createBlock( 'core/html', { + content, + } ), }, { - block: 'core/archives', - widget: 'archives', - transform: ( { count, dropdown } ) => { - return { + idBase: 'archives', + blockName: 'core/archives', + convert: ( { count, dropdown } ) => + createBlock( 'core/archives', { displayAsDropdown: !! dropdown, showPostCounts: !! count, - }; - }, + } ), }, { - block: 'core/latest-posts', - widget: 'recent-posts', - transform: ( { show_date: displayPostDate, number } ) => { - return { + idBase: 'recent-posts', + blockName: 'core/latest-posts', + convert: ( { show_date: displayPostDate, number } ) => + createBlock( 'core/latest-posts', { displayPostDate: !! displayPostDate, postsToShow: number, - }; - }, + } ), }, { - block: 'core/latest-comments', - widget: 'recent-comments', - transform: ( { number } ) => { - return { + idBase: 'recent-comments', + blockName: 'core/latest-comments', + convert: ( { number } ) => + createBlock( 'core/latest-comments', { commentsToShow: number, - }; - }, + } ), }, { - block: 'core/tag-cloud', - widget: 'tag_cloud', - transform: ( { taxonomy, count } ) => { - return { + idBase: 'tag_cloud', + blockName: 'core/tag-cloud', + convert: ( { taxonomy, count } ) => + createBlock( 'core/tag-cloud', { showTagCounts: !! count, taxonomy, - }; - }, + } ), }, { - block: 'core/categories', - widget: 'categories', - transform: ( { count, dropdown, hierarchical } ) => { - return { + idBase: 'categories', + blockName: 'core/categories', + convert: ( { count, dropdown, hierarchical } ) => + createBlock( 'core/categories', { displayAsDropdown: !! dropdown, showPostCounts: !! count, showHierarchy: !! hierarchical, - }; - }, + } ), }, { - block: 'core/audio', - widget: 'media_audio', - transform: ( { url, preload, loop, attachment_id: id } ) => { - return { + idBase: 'media_audio', + blockName: 'core/audio', + convert: ( { url, preload, loop, attachment_id: id } ) => + createBlock( 'core/audio', { src: url, id, preload, loop, - }; - }, + } ), }, { - block: 'core/video', - widget: 'media_video', - transform: ( { url, preload, loop, attachment_id: id } ) => { - return { + idBase: 'media_video', + blockName: 'core/video', + convert: ( { url, preload, loop, attachment_id: id } ) => + createBlock( 'core/video', { src: url, id, preload, loop, - }; - }, + } ), }, { - block: 'core/image', - widget: 'media_image', - transform: ( { + idBase: 'media_image', + blockName: 'core/image', + convert: ( { alt, attachment_id: id, caption, @@ -109,8 +105,8 @@ const legacyWidgetTransforms = [ size: sizeSlug, url, width, - } ) => { - return { + } ) => + createBlock( 'core/image', { alt, caption, height, @@ -123,14 +119,13 @@ const legacyWidgetTransforms = [ sizeSlug, url, width, - }; - }, + } ), }, { - block: 'core/gallery', - widget: 'media_gallery', - transform: ( { ids, link_type: linkTo, size, number } ) => { - return { + idBase: 'media_gallery', + blockName: 'core/gallery', + convert: ( { ids, link_type: linkTo, size, number } ) => + createBlock( 'core/gallery', { ids, columns: number, linkTo, @@ -138,55 +133,58 @@ const legacyWidgetTransforms = [ images: ids.map( ( id ) => ( { id, } ) ), - }; - }, + } ), }, { - block: 'core/rss', - widget: 'rss', - transform: ( { + idBase: 'rss', + blockName: 'core/rss', + convert: ( { url, show_author: displayAuthor, show_date: displayDate, show_summary: displayExcerpt, items, - } ) => { - return { + } ) => + createBlock( 'core/rss', { feedURL: url, displayAuthor: !! displayAuthor, displayDate: !! displayDate, displayExcerpt: !! displayExcerpt, itemsToShow: items, - }; - }, + } ), }, -].map( ( { block, widget, transform } ) => { + { + idBase: 'nav_menu', + blockName: 'core/navigation', + convert: ( { nav_menu: navMenu } ) => + createBlock( 'core/navigation', { + _classicMenuId: navMenu, + } ), + }, +].map( ( { idBase, blockName, convert } ) => { return { type: 'block', - blocks: [ block ], - isMatch: ( { idBase, instance } ) => { - return idBase === widget && !! instance?.raw; + blocks: [ blockName ], + isMatch( attributes ) { + return attributes.idBase === idBase && !! attributes.instance?.raw; }, - transform: ( { instance } ) => { - const transformedBlock = createBlock( - block, - transform ? transform( instance.raw ) : undefined - ); - if ( ! instance.raw?.title ) { - return transformedBlock; + transform( attributes ) { + const block = convert( attributes.instance.raw ); + if ( ! attributes.instance.raw?.title ) { + return block; } return [ createBlock( 'core/heading', { - content: instance.raw.title, + content: attributes.instance.raw.title, } ), - transformedBlock, + block, ]; }, }; } ); const transforms = { - to: legacyWidgetTransforms, + to: toTransforms, }; export default transforms; From 90e81a9659e48a640eac94e1ae8d79b13a394832 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 30 Mar 2023 13:38:42 +1100 Subject: [PATCH 4/4] Use internal block attribute to store internal widget IDs --- packages/edit-widgets/src/store/actions.js | 4 ++-- packages/widgets/package.json | 1 + packages/widgets/src/hooks/index.js | 4 ++++ packages/widgets/src/hooks/widget-id.js | 19 +++++++++++++++++++ packages/widgets/src/index.js | 1 + packages/widgets/src/utils.js | 10 +++++----- 6 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 packages/widgets/src/hooks/index.js create mode 100644 packages/widgets/src/hooks/widget-id.js diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 8124ace66bdb3a..51bad6c274cdea 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -235,9 +235,9 @@ export const saveWidgetArea = const widget = preservedRecords[ i ]; const { block, position } = batchMeta[ i ]; - // Set __internalWidgetId on the block. This will be persisted to the + // Set _widgetId on the block. This will be persisted to the // store when we dispatch receiveEntityRecords( post ) below. - post.blocks[ position ].attributes.__internalWidgetId = widget.id; + post.blocks[ position ].attributes._widgetId = widget.id; const error = registry .select( coreStore ) diff --git a/packages/widgets/package.json b/packages/widgets/package.json index a3bfcb9c8d88fb..058d56b0625a39 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -29,6 +29,7 @@ "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", diff --git a/packages/widgets/src/hooks/index.js b/packages/widgets/src/hooks/index.js new file mode 100644 index 00000000000000..bcc37c146bfae4 --- /dev/null +++ b/packages/widgets/src/hooks/index.js @@ -0,0 +1,4 @@ +/** + * Internal dependencies + */ +import './widget-id'; diff --git a/packages/widgets/src/hooks/widget-id.js b/packages/widgets/src/hooks/widget-id.js new file mode 100644 index 00000000000000..8f1c708183ca00 --- /dev/null +++ b/packages/widgets/src/hooks/widget-id.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { addFilter } from '@wordpress/hooks'; + +addFilter( + 'blocks.registerBlockType', + 'core/widgets/widget-id', + ( settings ) => ( { + ...settings, + attributes: { + ...settings.attributes, + _widgetId: { + type: 'string', + internal: true, + }, + }, + } ) +); diff --git a/packages/widgets/src/index.js b/packages/widgets/src/index.js index 9520943bcae873..a85848675725bf 100644 --- a/packages/widgets/src/index.js +++ b/packages/widgets/src/index.js @@ -8,6 +8,7 @@ import { registerBlockType } from '@wordpress/blocks'; */ import * as legacyWidget from './blocks/legacy-widget'; import * as widgetGroup from './blocks/widget-group'; +import './hooks'; export * from './components'; export * from './utils'; diff --git a/packages/widgets/src/utils.js b/packages/widgets/src/utils.js index 75b1f3d08140ec..64d48836ccfdbb 100644 --- a/packages/widgets/src/utils.js +++ b/packages/widgets/src/utils.js @@ -4,15 +4,15 @@ * Get the internal widget id from block. * * @typedef {Object} Attributes - * @property {string} __internalWidgetId The internal widget id. + * @property {string} _widgetId The internal widget id. * @typedef {Object} Block - * @property {Attributes} attributes The attributes of the block. + * @property {Attributes} attributes The attributes of the block. * - * @param {Block} block The block. + * @param {Block} block The block. * @return {string} The internal widget id. */ export function getWidgetIdFromBlock( block ) { - return block.attributes.__internalWidgetId; + return block.attributes._widgetId; } /** @@ -27,7 +27,7 @@ export function addWidgetIdToBlock( block, widgetId ) { ...block, attributes: { ...( block.attributes || {} ), - __internalWidgetId: widgetId, + _widgetId: widgetId, }, }; }