-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Image block: Conditionally remove empty <figcaption>
#71893
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
Conversation
|
Flaky tests detected in 213be74. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/18155751268
|
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
justintadlock
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything looks good and works for me. 👍
|
Thank you very much! It looks like e2e tests are failing reproducibly, and the test failures are related to the Image block, so I probably didn't get the logic quite right. I'll look into it on Monday! |
| } | ||
| }; | ||
|
|
||
| $p = $internal_processor_class::create_fragment( $content ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks like existing code, but it would be nice to rename $p here to $processor for consistency with other code in Core as example code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll tackle this in a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5485aa9 to
714f480
Compare
|
I looked into the failing e2e tests. Apparently, when used with pattern overrides, the I think I'll add a unit test to cover this -- it'd be good to have lower-level coverage rather than rely on the e2e test for this. |
That's an interesting one. It looks like when the |
|
I added unit test coverage that reproduces the e2e test in 4899d29. This test passes on
AFAICS, the Image block has logic to update the class name to use the ID from the override: gutenberg/packages/block-library/src/image/index.php Lines 33 to 45 in f029e42
Hmm, it's included here though 🤔 |
I wasn't aware of that. The complexity comes from the fact that you can mix uploaded images and references through URL. <!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://www.online-image-editor.com/styles/2019/images/devices.png" alt=""/></figure>
<!-- /wp:image -->vs <!-- wp:image {"id":5,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="http://localhost:8889/wp-content/uploads/2025/09/image.png" alt="" class="wp-image-5"/></figure>
<!-- /wp:image -->By the way, it's the same image after clicking |
gziolo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. It would be great to get confirmation from @dmsnell about HTML API integration aspects.
|
|
||
| $p = new WP_HTML_Tag_Processor( $content ); | ||
| $p = new class( $content ) extends WP_HTML_Tag_Processor { | ||
| // phpcs:ignore Gutenberg.NamingConventions.ValidBlockLibraryFunctionName.FunctionNameInvalid, Gutenberg.Commenting.SinceTag.MissingMethodSinceTag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we not eliminate these ignore statements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add a @since tag (even though I'm not sure it makes that much sense for a method of an anonymous class) to get rid of MissingMethodSinceTag.
The FunctionNameInvalid is trickier.
The function name 'span_of_empty_element()' is invalid. In this file, PHP function names
must either match one of the allowed prefixes exactly or begin with one of them, followed by
an underscore. The allowed prefixes are: 'block_core_image', 'render_block_core_image',
'register_block_core_image'.
Of all these, I guess only block_core_image is a somewhat reasonable prefix 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is wild. the linting rule would prevent overloading the base class methods, wouldn’t it?
| $p = new WP_HTML_Tag_Processor( $content ); | ||
| $p = new class( $content ) extends WP_HTML_Tag_Processor { | ||
| // phpcs:ignore Gutenberg.NamingConventions.ValidBlockLibraryFunctionName.FunctionNameInvalid, Gutenberg.Commenting.SinceTag.MissingMethodSinceTag | ||
| public function span_of_empty_element() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reading the code it seems like the goal of this function is to return the textual span across which an empty element appears, or false if there are other elements inside it.
there’s a question in here whether HTML comments should invalidate an empty element or not and whether void tags count. to that end, I feel like it’s okay to leave this specialized.
we can also lean on next_token() since we expect an empty element. if there’s no closing tag this iteration will halt immediately whereas next_tag() will scan the entire document until the end.
/**
* Returns span of input for an empty FIGCAPTION, if currently matched on a
* FIGCAPTION opening tag and if the element is properly closed and empty.
*/
public function extract_empty_figcaption_element() {
$this->set_bookmark( 'here' );
$opener = $this->bookmarks['here'];
// Allow comments within the definition of “empty.”
while ( $this->next_token() && '#comment' === $this->get_token_name() ) {
continue;
}
if ( 'FIGCAPTION' !== $this->get_tag() || ! $this->is_tag_closer() ) {
return false;
}
$this->set_bookmark( 'here' );
$closer = $this->bookmarks['here'];
return new WP_HTML_Span( $opener->start, $closer->start + $closer->length - $opener->start );
}Being an anonymous class within a specific function helps with the guards that otherwise would be loose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reading the code it seems like the goal of this function is to return the textual span across which an empty element appears, or
falseif there are other elements inside it.
That's exactly right. I had to tweak the code in response to a regression that Grzegorz found, where the code accidentally removed <figcaption>s with elements inside of them.
there’s a question in here whether HTML comments should invalidate an empty element or not and whether void tags count.
Yeah, I was debating that for a moment myself. I had an HTML Processor based version first and was considering covering those edge cases. Eventually, I succumbed to the temptation to use the Tag Processor -- based on the assumption that we control the saved markup and can probably rule out "pathological" cases. In the process, I removed all calls to next_token(), since TBH I thought it wasn't available from the Tag Processor 🙈
to that end, I feel like it’s okay to leave this specialized.
I've updated the code based on your suggestion: 7adf0b6. Thank you!
|
|
||
| return $p->get_updated_html(); | ||
| $output = $p->get_updated_html(); | ||
| if ( ! empty( $figcaption_span ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is 100% fine and safe. still, I find if ( isset( $figcaption_span ) ) clearer from a semantic point of view. empty() makes me think of content whereas isset() makes me think of assignment.
well, ! empty() does have a marginal and insignificant performance implication that it performs an additional negation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to cover the case that $figcaption_span is set but false (which the anonymous class method returns if the <figcaption> is empty).
I had if ( isset( $figcaption_span ) && false !== $figcaption_span ) (or maybe if ( isset( $figcaption_span ) && $figcaption_span )) but remembered that that's equivalent to ! empty().
(I've found the latter to be quite widespread across the WordPress codebase in cases like these, and even though it took me some getting used to at first, I'm now accustomed enough with this little idiosyncracy that I find myself using it 😅 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it returned null instead of false it wouldn’t have the awkward mixed types 🤷♂️
no big deal.
7adf0b6 to
262995b
Compare
|
I'd like to include this in GB 21.8. I'll cherry-pick it to the corresponding branch. |
Remove the `<figcaption>` element from the block markup upon render if the `caption` attribute is empty. Since `caption` is a sourced attribute, this requires checking if the `<figcaption>` has any inner elements. If it doesn't, it's still possible that the `caption` attribute is connected to a block bindings source, so we also need to rule out that it received a non-empty value from that source. If neither criterion is met, we remove the `<figcaption>`. Co-authored-by: ockham <[email protected]> Co-authored-by: justintadlock <[email protected]> Co-authored-by: dmsnell <[email protected]> Co-authored-by: gziolo <[email protected]>
Done in 5884fa4. |
What?
If the
captionattribute is empty and bound to a block bindings source, and if that source returns an empty value, remove the<figcaption>element from the block markup upon render.Why?
In #71483, we started adding the
<figcaption>even if thecaptionattribute was empty, as long as it was bound to a block bindings source. This allows the<figcaption>to be populated with the value from the block bindings source during render.However, we don't want the
<figcaption>to be rendered if the block bindings source returns an empty value. (An empty<figcaption>could create a weird whitespace below an image, as e.g. reported by @justintadlock.)How?
The HTML API does not yet offer functionality to remove the "outer HTML" of a given tag (i.e. the tag opener followed by all tokens up until and including the matching tag closer). However, we can furnish this functionality by keeping a separate
$outputbuffer to copy each serialized token we encounter as we traverse the markup. (h/t @dmsnell for this tip)Testing Instructions
<figcaption>element.title.<figcaption>.Screencast