diff --git a/app/assets/javascripts/discourse/app/lib/lightbox/process-html.js b/app/assets/javascripts/discourse/app/lib/lightbox/process-html.js index 9f95e615a17..9c8d00a1728 100644 --- a/app/assets/javascripts/discourse/app/lib/lightbox/process-html.js +++ b/app/assets/javascripts/discourse/app/lib/lightbox/process-html.js @@ -23,7 +23,12 @@ export async function processHTML({ container, selector, clickTarget }) { item.parentElement?.style?.backgroundImage || null; - const _fullsizeURL = item.href || item.src || innerImage.src || null; + const _fullsizeURL = + item.dataset?.largeSrc || + item.href || + item.src || + innerImage.src || + null; const _smallURL = innerImage.currentSrc || diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index bc7fbcf2890..b82995e855e 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -222,15 +222,6 @@ class CookedPostProcessor end end - def each_responsive_ratio - SiteSetting - .responsive_post_image_sizes - .split("|") - .map(&:to_f) - .sort - .each { |r| yield r if r > 1 } - end - def optimize_image!(img, upload, cropped: false) w, h = img["width"].to_i, img["height"].to_i onebox = img.ancestors(".onebox, .onebox-body").first diff --git a/lib/cooked_processor_mixin.rb b/lib/cooked_processor_mixin.rb index 1ad4d74853a..967695659a3 100644 --- a/lib/cooked_processor_mixin.rb +++ b/lib/cooked_processor_mixin.rb @@ -362,4 +362,13 @@ module CookedProcessorMixin span.content = content if content span end + + def each_responsive_ratio + SiteSetting + .responsive_post_image_sizes + .split("|") + .map(&:to_f) + .sort + .each { |r| yield r if r > 1 } + end end diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-upload.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-upload.gjs index 131b3bc8573..aa2780ebec7 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-upload.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-upload.gjs @@ -7,7 +7,7 @@ import { htmlSafe } from "@ember/template"; import { isAudio, isImage, isVideo } from "discourse/lib/uploads"; import eq from "truth-helpers/helpers/eq"; -export default class extends Component { +export default class ChatUpload extends Component { @service siteSettings; @tracked loaded = false; @@ -44,6 +44,10 @@ export default class extends Component { return { width: width * ratio, height: height * ratio }; } + get imageUrl() { + return this.args.upload.thumbnail?.url ?? this.args.upload.url; + } + get imageStyle() { if (this.args.upload.dominant_color && !this.loaded) { return htmlSafe(`background-color: #${this.args.upload.dominant_color};`); @@ -60,9 +64,10 @@ export default class extends Component { { - item.src = item.el[0].src; + item.src = item.el[0].dataset.largeSrc || item.el[0].src; }, }, }); diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-channel-subscription-manager.js b/plugins/chat/assets/javascripts/discourse/lib/chat-channel-subscription-manager.js index ac4c9d38e78..de1824c8c53 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/chat-channel-subscription-manager.js +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-channel-subscription-manager.js @@ -126,6 +126,7 @@ export default class ChatChannelSubscriptionManager { const message = this.messagesManager.findMessage(data.chat_message.id); if (message) { message.cooked = data.chat_message.cooked; + message.uploads = cloneJSON(data.chat_message.uploads || []); message.processed = true; message.incrementVersion(); } diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-channel-thread-subscription-manager.js b/plugins/chat/assets/javascripts/discourse/lib/chat-channel-thread-subscription-manager.js index a774dc52b3a..e5230548cdf 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/chat-channel-thread-subscription-manager.js +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-channel-thread-subscription-manager.js @@ -116,6 +116,7 @@ export default class ChatChannelThreadSubscriptionManager { const message = this.messagesManager.findMessage(data.chat_message.id); if (message) { message.cooked = data.chat_message.cooked; + message.uploads = cloneJSON(data.chat_message.uploads || []); message.processed = true; message.incrementVersion(); } diff --git a/plugins/chat/lib/chat/message_processor.rb b/plugins/chat/lib/chat/message_processor.rb index 9317dec377a..0ddadd13d00 100644 --- a/plugins/chat/lib/chat/message_processor.rb +++ b/plugins/chat/lib/chat/message_processor.rb @@ -17,9 +17,39 @@ module Chat def run! post_process_oneboxes + process_thumbnails DiscourseEvent.trigger(:chat_message_processed, @doc, @model) end + def process_thumbnails + @model.uploads.each do |upload| + if upload.width <= SiteSetting.max_image_width && + upload.height <= SiteSetting.max_image_height + return false + end + + crop = + SiteSetting.min_ratio_to_crop > 0 && + upload.width.to_f / upload.height.to_f < SiteSetting.min_ratio_to_crop + + width = upload.thumbnail_width + height = upload.thumbnail_height + + # create the main thumbnail + upload.create_thumbnail!(width, height, crop: crop) + + # create additional responsive thumbnails + each_responsive_ratio do |ratio| + resized_w = (width * ratio).to_i + resized_h = (height * ratio).to_i + + if upload.width && resized_w <= upload.width + upload.create_thumbnail!(resized_w, resized_h, crop: crop) + end + end + end + end + def large_images [] end diff --git a/plugins/chat/plugin.rb b/plugins/chat/plugin.rb index af5d8b3292b..56957cbaad2 100644 --- a/plugins/chat/plugin.rb +++ b/plugins/chat/plugin.rb @@ -253,6 +253,12 @@ after_initialize do object.chat_separate_sidebar_mode end + add_to_serializer( + :upload, + :thumbnail, + include_condition: -> { SiteSetting.chat_enabled && SiteSetting.create_thumbnails }, + ) { object.thumbnail } + RETENTION_SETTINGS_TO_USER_OPTION_FIELDS = { chat_channel_retention_days: :dismissed_channel_retention_reminder, chat_dm_retention_days: :dismissed_dm_retention_reminder, diff --git a/plugins/chat/spec/system/uploads_spec.rb b/plugins/chat/spec/system/uploads_spec.rb index 7889f1b26b0..6c9e33dd971 100644 --- a/plugins/chat/spec/system/uploads_spec.rb +++ b/plugins/chat/spec/system/uploads_spec.rb @@ -12,6 +12,7 @@ describe "Uploading files in chat messages", type: :system do context "when uploading to a new message" do before do + Jobs.run_immediately! channel_1.add(current_user) sign_in(current_user) end @@ -39,6 +40,34 @@ describe "Uploading files in chat messages", type: :system do expect(Chat::Message.last.uploads.count).to eq(1) end + it "adds a thumbnail for large images" do + SiteSetting.create_thumbnails = true + + chat.visit_channel(channel_1) + file_path = file_from_fixtures("huge.jpg", "images").path + + attach_file(file_path) do + channel_page.open_action_menu + channel_page.click_action_button("chat-upload-btn") + end + + expect { channel_page.send_message }.to change { Chat::Message.count }.by(1) + + expect(channel_page).to have_no_css(".chat-composer-upload") + + message = Chat::Message.last + + try_until_success(timeout: 5) { expect(message.uploads.first.thumbnail).to be_present } + + upload = message.uploads.first + + # image has src attribute with thumbnail url + expect(channel_page).to have_css(".chat-uploads img[src$='#{upload.thumbnail.url}']") + + # image has data-large-src with original image src + expect(channel_page).to have_css(".chat-uploads img[data-large-src$='#{upload.url}']") + end + it "adds dominant color attribute to images" do chat.visit_channel(channel_1) file_path = file_from_fixtures("logo.png", "images").path