thumbnail: Add a data-original-dimensions attribute.

This allows clients to potentially lay out the thumbnails more
intelligently, or to provide a better "progressive-load" experience
when enlarging the thumbnail.
This commit is contained in:
Alex Vandiver 2024-07-22 21:16:03 +00:00 committed by Alex Vandiver
parent 65828b20e9
commit 2ea0cc0005
5 changed files with 33 additions and 16 deletions

View File

@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 9.0 ## Changes in Zulip 9.0
**Feature level 276**
* [Markdown message formatting](/api/message-formatting#image-previews):
Image preview elements not contain a `data-original-dimensions`
attribute containing the dimensions of the original image.
**Feature level 275** **Feature level 275**
* [`POST /register`](/api/register-queue), [`PATCH * [`POST /register`](/api/register-queue), [`PATCH

View File

@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# new level means in api_docs/changelog.md, as well as "**Changes**" # new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`. # entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 275 # Last bumped for `web_animate_image_previews` setting. API_FEATURE_LEVEL = 276 # Last bumped for data-original-dimensions
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision

View File

@ -329,6 +329,8 @@ def split_thumbnail_path(file_path: str) -> tuple[str, BaseThumbnailFormat]:
class MarkdownImageMetadata: class MarkdownImageMetadata:
url: str url: str
is_animated: bool is_animated: bool
original_width_px: int
original_height_px: int
def get_user_upload_previews( def get_user_upload_previews(
@ -347,6 +349,8 @@ def get_user_upload_previews(
upload_preview_data[image_attachment.path_id] = MarkdownImageMetadata( upload_preview_data[image_attachment.path_id] = MarkdownImageMetadata(
url=url, url=url,
is_animated=is_animated, is_animated=is_animated,
original_width_px=image_attachment.original_width_px,
original_height_px=image_attachment.original_height_px,
) )
return upload_preview_data return upload_preview_data
@ -421,6 +425,9 @@ def rewrite_thumbnailed_images(
changed = True changed = True
del image_tag["class"] del image_tag["class"]
image_tag["src"] = image_data.url image_tag["src"] = image_data.url
image_tag["data-original-dimensions"] = (
f"{image_data.original_width_px}x{image_data.original_height_px}"
)
if image_data.is_animated: if image_data.is_animated:
image_tag["data-animated"] = "true" image_tag["data-animated"] = "true"

View File

@ -73,15 +73,15 @@ class MarkdownThumbnailTest(ZulipTestCase):
"<p>Test 1<br>\n" "<p>Test 1<br>\n"
f'<a href="/user_uploads/{path_ids[0]}">{image_names[0]}</a> </p>\n' f'<a href="/user_uploads/{path_ids[0]}">{image_names[0]}</a> </p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{path_ids[0]}" title="{image_names[0]}">' f'<div class="message_inline_image"><a href="/user_uploads/{path_ids[0]}" title="{image_names[0]}">'
f'<img src="/user_uploads/thumbnail/{path_ids[0]}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{path_ids[0]}/840x560.webp"></a></div>'
"<p>Next image<br>\n" "<p>Next image<br>\n"
f'<a href="/user_uploads/{path_ids[1]}">{image_names[1]}</a> </p>\n' f'<a href="/user_uploads/{path_ids[1]}">{image_names[1]}</a> </p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{path_ids[1]}" title="{image_names[1]}">' f'<div class="message_inline_image"><a href="/user_uploads/{path_ids[1]}" title="{image_names[1]}">'
f'<img src="/user_uploads/thumbnail/{path_ids[1]}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{path_ids[1]}/840x560.webp"></a></div>'
"<p>Another screenshot<br>\n" "<p>Another screenshot<br>\n"
f'<a href="/user_uploads/{path_ids[2]}">{image_names[2]}</a></p>\n' f'<a href="/user_uploads/{path_ids[2]}">{image_names[2]}</a></p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{path_ids[2]}" title="{image_names[2]}">' f'<div class="message_inline_image"><a href="/user_uploads/{path_ids[2]}" title="{image_names[2]}">'
f'<img src="/user_uploads/thumbnail/{path_ids[2]}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{path_ids[2]}/840x560.webp"></a></div>'
), ),
) )
@ -124,7 +124,7 @@ class MarkdownThumbnailTest(ZulipTestCase):
expected = ( expected = (
f'<p><a href="/user_uploads/{path_id}">image</a></p>\n' f'<p><a href="/user_uploads/{path_id}">image</a></p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="image">' f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="image">'
f'<img src="/user_uploads/thumbnail/{path_id}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{path_id}/840x560.webp"></a></div>'
) )
self.assert_message_content_is(message_id, expected) self.assert_message_content_is(message_id, expected)
@ -142,7 +142,8 @@ class MarkdownThumbnailTest(ZulipTestCase):
message_id = self.send_message_content(f"[I am 95% ± 5% certain!](/user_uploads/{path_id})") message_id = self.send_message_content(f"[I am 95% ± 5% certain!](/user_uploads/{path_id})")
expected = ( expected = (
f'<p><a href="/user_uploads/{path_id}">I am 95% &plusmn; 5% certain!</a></p>\n' f'<p><a href="/user_uploads/{path_id}">I am 95% &plusmn; 5% certain!</a></p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="I am 95% &plusmn; 5% certain!"><img src="/user_uploads/thumbnail/{path_id}/840x560.webp"></a></div>' f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="I am 95% &plusmn; 5% certain!">'
f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{path_id}/840x560.webp"></a></div>'
) )
self.assert_message_content_is(message_id, expected) self.assert_message_content_is(message_id, expected)
@ -157,12 +158,13 @@ class MarkdownThumbnailTest(ZulipTestCase):
ThumbnailFormat("webp", 100, 75, animated=True), ThumbnailFormat("webp", 100, 75, animated=True),
ThumbnailFormat("webp", 100, 75, animated=False), ThumbnailFormat("webp", 100, 75, animated=False),
): ):
path_id = self.upload_and_thumbnail_image("animated_img.gif") path_id = self.upload_and_thumbnail_image("animated_unequal_img.gif")
content = f"[animated_img.gif](/user_uploads/{path_id})" content = f"[animated_unequal_img.gif](/user_uploads/{path_id})"
expected = ( expected = (
f'<p><a href="/user_uploads/{path_id}">animated_img.gif</a></p>\n' f'<p><a href="/user_uploads/{path_id}">animated_unequal_img.gif</a></p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="animated_img.gif">' f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="animated_unequal_img.gif">'
f'<img data-animated="true" src="/user_uploads/thumbnail/{path_id}/100x75-anim.webp"></a></div>' '<img data-animated="true" data-original-dimensions="128x56"'
f' src="/user_uploads/thumbnail/{path_id}/100x75-anim.webp"></a></div>'
) )
message_id = self.send_message_content(content, do_thumbnail=True) message_id = self.send_message_content(content, do_thumbnail=True)
self.assert_message_content_is(message_id, expected) self.assert_message_content_is(message_id, expected)
@ -208,7 +210,7 @@ class MarkdownThumbnailTest(ZulipTestCase):
f'<div class="message_inline_image"><a href="/user_uploads/{first_path_id}" title="first image">' f'<div class="message_inline_image"><a href="/user_uploads/{first_path_id}" title="first image">'
'<img class="image-loading-placeholder" src="/static/images/loading/loader-black.svg"></a></div>' '<img class="image-loading-placeholder" src="/static/images/loading/loader-black.svg"></a></div>'
f'<div class="message_inline_image"><a href="/user_uploads/{second_path_id}" title="second image">' f'<div class="message_inline_image"><a href="/user_uploads/{second_path_id}" title="second image">'
f'<img src="/user_uploads/thumbnail/{second_path_id}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{second_path_id}/840x560.webp"></a></div>'
), ),
) )
@ -220,9 +222,9 @@ class MarkdownThumbnailTest(ZulipTestCase):
f'<p><a href="/user_uploads/{first_path_id}">first image</a><br>\n' f'<p><a href="/user_uploads/{first_path_id}">first image</a><br>\n'
f'<a href="/user_uploads/{second_path_id}">second image</a></p>\n' f'<a href="/user_uploads/{second_path_id}">second image</a></p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{first_path_id}" title="first image">' f'<div class="message_inline_image"><a href="/user_uploads/{first_path_id}" title="first image">'
f'<img src="/user_uploads/thumbnail/{first_path_id}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{first_path_id}/840x560.webp"></a></div>'
f'<div class="message_inline_image"><a href="/user_uploads/{second_path_id}" title="second image">' f'<div class="message_inline_image"><a href="/user_uploads/{second_path_id}" title="second image">'
f'<img src="/user_uploads/thumbnail/{second_path_id}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{second_path_id}/840x560.webp"></a></div>'
), ),
) )
@ -247,7 +249,7 @@ class MarkdownThumbnailTest(ZulipTestCase):
expected = ( expected = (
f'<p><a href="/user_uploads/{path_id}">image</a></p>\n' f'<p><a href="/user_uploads/{path_id}">image</a></p>\n'
f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="image">' f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="image">'
f'<img src="/user_uploads/thumbnail/{path_id}/840x560.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{path_id}/840x560.webp"></a></div>'
) )
self.assertEqual( self.assertEqual(
ArchivedMessage.objects.get(id=message_id).rendered_content, ArchivedMessage.objects.get(id=message_id).rendered_content,
@ -320,7 +322,7 @@ class MarkdownThumbnailTest(ZulipTestCase):
rendered_thumb = ( rendered_thumb = (
f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="image">' f'<div class="message_inline_image"><a href="/user_uploads/{path_id}" title="image">'
f'<img src="/user_uploads/thumbnail/{path_id}/100x75.webp"></a></div>' f'<img data-original-dimensions="128x128" src="/user_uploads/thumbnail/{path_id}/100x75.webp"></a></div>'
) )
self.assert_message_content_is( self.assert_message_content_is(

View File

@ -147,6 +147,8 @@ def ensure_thumbnails(image_attachment: ImageAttachment) -> int:
MarkdownImageMetadata( MarkdownImageMetadata(
url=url, url=url,
is_animated=is_animated, is_animated=is_animated,
original_width_px=image_attachment.original_width_px,
original_height_px=image_attachment.original_height_px,
), ),
) )
return written_images return written_images