Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block API: Add supports object to block attributes to configure copy support #38643

Open
wants to merge 32 commits into
base: trunk
Choose a base branch
from

Conversation

stacimc
Copy link
Contributor

@stacimc stacimc commented Feb 8, 2022

Closes #29693
Alternative to #32604, #34750

Description

This PR:

  • Adds handling for a new supports object property to block attributes. It includes the copy support, used to indicate whether the attribute should be copied on block duplication. It is true by default.

Example fragment of block.json:

            "attributes": {
		"myAttributeThatShouldNotBeCopied": {
			"type": "string",
			"default": "my-default-value",
			"__experimentalSupports": {
				"copy": false,
			}
		}
	},

Copy support is true by default, meaning it is true if the supports.copy is not configured, and if the supports property is missing altogether.

  • Refactors __internalWidgetID to use this new property/

For just one example of how this might be used: the Jetpack Pay with Paypal block uses a productId attribute to identify the actual product record referenced by the block. This data is meant to be internal to the block and should not be exposed to the user. When the user duplicates the block, we want to copy the other attributes used to store price info and other details, but create a brand new product record/productId.

Notes

  • In other regards, an attribute configured this way acts like a normal attribute
    • It respects any configured default values Default values are not filled in on block duplication either; support for this could be easily added if a viable use case becomes apparent, though
    • It can be managed as normal via setAttributes
    • It can apply to any type of attribute (ie it is not necessarily an id field)
  • While you can give this role to an attribute with a "source"/"selector" configuration, it is not meaningful to do so. For example, adding it to the content attribute of the Code block would not make sense, because although the attribute would not be copied on duplication, it will populate with the same value from the html.
  • Because this is experimental and also relies on experimental features, I have not added documentation; happy to do so if that is appropriate!
  • A note about Reusable blocks:
    Attributes without copy support are not removed when you convert a block into a Reusable block. I don’t know a ton about Reusable blocks, so calling this out so that someone more familiar can correct me; my understanding is that multiple instances of a Reusable block are not independent of each other (ie editing one copy affects them all), so even 'internal' attributes should also be shared.

How has this been tested?

  • Play around with adding supports: { copy: false } to any block attribute.
  • Insert the block into a new post, change its attributes, and duplicate it
  • Verify that the un-copyable attributes were not copied, but all other attributes were
  • Test with attributes that have a default value and ensure that the default is respected in the duplicated block
  • Test Copy+Paste using both the Copy menu item, and by keyboard commands
  • Test duplicating a block in the widget editor
  • Test adding and editing widgets in both the Widget editor and the Customizer
    • Test moving widgets between widget areas/side panels
    • Test a server-side rendered block in the widget editor and make sure the preview is displayed correctly (Basically verify that Fix server rendered widget not previewing #29197 is still fixed with the new implementation)

Screenshots

Types of changes

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).

@stacimc stacimc self-assigned this Feb 8, 2022
@stacimc stacimc added [Feature] Block API API that allows to express the block paradigm. [Type] New API New API to be used by plugin developers or package users. Needs Technical Feedback Needs testing from a developer perspective. labels Feb 8, 2022
@stacimc stacimc changed the title Try/copy support for block attributes Block API: Add supports object to block attributes to configure copy support Feb 8, 2022
@github-actions
Copy link

github-actions bot commented Feb 8, 2022

Size Change: +10.2 kB (+1%)

Total Size: 1.21 MB

