-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Add more data about the image as block props in the image block #11973
Conversation
I'm trying to test this but am having no luck. Probably doing something stupid or wrong: function twentynineteen_calculate_image_sizes_attr( $image_attributes, $block_attributes ) {
var_dump($image_attributes);
var_dump($block_attributes);
}
add_filter( 'render_block_core_image_tag_attributes', 'twentynineteen_calculate_image_sizes_attr', 10, 2 ); This is just a rough test to see what is contained within the attributes so I can build my conditions. Running the above code in WP 4.9.8 and this fork does nothing. Various permutations have not resulted in anything happening when calling the Like I said, probably doing something wrong. Please advise. |
Uh, sorry, should have explained better. All the code in Also, it is not there yet but the (old) |
@azaozz What, in your opinion, should be the preferred method for themes to control the |
@azaozz This works! Look at the PR to Twenty Nineteen above. |
@mor10 these filters have different scopes, depends what the theme wants to accomplish :)
Ideally every theme that supports the Block editor would hook into Thinking we will have to post something like this explanation on /make as soon as this PR is merged. Would you be up for that? :) Edit: just renamed |
I can help write a post explaining all this. Will test the different approaches first thing next week to get a clear picture of what each of the filters does and how best to guide theme developers to get this baked in before launch. |
I have a hard time imagining there's not at least some redundancy in having all of
|
Of course there is some redundancy, but it's a "good thing" imho :)
Sure, except when were width and height set from resizing by dragging, and when were they set directly by the user. In some cases that may not matter in others (scaling on the front end) it's pretty critical.
Pretty much all resized images? Both "100% width" and resized images can potentially be scaled.
Yes, since the image block doesn't add width/height for non-resized images. There must be some way for the theme (front-end) to get at least the actual image dimensions. If these are known, the theme (or core) can decide to scale these images too. Also, images outputted without width and height attributes behave "badly", make the page "jump" while loading, push the comments form below the fold (while the user is trying to type), etc.
Possibly, but to store it we will have to change the image meta schema.
Sure. That would mean the image block will always add However, even when having width and height we would still prefer to know the fileWidth and fileHeight at least for external images. And, if we add them for external, why not for internal? :) I don't see any problem in saving some extra/redundant image meta in the image block props. This will make in more futureproof. Not having enough meta is much worse (see the image attachments meta where we could have stored few more very very useful values). |
this.props.setAttributes( { url, width: undefined, height: undefined } ); | ||
setWidthHeight( width, height, fileWidth, fileHeight, userSet ) { | ||
// Set image size and also whether set directly by the user. | ||
userSet = userSet || undefined; |
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.
Is this needed?
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 this is what I'm thinking it is, it sounds like it's something like intentionallySetWidth
and intentionallySetHeight
or displayWidth
and displayHeight
is there confusion since this boolean value doesn't entirely describe why it exists or what the user set?
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.
You mean userSet = userSet || undefined;
or userSet
? The first is probably not, just some "normalization", but as that's an internal function it can be skipped.
The userSet
indicates whether the image width
and height
were set directly by the user (typed in the width or height fields in the inspector), or they were added as a result of resizing the image by dragging. This would make a difference when the theme or a plugin is deciding to scale or not to scale an image on the front-end.
In the previous iteration these were actually two values: userWidth
and userHeight
. Switched it to one boolean after some critique that we store too much image meta in the block props. Can revert that easily :)
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.
Worth noting there's a subtlety here in that if the argument was called with userSet = false
, it would normalize the value to undefined
, which in practice has the same result since the default of the attribute is false
. And while that default is unlikely to change, if it were to, the normalizing would have the adverse effect of preventing a value of explicit false
to be provided as an argument.
} | ||
|
||
return $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.
is there a reason for this not to be hooking into the block filter so that we can get the actual attributes?
function _gutenberg_cache_images_meta( $html, $block ) {
if ( 'core/image' === $block[ 'blockName' ] ) {
_prime_post_caches( $block[ 'attrs' ][ 'id' ], /* why aren't these */ false, /* commented? */ true );
}
return $html;
}
add_filter( 'rendered_block', '_gutenberg_cache_images_meta', 3 );
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.
The purpose here is to get the IDs of all images (where we will do srcset and sizes) from the post before we parse the blocks. That's needed to get all image attachment meta in one go from the DB as that's faster.
So we need an array with all attachment IDs for the images used in the post. This is equivalent to https://core.trac.wordpress.org/browser/tags/4.9.8/src/wp-includes/media.php#L1336.
That should also include images from "Cover" and "Media and Text" blocks, and probably the Gallery block (if images there are "hardcoded"). Any ideas how to do that without a regex are very welcome :)
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.
Previously: #11377 (comment)
I'm not sure I understand why this would need to occur prior to the parse. If it at least occurs prior to any block evaluating its render_callback
, wouldn't that be equally sufficient to warm the cache?
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 at least occurs prior to any block evaluating its
render_callback
, wouldn't that be equally sufficient to warm the cache?
Since this doesn't exist as an option today anyways (requires filter on this value), we probably can't do anything of this sort right now.
Should follow-up with a Trac ticket as appropriate.
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.
Yeah, this doesn't do anything if we do it "per block". It makes sense only when it happens for all of post_content at once before we start to look at individual images and generating srcset
and sizes
.
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 we hook into the block-level filter (the design of which I favor in #10108) then we can track the parse through the blocks and then prime the cache on the do_blocks
filter which occurs after parsing and rendering is complete.
that is, we could imagine something like this…
function _gutenberg_cache_images_meta( $html, $block ) {
if ( $block['blockName'] === 'core/image' ) {
$this->add_image( $block['attrs']['id'] );
}
return $html;
}
add_filter( 'rendered_block', '_gutenberg_cache_images_meta', 3 );
function _gutenberg_prime_cache() {
if ( empty( $this->images ) ) {
return;
}
// whatever command does all images in one go
_prime_post_caches( $this->images, false, true, true, true, false, false );
}
add_filter( 'do_blocks', '_gutenberg_prime_cache' );
and this because we know that the final do_blocks
filter runs after block-level processing.
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.
noting that this is still based on a RegExp instead of using the per-block and per-document filters
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.
Another option would be to "unify" this with the "old" cache priming. We have the (required) wp-image-####
className on all images, including in Cover and Media and Text blocks.
Will need to abstract wp_make_content_images_responsive()
a bit, move the caching out and run it earlier on the_content
filter.
On the other hand _prime_post_caches()
is "clever enough" to not fetch posts and meta that are already cached so running two pre-caching methods won't "hurt" anything.
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'm leaning towards doing this in conjunction with #10108 because I want to be sure the design is ergonomic enough to power this use case in a way that is scalable and transitive to other cases on other blocks.
} | ||
|
||
updateImageURL( url ) { | ||
this.props.setAttributes( { url, width: undefined, height: undefined } ); | ||
setWidthHeight( width, height, fileWidth, fileHeight, userSet ) { |
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 name is definitely confusing here…does the width have a height?
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.
Well, you cannot really read the name of a function without reading it's parameter names, then it becomes pretty clear imho :) But lets change it to something more descriptive.
width: undefined, | ||
height: undefined, | ||
userSet: undefined, | ||
} ); |
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's no need here for two calls to setAttributes()
- we can collapse them into one in a couple of different ways. my favorite is with Object.assign()
// what does this name mean?
resetWidthHeight( fileWidth, fileHeight ) {
this.props.setAttributes( Object.assign(
{
width: undefined,
height: undefined,
userSet: undefined,
},
fileWidth && fileHeight && {
fileWidth,
fileHeight,
}
) );
}
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.
Sure. The purpose here is to make it as easy to read and understand as possible. After going through babel and minification, the code that actually runs in production will be completely different.
(I actually "have a beef" with the way a lot of the code is written. All the redundant indentation, nested lambda functions (no names = poor context), "clever" conditionals, almost no inline comments... There seem to be quite a few cases where the code becomes more readable after running it through babel and minification, then "unminifying" it. But lets look at that after 5.0 is out.) :)
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's no need here for two calls to
setAttributes()
- we can collapse them into one in a couple of different ways. my favorite is withObject.assign()
(Noting that the supplied example doesn't use Object.assign
and wouldn't work as written)
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.
(Noting that the supplied example doesn't use Object.assign and wouldn't work as written)
Good call! 😆 (fixed)
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.
Sure. The purpose here is to make it as easy to read and understand as possible. After going through babel and minification, the code that actually runs in production will be completely different.
The main thing for me here would be to avoid multiple calls to setAttributes
, as it will incur a render for each (unnecessary impact on performance). This will remain true after transpilation and minification.
I pushed bf67408 which I think is a reasonable compromise to preserve a similar readability.
|
||
return { | ||
width: width, | ||
height: height, |
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.
looks like we're introducing rounding errors when none are needed.
const width = targetWidth = ( fullWidth * ( targetWidth / fullWidth ) );
if we look in the math world the fullWidth
s will cancel each other out, but in JS we'll probably convert first to the ratio and then multiply, leaving error. the good news is that the accurate way is faster due to needing no computation.
constrainImageDimensions( width, height, targetWidth ) {
return {
height: Math.max( 1, Math.round( height * targetWidth / width ) ),
width,
};
}
Also, since this doesn't depend on any instance variables, might as well put it outside of the class and make it a simple helper function?
function scaledBy( value, ratio ) {
return Math.max( 1, Math.round( value * ratio ) );
}
…
const width = fullWidth;
const height = scaledBy( height, width / targetWidth );
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.
constrainImageDimensions
is pretty much a copy of core's wp_constrain_dimensions()
: https://core.trac.wordpress.org/browser/tags/4.9.8/src/wp-includes/media.php#L400. We can change/improve it as long as it works in exactly the same way (and is "easy to read") :)
since this doesn't depend on any instance variables, might as well put it outside of the class and make it a simple helper function
Sure, it probably can be re-used somewhere else.
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.
We can change/improve it as long as it works in exactly the same way
well it caught my attention because a bug in the algorithm is already mentioned in the comment 😉
then I realized it's doing much more than it needs to, and unlike the core function we're only scaling one dimension.
seems silly to me to drag out old code that fulfills other purposes in a way that's inefficient and buggy 🙃 (my tone is light-hearted here).
"easy to read"
probably won't ever reach consensus here, but I am sharing my own difficult in figuring out what the code is supposed to do. you can take what you want from it. my bias is that I'm pretty ignorant and unable to keep lots of stuff in my head at once; smarter people will probably be able to do more in-head parsing/compiling/state-tracking.
fileWidth = imageData.width; | ||
fileHeight = imageData.height; | ||
return false; | ||
} |
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.
using map
without returning a new list and when modifying things inline is pretty unusual. we have a couple of choices depending on what our intention is. it looks like we're looking for a specific value and using that if it exists.
updateImageURL( url, sizeOptions ) {
const { width, height } = sizeOptions;
if ( width && height ) {
return this.setAttributes( {
url,
fileWidth: width,
fileHeight: height,
} );
}
const option = find( sizeOptions, ( { value } ) => value === url );
if ( option ) {
return this.setAttributes( {
url,
fileWidth: option.imageData.width,
fileHeight: option.imageData.height;
} );
}
}
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.
Right, it doesn't need to use map
there. In the previous iteration sizeOptions
was different so we always needed to iterate to find the proper values, now it's simpler.
What about adding
To implement this, WordPress core badly needs an API method for
|
All HTML attribute names match `/[a-z0-9-]+/` except `data-*`. Fix the check to do the same.
Update selecting a larger image when alignment is "wide" or "full". Look for larger sizes that may have been added by themes or plugins, then fall back to the original image (if larger). Keep the arbitrary limit of 4300px to avoid forcing site visitors to download huge image files.
packages/editor/src/utils/dom.js
Outdated
|
||
const measure = document.createElement( 'div' ); | ||
measure.className = 'wp-block editor-block-list__block'; | ||
layout.appendChild( measure ); |
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.
To avoid the arbitrary clientWidth - 30
, perhaps can append another div here with class="editor-block-list__block-edit"
to mimic the default block layout, then get clientWidth
from it. I'll try it.
I know, it's fragile, but seems workable for now.
Done in 078a75b.
There still appear to be an inconsistency/delay when TwentyNineteen is active, and a post with images is opened for editing. Seems caused by the CSS as the images are also not sized properly, "hang out on the right" a bit. Looks like some CSS is loaded/added later, after rendering has finished. That causes a re-flow in the browser. When that is fixed, the clientWidth
will be correct from the start.
lib/compat.php
Outdated
*/ | ||
function gutenberg_render_block_core_image( $html, $block ) { | ||
// Old post or... something's wrong. | ||
if ( empty( $html ) || empty( $block['attrs'] ) ) { |
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 think we should be shortcutting here if the block is not the image block? It'll eventually be shortcutted at the below test of empty( $block_attributes['url'] )
, but it seems both indirect and wasteful to let us get that far, when this is called for every block.
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.
Right, good catch. Added in 648811f.
Return early when the block is not `core/image`.
$image_attributes = apply_filters( 'render_block_core_image_tag_attributes', $image_attributes, $block_attributes, $html ); | ||
|
||
$attr = ''; | ||
foreach ( $image_attributes as $name => $value ) { |
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'm not super confident in going this route (i.e. rebuilding the markup) because it carries a lot of overhead in future updates to the image block client side.
Also, I worry we package a lot of this functionality in the image block alone, and any other block that uses images, misses on 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.
Using block props to rebuild the <img>
tags seems the best option. It would be trivial to add support for more/other attributes in the future.
Of course we can always go "the other route" like in the current processing when adding srcset and sizes: use regex to parse some of the attributes, then add or replace them with recalculated values.
One general concern I have here is scalability if we want to reproduce a similar behaviour to WordPress/twentynineteen#629 for other blocks that are not |
Heh, yoda conditionals fail for negative checks?? Who added that to the coding standards?? It was not a part of the original :) Needs revision. |
Right, the idea was to add that to the Image Block first, test it, tweak it, and then add support for all images in all blocks. It won't be necessary for all images to have all block props, would be enough to at least have Enabling support for other blocks would be as easy as changing (the negative yoda noncompliant) |
I'm not sure I follow. |
@youknowriad I'm working on triage for WP 5.0.3, on https://core.trac.wordpress.org/ticket/45407, and noticed that this PR was moved out of all GB milestones. Is still the approach planned to get the information to core? If it is, should it be milestoned somewhere so that it gets tracked? If not, I'd appreciate being linked to any other relevant issues or PRs so that I can add them to the core ticket. Thanks so much! |
Correct if I'm wrong but I think we kind of agreed that this PR is not the right approach to solve these issues. At least we should break it into more discrete items and discuss the path forward. About the milestoning. PRs are milestoned according the corresponding plugin release. This didn't seem ready for me for 4.9 or 5.0. But the priorities... are handled more precisely in issues. At the moment we have two milestones:
We also have "Phase 2" for things that can come more down the road. I'd like us to work and triage and clarify these milestones more. In my personal priorities for phase2, I consider this meta issue as a good one to follow #13113 |
From my perspective, this PR is indeed too wide in scope and much of what it tries to solve deserves to be broken into smaller pieces or reallocated to a wider conversation about media in general. Specifically regarding https://core.trac.wordpress.org/ticket/45407, this is still a pressing issue that needs a resolution immediately. Afaik it is possible to resolve that specific issue as proposed in the Trac ticket without altering large parts of core or GB. The main issue in the past seemed to be it requires both changes to Core and GB, but since GB is now core that shouldn't be a blocker. I strongly recommend flagging the RICG ticket as high priority as core is still shipping incorrect code and preventing theme and plugin developers from resolving it on their end. |
This didn't get to WordPress 5.0 and 5.1. What are the next steps planned, should it be refreshed? @youknowriad and @mapk any thoughts? It also sort of duplicates #11377 as it was extracted from it. |
Per the most recent comments, it seems clear this pull request cannot be salvaged in its current state and will need to be revisited in separate smaller tasks. Related tracking issues / tickets: |
This is the second "chunk" split from #11377. It contains:
fileWidth
,fileHeight
,editWidth
,userSet
block attributes. These would allow us to add width and height, and the themes to generate bettersizes
attributes on the front-end.fileWidth
andfileHeight
are the image file dimensions at the time when the image was inserted. For "local" images they can also be "extracted" from the image meta, however there are edge cases where that meta may have changed since the post was published. Also needed for non-resized external images where we don't know the width and height.editWidth
holds the block width inside the editor at the moment the post was created/edited. Can be used to scale images on the front-end when the block widths don't match (in themes with fluid width or after changing the theme).userSet
indicated when the user has set the width and height manually. In these cases the theme should (probably) honor the settings when scaling.editWidth
when scaling images on the front-end. Also images "line-up" when setting them to the same scale.render_block
filter to add attributes to the img tag on the front-end, includingsrcset
andsizes
.