thumbnails: Add height and width attributes to images.

This commit is contained in:
Karl Stolley
2025-03-28 10:37:50 -04:00
committed by Tim Abbott
parent 4e19e82f64
commit d22c6318db
2 changed files with 54 additions and 4 deletions

View File

@@ -92,6 +92,39 @@ export function postprocess_content(html: string): string {
elt.setAttribute("aria-label", title);
elt.removeAttribute("title");
}
// To prevent layout shifts and flexibly size image previews,
// we read the image's original dimensions, when present, and
// set those values as `height` and `width` attributes on the
// image source.
const inline_image = elt.querySelector("img");
if (inline_image?.hasAttribute("data-original-dimensions")) {
// TODO: Modify eslint config, if we wish to avoid dataset
//
/* eslint unicorn/prefer-dom-node-dataset: "off" */
const original_dimensions_attribute = inline_image.getAttribute(
"data-original-dimensions",
);
assert(original_dimensions_attribute);
const original_dimensions: string[] = original_dimensions_attribute.split("x");
assert(
original_dimensions.length === 2 &&
typeof original_dimensions[0] === "string" &&
typeof original_dimensions[1] === "string",
);
const original_width = Number(original_dimensions[0]);
const original_height = Number(original_dimensions[1]);
const is_portrait_image = original_width <= original_height;
inline_image.setAttribute("width", `${original_width}`);
inline_image.setAttribute("height", `${original_height}`);
if (is_portrait_image) {
inline_image.classList.add("portrait-thumbnail");
} else {
inline_image.classList.add("landscape-thumbnail");
}
}
} else {
// For non-image user uploads, the following block ensures that the title
// attribute always displays the filename as a security measure.

View File

@@ -107,6 +107,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
override(thumbnail, "preferred_format", thumbnail_formats[3]);
override(thumbnail, "animated_format", thumbnail_formats[2]);
// Test for landscape thumbnails
assert.equal(
postprocess_content(
'<div class="message_inline_image">' +
@@ -117,7 +118,23 @@ run_test("message_inline_animated_image_still", ({override}) => {
),
'<div class="message_inline_image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" class="media-image-element" loading="lazy">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" class="media-image-element landscape-thumbnail" width="3264" height="2448" loading="lazy">' +
"</a>" +
"</div>",
);
// Test for portrait thumbnails
assert.equal(
postprocess_content(
'<div class="message_inline_image">' +
'<a href="/user_uploads/path/to/image.png" title="image.png">' +
'<img data-original-dimensions="100x200" src="/user_uploads/thumbnail/path/to/image.png/840x560.webp">' +
"</a>" +
"</div>",
),
'<div class="message_inline_image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' +
'<img data-original-dimensions="100x200" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" class="media-image-element portrait-thumbnail" width="100" height="200" loading="lazy">' +
"</a>" +
"</div>",
);
@@ -134,7 +151,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
),
'<div class="message_inline_image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200-anim.webp" data-animated="true" class="media-image-element" loading="lazy">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200-anim.webp" data-animated="true" class="media-image-element landscape-thumbnail" width="3264" height="2448" loading="lazy">' +
"</a>" +
"</div>",
);
@@ -151,7 +168,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
),
'<div class="message_inline_image message_inline_animated_image_still">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" data-animated="true" class="media-image-element" loading="lazy">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" data-animated="true" class="media-image-element landscape-thumbnail" width="3264" height="2448" loading="lazy">' +
"</a>" +
"</div>",
);
@@ -167,7 +184,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
),
'<div class="message_inline_image message_inline_animated_image_still">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" data-animated="true" class="media-image-element" loading="lazy">' +
'<img data-original-dimensions="3264x2448" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" data-animated="true" class="media-image-element landscape-thumbnail" width="3264" height="2448" loading="lazy">' +
"</a>" +
"</div>",
);