Filename Size Change
build/block-editor/index.min.js 146 kB +1.13 kB (+1%)
build/block-editor/style-rtl.css 15.4 kB +418 B (+3%)
build/block-editor/style.css 15.4 kB +412 B (+3%)
build/block-library/blocks/pullquote/style-rtl.css 370 B -19 B (-5%)
build/block-library/blocks/pullquote/style.css 370 B -18 B (-5%)
build/block-library/blocks/separator/editor-rtl.css 140 B +41 B (+41%) 🚨
build/block-library/blocks/separator/editor.css 140 B +41 B (+41%) 🚨
build/block-library/blocks/separator/theme-rtl.css 194 B +22 B (+13%) ⚠️
build/block-library/blocks/separator/theme.css 194 B +22 B (+13%) ⚠️
build/block-library/blocks/site-logo/editor-rtl.css 759 B +15 B (+2%)
build/block-library/blocks/site-logo/editor.css 759 B +15 B (+2%)
build/block-library/editor-rtl.css 10 kB +37 B (0%)
build/block-library/editor.css 10 kB +36 B (0%)
build/block-library/index.min.js 171 kB +2.51 kB (+1%)
build/block-library/style-rtl.css 11.2 kB -9 B (0%)
build/block-library/style.css 11.2 kB -7 B (0%)
build/block-library/theme-rtl.css 689 B +24 B (+4%)
build/block-library/theme.css 694 B +24 B (+4%)
build/blocks/index.min.js 46.8 kB +5 B (0%)
build/components/index.min.js 223 kB +4.72 kB (+2%)
build/components/style-rtl.css 14.9 kB -648 B (-4%)
build/components/style.css 14.9 kB -641 B (-4%)
build/core-data/index.min.js 14.3 kB -11 B (0%)
build/customize-widgets/index.min.js 11.2 kB +20 B (0%)
build/date/index.min.js 32 kB +66 B (0%)
build/edit-navigation/index.min.js 16.1 kB -54 B (0%)
build/edit-navigation/style-rtl.css 4.04 kB +1 B (0%)
build/edit-navigation/style.css 4.05 kB +1 B (0%)
build/edit-post/index.min.js 29.8 kB +12 B (0%)
build/edit-post/style-rtl.css 7.07 kB +1 B (0%)
build/edit-post/style.css 7.07 kB +2 B (0%)
build/edit-site/index.min.js 45.4 kB +1.6 kB (+4%)
build/edit-site/style-rtl.css 7.6 kB +160 B (+2%)
build/edit-site/style.css 7.58 kB +156 B (+2%)
build/edit-widgets/index.min.js 16.5 kB +11 B (0%)
build/edit-widgets/style-rtl.css 4.4 kB +1 B (0%)
build/edit-widgets/style.css 4.39 kB +1 B (0%)
build/editor/index.min.js 38.4 kB +28 B (0%)
build/server-side-render/index.min.js 1.6 kB -10 B (-1%)
build/widgets/index.min.js 7.29 kB +76 B (+1%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/admin-manifest/index.min.js 1.24 kB
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.49 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/avatar/editor-rtl.css 59 B
build/block-library/blocks/avatar/editor.css 59 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 961 B
build/block-library/blocks/gallery/editor.css 964 B
build/block-library/blocks/gallery/style-rtl.css 1.51 kB
build/block-library/blocks/gallery/style.css 1.51 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 114 B
build/block-library/blocks/heading/style.css 114 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.89 kB
build/block-library/blocks/navigation/style.css 1.88 kB
build/block-library/blocks/navigation/view.min.js 2.85 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 201 B
build/block-library/blocks/quote/style.css 201 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 934 B
build/block-library/common.css 932 B
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/compose/index.min.js 11.2 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 8.19 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.53 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 4.29 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 751 B
build/nux/style.css 749 B
build/plugins/index.min.js 1.98 kB
build/preferences/index.min.js 1.2 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 611 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@stacimc
Copy link
Contributor Author

stacimc commented Feb 8, 2022

Relative to the implementation proposed in #34750, these are the changes, (thanks @noisysocks 😄):

  • The API used to make use of the __experimentalRole to create 'internal' attributes. This PR instead adds a supports object:
	"attributes": {
		"myAttributeThatShouldNotBeCopied": {
			"type": "string",
			"default": "my-default-value",
			"supports": {
				"copy": false,
			}
		}
	},
  • Removes __experimentalCloneBlock and instead provides an option for both cloneBlock and serialize to toggle retention of non-copyable attributes (by default they will be copied):
const serialized = serialize( blocks, { retainCopyAttributes: false  } );
const cloned = cloneBlock( block, {}, null, { retainCopyAttributes: false } )
  • The pre-existing __experimentalGetBlockAttributesNamesByRole utility has been modified to __experimentalFilterBlockAttributes and now filters attributes by arbitrary properties, so:
__experimentalFilterBlockAttributes( blockName, { __experimentalRole: 'content' } );
__experimentalFilterBlockAttributes( blockName, { supports: { copy: false } } );

@@ -33,7 +33,10 @@ const POPOVER_PROPS = {
};

function CopyMenuItem( { blocks, onCopy } ) {
const ref = useCopyToClipboard( () => serialize( blocks ), onCopy );
const ref = useCopyToClipboard(
() => serialize( blocks, { retainCopyAttributes: false } ),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retainCopyAttributes isn't a spectacular name, but I had difficulty coming up with a better one. excludeNonCopyableAttributes? retainOnlyCopyableAttributes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe includeCopyableAttributes? Also let's add __experimental for the moment 😀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed about __experimental :)

Updated to __experimentalExcludeNonCopyableAttributes (and inverted the logic). It's an... intense name 😅 but I'm having trouble being more concise. I'm worried that something like retainCopyAttributes or includeCopyableAttributes doesn't really get across that it means retain only copyable attributes.

__experimentalSanitizeBlockAttributes( block, attributes );
// The __internalWidgetId is only defined on the client-side and must be removed before making the
// request.
const retainedAttributes = omit( attributes, [ '__internalWidgetId' ] );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might be able to do this in a more generic way, but the __internalWidgetId is actually a special case and is the only attribute that needs to be stripped here 🤔

My plan was to just add back in __experimentalSanitizeBlockAttributes, which on trunk handles the removal of the widgetId -- but that will no longer work! That function removes un-registered attributes, but the widgetId is registered now (just only on the client side).

We don't want to omit all attributes with copy: false here, because it's entirely possible to add 'normal' server-side attributes that don't support copy, and those shouldn't be stripped. Only the widgetId is a special case.

Alternative suggestions from @noisysocks on #34750:

We could just set 'additionalProperties' to true on the server. I don't really see the need to be so strict there. This might require a Core patch, though, as I'm not sure that it's possible for a plugin to re-register a route.

Or, we could have ServerSideRender perform a GET on the /block-types endpoint before making its request to /block-renderer. If an attribute isn't in the attributes object that comes back from /block-types, then it hasn't been defined on the server, and so we must not send it to /block-renderer.

Copy link
Member

@noisysocks noisysocks Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could just set 'additionalProperties' to true on the server. I don't really see the need to be so strict there. This might require a Core patch, though, as I'm not sure that it's possible for a plugin to re-register a route.

I'm leaning towards this since it matches how the frontend works, i.e. you can put whatever you like in the block's markup and the block editor will silently ignore it.

But yeah this is fine as a temporary solution. Before merging, could you please create an issue to track this and link to it in a // TODO?

@noisysocks
Copy link
Member

I much prefer this 😀 it's "KISS" and we're not overloading the meaning of role which is for giving an attribute semantic meaning. I'll look more closely at and test the PR on Monday.

@draganescu
Copy link
Contributor

I think this is better than internal which does not hold any meaning and could be interpreted in any way.

@stacimc
Copy link
Contributor Author

stacimc commented Feb 11, 2022

Updated to change supports --> __experimentalSupports, as well as the new option to cloneBlock.

const serialized = serialize( blocks );
const serialized = serialize( blocks, {
__experimentalExcludeNonCopyableAttributes:
event.type === 'copy',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one feels overly specific when we're introducing a general way we could accomplish this too.

const serializeOptiosn = event.type === 'copy'
	? { __experimentalIncludeAttributes: [ 'copy' ]
	: undefined;

const serialized = serialize( blocks, serializeOptions );

Also I'm really lost on the double negative. A positive phrasing could make that easier to understand.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this would give us the ability to denote multiple sets of attributes for inclusion or sets for explicit exclusion)

{
	__experimentalExcludeAttributes: [ 'style' ]
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name definitely leaves a lot to be desired, suggestions are very much appreciated 😄

Making it extensible to other sets of attributes is great! It gets a little busy if we want to include the ability to filter out by any attribute property (rather than limiting it to a list of supports keys for example):

const serializationOptions =  {
    __experimentalIncludeAttributes: {
            __experimentalSupports: { copy: false },
    }
}
const serialized = serialize(
    blocks,
    event.type === 'copy' ? serializationOptions : undefined
);

Not sure much can be done about that 🤔 I think the benefit to extensibility and the clearer naming is a big win.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a note about my example: I wasn't proposing nesting supports inside of includeAttributes. using your object notation instead of an array it could be this too, which is closer to what I was trying to imply. the include/exclude arrays have some advantages, but they aren't directly necessary here either

const options = {
	__experimentalAttributeSupports: {
		copy: false,
	}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky. Nesting supports inside of includeAttributes is very verbose but reads clearly to me. I'm a little concerned with this:

// Is it clear from this that attributes that do not support `copy` will be omitted?
cloneBlock(
    block,
    __experimentalOptions: {
        __experimentalSupports: { copy: false }
    }
}

// Would it be easy to confuse with this -- here attributes that DO support `copy` would be omitted:
cloneBlock(
    block,
    __experimentalOptions: {
        __experimentalSupports: { copy: true }
    }
}

Sorry if I'm misunderstanding, the double negatives are confusing.

// This is more 'streamlined' and reads more clearly to me, but only supports excluding attributes by `supports` keys. Eg you can't exclude attributes by their role for example
cloneBlock(
    block,
    __experimentalOptions: {
        __experimentalExcludeAttributes: { copy: false }
    }
}

@@ -1172,7 +1171,7 @@ export const duplicateBlocks = ( clientIds, updateSelection = true ) => ( {
last( castArray( clientIds ) )
);
const clonedBlocks = blocks.map( ( block ) =>
__experimentalCloneSanitizedBlock( block )
cloneBlock( block, {}, null, { retainCopyAttributes: false } )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't cloning imply retaining?

same question as above with regards to being overly-specific to copy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed updating retainCopyAttributes here, I'll update that when I settle on the API.

wouldn't cloning imply retaining?

In the driving use cases of a productId or widgetId, we don't want it retained on copy (whether that be from the block menu or keyboard commands) or on duplication. With this API though we could consider those to be separate supports:

"__experimentalSupports": {
    "copy": false,
    "duplicate": false
}

In either case, I think it's a good point that adding the logic to cloneBlock is unnecessarily confusing -- we could clone the block and strip the attributes afterward, here in the duplication logic. In an earlier iteration it made a little more sense to add the logic to cloneBlock because we were using it in multiple places.

saveAttributes = omit(
saveAttributes,
__experimentalFilterBlockAttributes( blockName, {
__experimentalSupports: { copy: false },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this here feels like it could be the thing exposed through serializeBlock's options. do we need both the inner and outer language of talking about it?

@ntsekouras
Copy link
Contributor

@stacimc thank you for all your work and the patience with all the back and forth in the approach 😄

Is the PR ready for review? I've started testing(without looking at the code yet) but I'm not observing the expected results.. I've tried adding to level and textAlign in Heading block.. Also can you please rebase the PR?

@stacimc stacimc force-pushed the try/copy-support-for-block-attributes branch from 1e20f1d to 6afe9e0 Compare February 23, 2022 17:39
@stacimc
Copy link
Contributor Author

stacimc commented Feb 23, 2022

@stacimc thank you for all your work and the patience with all the back and forth in the approach 😄

Is the PR ready for review? I've started testing(without looking at the code yet) but I'm not observing the expected results.. I've tried adding to level and textAlign in Heading block.. Also can you please rebase the PR?

Rebased -- and fixed a bug with duplicate that you may have been seeing. I am also planning on addressing @dmsnell's feedback here now it seems we may be going with this approach.

@ntsekouras
Copy link
Contributor

Rebased -- and fixed a bug with duplicate that you may have been seeing.

Thanks so much for doing this! I'll test and review tomorrow!

@stacimc stacimc force-pushed the try/copy-support-for-block-attributes branch from 5e5ca19 to 94efe0c Compare March 17, 2022 22:14
@stacimc
Copy link
Contributor Author

stacimc commented Mar 17, 2022

Rebased.

I'm a little concerned that our language introduces double negatives: we have exclude and { copy: false } which sounds like it's saying, "don't exclude the copy attributes" but I think we're actually saying, "go ahead and exclude the copy attributes."

Did we consider __experimentalRetainAttributes: { copy: false }?

@dmsnell The initial version was something close to that! One concern I have with the word retain is that it doesn't really reflect that we are only retaining those attributes, and excluding others. I do agree that the current language could be confusing/misleading, though 😞

I think part of the problem is that the object passed in is actually a filter which matches the structure of the attribute's configuration in block.json. So __experimentalExcludeAttributes: { copy: false } should be read not as "don't exclude copy attributes" but as "exclude attributes that match the filter: {copy: false}". That's not obvious.

What do you think about __experimentalFilterAttributes? __experimentalExcludeAttributesByFilter?

@dmsnell
Copy link
Member

dmsnell commented Mar 17, 2022

There are a few ways we can address the language, some of which none of us have considered yet.

For one, we don't have to pass an object. Do we ever have a valid case to call excludeAttributes: { copy: true } if indeed it implies the double-negative? We could pass an array of attribute sets to exclude and that would remove the double-boolean.

excludeAttributes: [ 'copy' ]

For two I'm not sure that retain carries the same heavy implication you mention given that we're in cloneBlock, but I see your point.

filter seems even more confusing to me.

__experimentalOmitting: { copy: true }
__experimentalExcludeAttributes: { copy: true }
__experimentalOmittingAttributes: [ 'copy' ]

the word exclude isn't as troublesome as saying that exclude: false implies that we do exclude.

@stacimc
Copy link
Contributor Author

stacimc commented Mar 18, 2022

excludeAttributes: [ 'copy' ] does read "exclude copy attributes" to me, but I'm less sure that "copy attributes" is clearly understood to be attributes that do not support the copy operation. I would likely read that as "exclude copyable attributes". Maybe that's just me, or maybe given that this is only being used in cloneBlock (since I removed it from serialize), it's no longer likely to be misinterpreted.

The reason I said filter is because the object passed is literally a filter which cloneBlock uses to filter through the block's attributes, and omits those that match. This would be clearer if we use the full object, at the expense of looking more unwieldy:

# Clone block and exclude attributes that do not support copy
cloneBlock( block, {}, null, {
	__experimentalExcludeAttributes: {
		__experimentalSupports: { copy: false }, # Here it's clear 'false' doesn't refer to exclusion
	}
);

I've also considered just passing a boolean, something like:

cloneBlock( block, {}, null, {
	excludeNonCopyableAttributes: false
);

@dmsnell
Copy link
Member

dmsnell commented Mar 18, 2022

Is there a problem with leaving the abstraction one-level deep and exposing { __experimentalSupports: { copy: false } } on cloneBlock()? I believe this was one of my earlier recommendations as well since it prevents introducing two new concepts when only one is being created.

cloneBlock( block, {}, null, { __experimentalSupports: { copy: false } } );

In terms of cloning that seems like there's a reasonable semantic connection between attributes we carry over and those we leave behind, plus it eliminates the confusion that exclude or include terminology introduces.

@stacimc
Copy link
Contributor Author

stacimc commented Mar 24, 2022

Is there a problem with leaving the abstraction one-level deep and exposing { __experimentalSupports: { copy: false } } on cloneBlock()?

My worry is it conflates the concepts of what the clone operation supports (what it looks like) versus what the attributes it includes and excludes support (what it's actually doing). To clarify:

cloneBlock( block, {}, null, { __experimentalSupports: { copy: false } } );

You could interpret this to say the clone operation itself should not support copy. I don't think it's obvious that means we'll remove attributes that don't support copy; the double negative is still an underlying issue. You could easily read this to say copyable attributes shouldn't be 'supported', ie should be excluded.

Or to put it another way, let's say we add additional support keys for attributes, and I would like to clone a block and exclude attributes that do support export (ie are configured with "__experimentalSupports": { "export": true}. I would probably guess the right way to do that would be:

// I want to clone a block, but I don't want to support exportable attributes
cloneBlock( block, {}, null, { __experimentalSupports: { export: false } } );

But that does the exact opposite of what I want, it excludes attributes that have export: false.

Maybe that latter case is contrived, but I think it's semantically clearer to be fully explicit, as heavy as it is:

// Clone block and exclude attributes that do not support copy
cloneBlock( block, {}, null, {
	__experimentalExcludeAttributes: {
		__experimentalSupports: { copy: false },
	}
);

@stacimc
Copy link
Contributor Author

stacimc commented Mar 24, 2022

I pushed this update:

cloneBlock( block, {}, null, {
	__experimentalExcludeAttributes: {
		__experimentalSupports: { copy: false },
	}
);

For the reasons I mentioned here. I think this is at least clear, although I acknowledge it's heavy-handed.

I think we could go in circles on this one, so I'm perfectly happy to change the language if folks generally prefer a simpler option. Given that this is only being added to cloneBlock and not to the serializer as well, there's less potential for misunderstanding. The language here is hard to get right and I really appreciate all the input 😄

@dmsnell
Copy link
Member

dmsnell commented Mar 24, 2022

It sure is a mess 😄 but maybe we'll eventually think of something without the confusion. This feels like a direct clone of the problem with .gitignore.

One last check: was there a problem with the array-of-supports-names? I still find that at least easier since it's presence-or-absence instead of a boolean.

Did we consider __experimentalIgnoreAttributesSupporting: [ 'copy' ]? Thinking of that on the same lines as .gitignore (if we're going to entertain heavy-handed names).

@stacimc
Copy link
Contributor Author

stacimc commented Mar 24, 2022

It sure is a mess 😄 but maybe we'll eventually think of something without the confusion. This feels like a direct clone of the problem with .gitignore.

Ha, definitely! Thank you for sticking with me 😅

One last check: was there a problem with the array-of-supports-names? I still find that at least easier since it's presence-or-absence instead of a boolean.

Did we consider __experimentalIgnoreAttributesSupporting: [ 'copy' ]? Thinking of that on the same lines as .gitignore (if we're going to entertain heavy-handed names).

I think it would have to be even slightly longer! __experimentalIgnoreAttributesNotSupporting: ['copy'], adding in the 'Not' 😅 This wouldn't support the hypothetical future scenario where you want to ignore attributes that do support some particular key, but it's definitely clear -- actually, I think even clearer than __experimentalExcludeAttributes: { __experimentalSupports: { copy: false } }.

Or we could take your earlier suggestion to invert the logic and adjust it to __experimentalRetainAttributesSupporting: ['copy']. I still worry a little that "include attributes that support copy" might not convey "and exclude attributes that don't", but that feels like much less of a concern than the double negative 🤷‍♀️ Using 'retain' instead of 'include' I think helps as well.

@dmsnell
Copy link
Member

dmsnell commented Mar 24, 2022

__experimentalIgnoreAttributesNotSupporting: ['copy']

as long as that is I feel like it's the clearest yet. it even reveals how deep the double-negations go, as I think I'm still getting lost knowing what the intentions are when I read these.

retain seems at least decent if we want something shorter and without Not in the name.

__experimentalOnlyCloneAttributesSupporting: [ 'copy' ] is another wholly-positive construction

@stacimc
Copy link
Contributor Author

stacimc commented Mar 24, 2022

as long as that is I feel like it's the clearest yet. it even reveals how deep the double-negations go, as I think I'm still getting lost knowing what the intentions are when I read these.

Agreed 😄

Random inspiration: how about something like __experimentalRequiredSupportKeys: [ 'copy' ] or just __experimentalRequiredSupports: [ 'copy' ]?

@dmsnell
Copy link
Member

dmsnell commented Mar 24, 2022

__experimentalRequiredSupports: [ 'copy' ]

I like this, but with one possible modification that we link it back to attributes since otherwise it's only evident to you and me that's the case. blocks, which are cloned, also have supports

__experimentalRequiredAttributeSupports: [ 'copy' ]
__experimentalRequireAttributeSupports: [ 'copy' ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. Needs Technical Feedback Needs testing from a developer perspective. [Type] New API New API to be used by plugin developers or package users.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Block API: Allow for internal, non-duplicable block attributes
5 participants