diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 6eb250d328..dba6d76e08 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 9.0 +**Feature level 275** + +* [`POST /register`](/api/register-queue), [`PATCH + /settings`](/api/update-settings), [`PATCH + /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults): + Added new `web_animate_image_previews` setting, which controls how + animated images should be played in the web/desktop app message feed. + **Feature level 274** * [`GET /events`](/api/get-events): `delete_message` events are now diff --git a/version.py b/version.py index de9464f90e..cdc2fce7a0 100644 --- a/version.py +++ b/version.py @@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 274 # Last bumped for `delete_message` event. +API_FEATURE_LEVEL = 275 # Last bumped for `web_animate_image_previews` setting. # Bump the minor PROVISION_VERSION to indicate that folks should provision diff --git a/web/src/admin.js b/web/src/admin.js index dc875f2ec1..2bbee868bb 100644 --- a/web/src/admin.js +++ b/web/src/admin.js @@ -194,6 +194,7 @@ export function build_page() { user_list_style_values: settings_config.user_list_style_values, web_stream_unreads_count_display_policy_values: settings_config.web_stream_unreads_count_display_policy_values, + web_animate_image_previews_values: settings_config.web_animate_image_previews_values, color_scheme_values: settings_config.color_scheme_values, web_home_view_values: settings_config.web_home_view_values, settings_object: realm_user_settings_defaults, diff --git a/web/src/click_handlers.js b/web/src/click_handlers.js index 61ce74c12f..2a1fa151e9 100644 --- a/web/src/click_handlers.js +++ b/web/src/click_handlers.js @@ -119,6 +119,7 @@ export function initialize() { // Inline image, video and twitter previews. if ( $target.is("img.message_inline_image") || + $target.is(".message_inline_animated_image_still") || $target.is("video") || $target.is(".message_inline_video") || $target.is("img.twitter-avatar") diff --git a/web/src/lightbox.ts b/web/src/lightbox.ts index 35eb9699ca..b6ff0e3b45 100644 --- a/web/src/lightbox.ts +++ b/web/src/lightbox.ts @@ -580,7 +580,7 @@ export function initialize(): void { $("#main_div, #compose .preview_content").on( "click", - ".message_inline_image:not(.message_inline_video) a", + ".message_inline_image:not(.message_inline_video) a, .message_inline_animated_image_still", function (e) { // prevent the link from opening in a new page. e.preventDefault(); diff --git a/web/src/message_list_hover.js b/web/src/message_list_hover.js index 9400a7ea3a..a59926f764 100644 --- a/web/src/message_list_hover.js +++ b/web/src/message_list_hover.js @@ -6,6 +6,8 @@ import render_edit_content_button from "../templates/edit_content_button.hbs"; import * as message_edit from "./message_edit"; import * as message_lists from "./message_lists"; import * as rows from "./rows"; +import * as thumbnail from "./thumbnail"; +import {user_settings} from "./user_settings"; let $current_message_hover; export function message_unhover() { @@ -73,6 +75,40 @@ export function initialize() { $row.removeClass("sender_info_hovered"); }); + $("#main_div").on( + "mouseover", + '.message-list div.message_inline_image img[data-animated="true"]', + function () { + if (user_settings.web_animate_image_previews !== "on_hover") { + return; + } + const $img = $(this); + $img.closest(".message_inline_image").removeClass( + "message_inline_animated_image_still", + ); + $img.attr( + "src", + $img.attr("src").replace(/\/[^/]+$/, "/" + thumbnail.animated_format.name), + ); + }, + ); + + $("#main_div").on( + "mouseout", + '.message-list div.message_inline_image img[data-animated="true"]', + function () { + if (user_settings.web_animate_image_previews !== "on_hover") { + return; + } + const $img = $(this); + $img.closest(".message_inline_image").addClass("message_inline_animated_image_still"); + $img.attr( + "src", + $img.attr("src").replace(/\/[^/]+$/, "/" + thumbnail.preferred_format.name), + ); + }, + ); + function handle_video_preview_mouseenter($elem) { // Set image height and css vars for play button position, if not done already const setPosition = !$elem.data("entered-before"); diff --git a/web/src/realm_user_settings_defaults.ts b/web/src/realm_user_settings_defaults.ts index e67562501b..ffbd4eb313 100644 --- a/web/src/realm_user_settings_defaults.ts +++ b/web/src/realm_user_settings_defaults.ts @@ -52,6 +52,7 @@ export const realm_default_settings_schema = z.object({ translate_emoticons: z.boolean(), twenty_four_hour_time: z.boolean(), user_list_style: z.number(), + web_animate_image_previews: z.string(), web_channel_default_view: z.number(), web_escape_navigates_to_home_view: z.boolean(), web_font_size_px: z.number(), diff --git a/web/src/rendered_markdown.ts b/web/src/rendered_markdown.ts index a96e0c1e8a..f9fc2cd75d 100644 --- a/web/src/rendered_markdown.ts +++ b/web/src/rendered_markdown.ts @@ -345,7 +345,21 @@ export const update_elements = ($content: JQuery): void => { const $inline_img_thumbnail = $(this); let thumbnail_name = thumbnail.preferred_format.name; if ($inline_img_thumbnail.attr("data-animated") === "true") { - thumbnail_name = thumbnail.animated_format.name; + if ( + user_settings.web_animate_image_previews === "always" || + // Treat on_hover as "always" on mobile web, where + // hovering is impossible and there's much less on + // the screen. + (user_settings.web_animate_image_previews === "on_hover" && util.is_mobile()) + ) { + thumbnail_name = thumbnail.animated_format.name; + } else { + // If we're showing a still thumbnail, show a play + // button so that users that it can be played. + $inline_img_thumbnail + .closest(".message_inline_image") + .addClass("message_inline_animated_image_still"); + } } $inline_img_thumbnail.attr( "src", diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index baccea2ac8..3df1434fe3 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -728,6 +728,7 @@ export function dispatch_normal_event(event) { "translate_emoticons", "display_emoji_reaction_users", "user_list_style", + "web_animate_image_previews", "web_stream_unreads_count_display_policy", "starred_message_counts", "send_stream_typing_notifications", @@ -779,6 +780,12 @@ export function dispatch_normal_event(event) { stream_list.update_streams_sidebar(); stream_list_sort.set_filter_out_inactives(); } + if (event.property === "web_animate_image_previews") { + // Rerender the whole message list UI + for (const msg_list of message_lists.all_rendered_message_lists()) { + msg_list.rerender(); + } + } if (event.property === "web_stream_unreads_count_display_policy") { stream_list.update_dom_unread_counts_visibility(); } diff --git a/web/src/settings.js b/web/src/settings.js index 187d3093db..0c7d6b4fb9 100644 --- a/web/src/settings.js +++ b/web/src/settings.js @@ -105,6 +105,7 @@ export function build_page() { settings_config.web_mark_read_on_scroll_policy_values, web_channel_default_view_values: settings_config.web_channel_default_view_values, user_list_style_values: settings_config.user_list_style_values, + web_animate_image_previews_values: settings_config.web_animate_image_previews_values, web_stream_unreads_count_display_policy_values: settings_config.web_stream_unreads_count_display_policy_values, color_scheme_values: settings_config.color_scheme_values, diff --git a/web/src/settings_config.ts b/web/src/settings_config.ts index 22ed27b57f..dfa589618d 100644 --- a/web/src/settings_config.ts +++ b/web/src/settings_config.ts @@ -84,6 +84,21 @@ export const user_list_style_values = { // }, }; +export const web_animate_image_previews_values = { + always: { + code: "always", + description: $t({defaultMessage: "Always"}), + }, + on_hover: { + code: "on_hover", + description: $t({defaultMessage: "On hover"}), + }, + never: { + code: "never", + description: $t({defaultMessage: "Only in image viewer"}), + }, +}; + export const web_stream_unreads_count_display_policy_values = { all_streams: { code: 1, diff --git a/web/src/settings_preferences.ts b/web/src/settings_preferences.ts index 38d0954b92..9f0d90de9b 100644 --- a/web/src/settings_preferences.ts +++ b/web/src/settings_preferences.ts @@ -230,6 +230,9 @@ export function set_up(settings_panel: SettingsPanel): void { .find(`.setting_user_list_style_choice[value=${settings_object.user_list_style}]`) .prop("checked", true); + $container + .find(".setting_web_animate_image_previews") + .val(settings_object.web_animate_image_previews); $container .find(".setting_web_stream_unreads_count_display_policy") .val(settings_object.web_stream_unreads_count_display_policy); diff --git a/web/src/user_settings.ts b/web/src/user_settings.ts index 4e5a8b755e..446856afc1 100644 --- a/web/src/user_settings.ts +++ b/web/src/user_settings.ts @@ -71,6 +71,7 @@ export const user_settings_schema = stream_notification_settings_schema translate_emoticons: z.boolean(), twenty_four_hour_time: z.boolean(), user_list_style: z.number(), + web_animate_image_previews: z.enum(["always", "on_hover", "never"]), web_channel_default_view: z.number(), web_escape_navigates_to_home_view: z.boolean(), web_font_size_px: z.number(), diff --git a/web/styles/rendered_markdown.css b/web/styles/rendered_markdown.css index 086e3fc37f..26966fbaa0 100644 --- a/web/styles/rendered_markdown.css +++ b/web/styles/rendered_markdown.css @@ -564,7 +564,8 @@ float: none; } - .message_inline_video { + .message_inline_video, + .message_inline_animated_image_still { &:hover { &::after { transform: scale(1); diff --git a/web/templates/settings/display_settings_information.hbs b/web/templates/settings/display_settings_information.hbs index f68d91f973..96bbbb1885 100644 --- a/web/templates/settings/display_settings_information.hbs +++ b/web/templates/settings/display_settings_information.hbs @@ -35,6 +35,13 @@ + +