media: Replace legacy .message_inline_image class.

This introduces two new replacement classes, depending on
whether the inner content is an image or a video.
This commit is contained in:
Karl Stolley
2025-09-17 12:32:41 -05:00
committed by Tim Abbott
parent 1b3ea708be
commit a64276c58f
10 changed files with 65 additions and 41 deletions

View File

@@ -117,7 +117,7 @@ export function initialize(): void {
// Inline image, video and twitter previews. // Inline image, video and twitter previews.
if ( if (
$target.is("img.message_inline_image") || $target.is(".media-image-element") ||
$target.is(".message_inline_animated_image_still") || $target.is(".message_inline_animated_image_still") ||
$target.is("video") || $target.is("video") ||
$target.is(".message_inline_video") || $target.is(".message_inline_video") ||

View File

@@ -241,7 +241,7 @@ export function canonical_url_of_media(media: HTMLMediaElement | HTMLImageElemen
export function render_lightbox_media_list(): void { export function render_lightbox_media_list(): void {
if (!is_open) { if (!is_open) {
const message_media_list = $<HTMLMediaElement | HTMLImageElement>( const message_media_list = $<HTMLMediaElement | HTMLImageElement>(
".focused-message-list .message_inline_image img, .focused-message-list .message_inline_video video", ".focused-message-list .message-media-preview-image img, .focused-message-list .message_inline_video video",
).toArray(); ).toArray();
const $lightbox_media_list = $("#lightbox_overlay .image-list").empty(); const $lightbox_media_list = $("#lightbox_overlay .image-list").empty();
for (const media of message_media_list) { for (const media of message_media_list) {
@@ -413,7 +413,8 @@ export function show_from_selected_message(): void {
const $message_selected = $(".selected_message"); const $message_selected = $(".selected_message");
let $message = $message_selected; let $message = $message_selected;
// This is a function to satisfy eslint unicorn/no-array-callback-reference // This is a function to satisfy eslint unicorn/no-array-callback-reference
const media_classes = (): string => ".message_inline_image img, .message_inline_image video"; const media_classes = (): string =>
".message-media-preview-image img, .message-media-preview-video video";
let $media = $message.find<HTMLMediaElement | HTMLImageElement>(media_classes()); let $media = $message.find<HTMLMediaElement | HTMLImageElement>(media_classes());
let $prev_traverse = false; let $prev_traverse = false;
@@ -644,7 +645,7 @@ export function initialize(): void {
$("#main_div, #compose .preview_content").on( $("#main_div, #compose .preview_content").on(
"click", "click",
".message_inline_image:not(.message_inline_video) a, .message_inline_animated_image_still", ".message-media-preview-image:not(.message_inline_video) a, .message_inline_animated_image_still",
function (e) { function (e) {
// prevent the link from opening in a new page. // prevent the link from opening in a new page.
e.preventDefault(); e.preventDefault();

View File

@@ -120,13 +120,13 @@ export function initialize(): void {
$("#main_div").on( $("#main_div").on(
"mouseover", "mouseover",
'.message-list div.message_inline_image img[data-animated="true"]', '.message-list .message-media-preview-image img[data-animated="true"]',
function (this: HTMLElement) { function (this: HTMLElement) {
if (user_settings.web_animate_image_previews !== "on_hover") { if (user_settings.web_animate_image_previews !== "on_hover") {
return; return;
} }
const $img = $(this); const $img = $(this);
$img.closest(".message_inline_image").removeClass( $img.closest(".message-media-preview-image").removeClass(
"message_inline_animated_image_still", "message_inline_animated_image_still",
); );
$img.attr( $img.attr(
@@ -138,13 +138,15 @@ export function initialize(): void {
$("#main_div").on( $("#main_div").on(
"mouseout", "mouseout",
'.message-list div.message_inline_image img[data-animated="true"]', '.message-list .message-media-preview-image img[data-animated="true"]',
function (this: HTMLElement) { function (this: HTMLElement) {
if (user_settings.web_animate_image_previews !== "on_hover") { if (user_settings.web_animate_image_previews !== "on_hover") {
return; return;
} }
const $img = $(this); const $img = $(this);
$img.closest(".message_inline_image").addClass("message_inline_animated_image_still"); $img.closest(".message-media-preview-image").addClass(
"message_inline_animated_image_still",
);
$img.attr( $img.attr(
"src", "src",
$img.attr("src")!.replace(/\/[^/]+$/, "/" + thumbnail.preferred_format.name), $img.attr("src")!.replace(/\/[^/]+$/, "/" + thumbnail.preferred_format.name),

View File

@@ -341,13 +341,13 @@ export function initialize(): void {
}, },
}); });
message_list_tooltip(".message_inline_image > a > img", { message_list_tooltip(".media-image-element", {
// Add a short delay so the user can mouseover several inline images without // Add a short delay so the user can mouseover several inline images without
// tooltips showing and hiding rapidly // tooltips showing and hiding rapidly
delay: [300, 20], delay: [300, 20],
onShow(instance) { onShow(instance) {
// Some message_inline_images aren't actually images with a title, // Some message images do not include a title, such as YouTube
// for example youtube videos, so we default to the actual href // video previews, so we fall back to displaying the href value
const title = const title =
$(instance.reference).parent().attr("aria-label") ?? $(instance.reference).parent().attr("aria-label") ??
$(instance.reference).parent().attr("href"); $(instance.reference).parent().attr("href");

View File

@@ -1038,9 +1038,9 @@ export class MessageListView {
if (page_params.is_spectator) { if (page_params.is_spectator) {
// For images that fail to load due to being rate limited or being denied access // For images that fail to load due to being rate limited or being denied access
// by server in general, we tell user to login to be able to view the image. // by server in general, we tell user to login to be able to view the image.
$message_rows.find(".message_inline_image img").on("error", (e) => { $message_rows.find(".media-image-element").on("error", (e) => {
$(e.target) $(e.target)
.closest(".message_inline_image") .closest(".message-media-preview-image")
.replaceWith($(render_login_to_view_image_button())); .replaceWith($(render_login_to_view_image_button()));
}); });
} }

View File

@@ -17,7 +17,7 @@ export function message_has_link(message_content: string): boolean {
} }
export function message_has_image(message_content: string): boolean { export function message_has_image(message_content: string): boolean {
return is_element_in_message_content(message_content, ".message_inline_image"); return is_element_in_message_content(message_content, ".message-media-preview-image");
} }
export function message_has_attachment(message_content: string): boolean { export function message_has_attachment(message_content: string): boolean {

View File

@@ -53,12 +53,25 @@ export function postprocess_content(html: string): string {
} }
if (elt.querySelector("img") || elt.querySelector("video")) { if (elt.querySelector("img") || elt.querySelector("video")) {
// Rewrite the legacy .message_inline_image class, whose name would add
// confusion when Zulip supports inline images via standard Markdown.
// We further adjust this class below for when the element contains a
// video.
elt.parentElement?.classList.replace(
"message_inline_image",
"message-media-preview-image",
);
// We want a class to refer to media links // We want a class to refer to media links
elt.classList.add("media-anchor-element"); elt.classList.add("media-anchor-element");
// Add a class to the video, if it exists, including // Add a class to the video, if it exists, including
// the .media-image-element class for properly treating // the .media-image-element class for properly treating
// video thumbnails // video thumbnails
if (elt.querySelector("video")) { if (elt.querySelector("video")) {
// We use a different class name to distinguish videos from images
elt.parentElement?.classList.replace(
"message-media-preview-image",
"message-media-preview-video",
);
elt elt
.querySelector("video") .querySelector("video")
?.classList.add("media-video-element", "media-image-element"); ?.classList.add("media-video-element", "media-image-element");
@@ -86,7 +99,10 @@ export function postprocess_content(html: string): string {
elt.classList.add("message-embed-title-link"); elt.classList.add("message-embed-title-link");
} }
if (elt.parentElement?.classList.contains("message_inline_image")) { if (
elt.parentElement?.classList.contains("message-media-preview-image") ||
elt.parentElement?.classList.contains("message-media-preview-video")
) {
// For inline images we want to handle the tooltips explicitly, and disable // For inline images we want to handle the tooltips explicitly, and disable
// the browser's built in handling of the title attribute. // the browser's built in handling of the title attribute.
const title = elt.getAttribute("title"); const title = elt.getAttribute("title");
@@ -210,7 +226,7 @@ export function postprocess_content(html: string): string {
} }
for (const inline_img of template.content.querySelectorAll<HTMLImageElement>( for (const inline_img of template.content.querySelectorAll<HTMLImageElement>(
"div.message_inline_image > a > img", ".message-media-preview-image img",
)) { )) {
inline_img.setAttribute("loading", "lazy"); inline_img.setAttribute("loading", "lazy");
// We can't just check whether `inline_image.src` starts with // We can't just check whether `inline_image.src` starts with
@@ -224,7 +240,7 @@ export function postprocess_content(html: string): string {
// If the image source URL can't be parsed, likely due to // If the image source URL can't be parsed, likely due to
// some historical bug in the Markdown processor, just // some historical bug in the Markdown processor, just
// drop the invalid image element. // drop the invalid image element.
inline_img.closest("div.message_inline_image")!.remove(); inline_img.closest(".message-media-preview-image")!.remove();
continue; continue;
} }
@@ -246,7 +262,7 @@ export function postprocess_content(html: string): string {
// If we're showing a still thumbnail, show a play // If we're showing a still thumbnail, show a play
// button so that users that it can be played. // button so that users that it can be played.
inline_img inline_img
.closest(".message_inline_image")! .closest(".message-media-preview-image")!
.classList.add("message_inline_animated_image_still"); .classList.add("message_inline_animated_image_still");
} }
} }
@@ -255,10 +271,10 @@ export function postprocess_content(html: string): string {
} }
// After all other processing on images has been done, we look for // After all other processing on images has been done, we look for
// adjacent images and tuck them structurally into galleries. // adjacent images and videos, and tuck them structurally into galleries.
// This will also process uploaded video thumbnails, which likewise for (const elt of template.content.querySelectorAll(
// take the `.message_inline_image` class ".message-media-preview-image, .message-media-preview-video",
for (const elt of template.content.querySelectorAll(".message_inline_image")) { )) {
let gallery_element; let gallery_element;
const is_part_of_open_gallery = elt.previousElementSibling?.classList.contains( const is_part_of_open_gallery = elt.previousElementSibling?.classList.contains(

View File

@@ -505,7 +505,8 @@
} }
.twitter-image, .twitter-image,
.message_inline_image { .message-media-preview-image,
.message-media-preview-video {
/* Set a background for the image; the background will be visible /* Set a background for the image; the background will be visible
behind the width of the transparent border. */ behind the width of the transparent border. */
border: solid 3px transparent; border: solid 3px transparent;
@@ -595,7 +596,7 @@
border: none !important; border: none !important;
} }
.message_inline_image .media-image-element { .media-image-element {
cursor: zoom-in; cursor: zoom-in;
} }

View File

@@ -1330,7 +1330,7 @@ test("predicate_basics", ({override}) => {
const img_msg = { const img_msg = {
content: content:
'<p><a href="/user_uploads/randompath/test.jpeg">test.jpeg</a></p><div class="message_inline_image"><a href="/user_uploads/randompath/test.jpeg" title="test.jpeg"><img src="/user_uploads/randompath/test.jpeg"></a></div>', '<p><a href="/user_uploads/randompath/test.jpeg">test.jpeg</a></p><div class="message-media-preview-image"><a href="/user_uploads/randompath/test.jpeg" title="test.jpeg"><img src="/user_uploads/randompath/test.jpeg"></a></div>',
}; };
const link_msg = { const link_msg = {
@@ -1403,13 +1403,17 @@ test("predicate_basics", ({override}) => {
assert.ok(!has_attachment(no_has_filter_matching_msg)); assert.ok(!has_attachment(no_has_filter_matching_msg));
const has_image = get_predicate([["has", "image"]]); const has_image = get_predicate([["has", "image"]]);
set_find_results_for_msg_content(img_msg, ".message_inline_image", ["stub"]); set_find_results_for_msg_content(img_msg, ".message-media-preview-image", ["stub"]);
assert.ok(has_image(img_msg)); assert.ok(has_image(img_msg));
set_find_results_for_msg_content(non_img_attachment_msg, ".message_inline_image", false); set_find_results_for_msg_content(non_img_attachment_msg, ".message-media-preview-image", false);
assert.ok(!has_image(non_img_attachment_msg)); assert.ok(!has_image(non_img_attachment_msg));
set_find_results_for_msg_content(link_msg, ".message_inline_image", false); set_find_results_for_msg_content(link_msg, ".message-media-preview-image", false);
assert.ok(!has_image(link_msg)); assert.ok(!has_image(link_msg));
set_find_results_for_msg_content(no_has_filter_matching_msg, ".message_inline_image", false); set_find_results_for_msg_content(
no_has_filter_matching_msg,
".message-media-preview-image",
false,
);
assert.ok(!has_image(no_has_filter_matching_msg)); assert.ok(!has_image(no_has_filter_matching_msg));
const has_reaction = get_predicate([["has", "reaction"]]); const has_reaction = get_predicate([["has", "reaction"]]);

View File

@@ -59,12 +59,12 @@ run_test("postprocess_media_and_embeds", () => {
"</div>", "</div>",
), ),
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image message_inline_video">' + '<div class="message-media-preview-video message_inline_video">' +
'<a href="http://zulip.zulipdev.com/user_uploads/w/ha/tever/inline.mp4" target="_blank" rel="noopener noreferrer" class="media-anchor-element">' + '<a href="http://zulip.zulipdev.com/user_uploads/w/ha/tever/inline.mp4" target="_blank" rel="noopener noreferrer" class="media-anchor-element">' +
'<video src="http://zulip.zulipdev.com/user_uploads/w/ha/tever/inline.mp4" class="media-video-element media-image-element"></video>' + '<video src="http://zulip.zulipdev.com/user_uploads/w/ha/tever/inline.mp4" class="media-video-element media-image-element"></video>' +
"</a>" + "</a>" +
"</div>" + "</div>" +
'<div class="youtube-video message_inline_image">' + '<div class="youtube-video message-media-preview-image">' +
'<a class="media-anchor-element" href="https://www.youtube.com/watch?v=tyKJueEk0XM" target="_blank" rel="noopener noreferrer">' + '<a class="media-anchor-element" href="https://www.youtube.com/watch?v=tyKJueEk0XM" target="_blank" rel="noopener noreferrer">' +
'<img src="https://i.ytimg.com/vi/tyKJueEk0XM/mqdefault.jpg" class="media-image-element" loading="lazy">' + '<img src="https://i.ytimg.com/vi/tyKJueEk0XM/mqdefault.jpg" class="media-image-element" loading="lazy">' +
"</a>" + "</a>" +
@@ -122,12 +122,12 @@ run_test("inline_image_galleries", ({override}) => {
), ),
"<p>Message text</p>" + "<p>Message text</p>" +
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' + '<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="1000x2000" src="/user_uploads/thumbnail/path/to/image.png/840x560.webp" class="media-image-element portrait-thumbnail" width="1000" height="2000" style="width: 5em;" loading="lazy">' + '<img data-original-dimensions="1000x2000" src="/user_uploads/thumbnail/path/to/image.png/840x560.webp" class="media-image-element portrait-thumbnail" width="1000" height="2000" style="width: 5em;" loading="lazy">' +
"</a>" + "</a>" +
"</div>" + "</div>" +
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' + '<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="2000x1000" src="/user_uploads/thumbnail/path/to/image.png/840x560.webp" class="media-image-element landscape-thumbnail" width="2000" height="1000" style="width: 20em;" loading="lazy">' + '<img data-original-dimensions="2000x1000" src="/user_uploads/thumbnail/path/to/image.png/840x560.webp" class="media-image-element landscape-thumbnail" width="2000" height="1000" style="width: 20em;" loading="lazy">' +
"</a>" + "</a>" +
@@ -135,7 +135,7 @@ run_test("inline_image_galleries", ({override}) => {
"</div>" + "</div>" +
"<p>Message text</p>" + "<p>Message text</p>" +
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' + '<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="1000x1000" src="/user_uploads/thumbnail/path/to/image.png/840x560.webp" class="media-image-element portrait-thumbnail" width="1000" height="1000" style="width: 10em;" loading="lazy">' + '<img data-original-dimensions="1000x1000" src="/user_uploads/thumbnail/path/to/image.png/840x560.webp" class="media-image-element portrait-thumbnail" width="1000" height="1000" style="width: 10em;" loading="lazy">' +
"</a>" + "</a>" +
@@ -196,7 +196,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
"</div>", "</div>",
), ),
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' + '<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 landscape-thumbnail" width="3264" height="2448" style="width: 13.333333333333334em;" 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" style="width: 13.333333333333334em;" loading="lazy">' +
"</a>" + "</a>" +
@@ -214,7 +214,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
"</div>", "</div>",
), ),
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' + '<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" style="width: 5em;" loading="lazy">' + '<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" style="width: 5em;" loading="lazy">' +
"</a>" + "</a>" +
@@ -232,7 +232,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
"</div>", "</div>",
), ),
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' + '<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="1x10" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" class="media-image-element dinky-thumbnail extreme-aspect-ratio portrait-thumbnail" width="1" height="10" style="width: 1px;" loading="lazy">' + '<img data-original-dimensions="1x10" src="/user_uploads/thumbnail/path/to/image.png/300x200.webp" class="media-image-element dinky-thumbnail extreme-aspect-ratio portrait-thumbnail" width="1" height="10" style="width: 1px;" loading="lazy">' +
"</a>" + "</a>" +
@@ -251,7 +251,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
"</div>", "</div>",
), ),
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="/user_uploads/path/to/image.png" target="_blank" rel="noopener noreferrer" class="media-anchor-element" aria-label="image.png">' + '<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 landscape-thumbnail" width="3264" height="2448" style="width: 13.333333333333334em;" 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" style="width: 13.333333333333334em;" loading="lazy">' +
"</a>" + "</a>" +
@@ -270,7 +270,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
"</div>", "</div>",
), ),
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image message_inline_animated_image_still">' + '<div class="message-media-preview-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">' + '<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 landscape-thumbnail" width="3264" height="2448" style="width: 13.333333333333334em;" 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" style="width: 13.333333333333334em;" loading="lazy">' +
"</a>" + "</a>" +
@@ -288,7 +288,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
"</div>", "</div>",
), ),
'<div class="message-thumbnail-gallery">' + '<div class="message-thumbnail-gallery">' +
'<div class="message_inline_image message_inline_animated_image_still">' + '<div class="message-media-preview-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">' + '<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 landscape-thumbnail" width="3264" height="2448" style="width: 13.333333333333334em;" 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" style="width: 13.333333333333334em;" loading="lazy">' +
"</a>" + "</a>" +
@@ -301,7 +301,7 @@ run_test("message_inline_animated_image_still", ({override}) => {
// history. // history.
assert.equal( assert.equal(
postprocess_content( postprocess_content(
'<div class="message_inline_image">' + '<div class="message-media-preview-image">' +
'<a href="https://zulip.%20[Click%20to%20join%20video%20call](https://meeting.example.com/abcd1234)%20example.com/user_uploads/2/ab/abcd1234/image.png" target="_blank" title="image.png">' + '<a href="https://zulip.%20[Click%20to%20join%20video%20call](https://meeting.example.com/abcd1234)%20example.com/user_uploads/2/ab/abcd1234/image.png" target="_blank" title="image.png">' +
'<img src="https://zulip.%20[Click%20to%20join%20video%20call](https://meeting.example.com/abcd1234)%20example.com/user_uploads/2/ab/abcd1234/image.png">' + '<img src="https://zulip.%20[Click%20to%20join%20video%20call](https://meeting.example.com/abcd1234)%20example.com/user_uploads/2/ab/abcd1234/image.png">' +
"</a>" + "</a>" +