FEATURE: Add thumbnails for chat image uploads (#24328)
Introduces the concept of image thumbnails in chat, prior to this we uploaded and used full size chat images within channels and direct messages. The following changes are covered: - Post processing of image uploads to create the thumbnail within Chat::MessageProcessor - Extract responsive image ratios into CookedProcessorMixin (used for creating upload variations) - Add thumbnail to upload serializer from plugin.rb - Convert chat upload template to glimmer component using .gjs format - Use thumbnail image within chat upload component (stores full size img in orig-src data attribute) - Old uploads which don't have thumbnails will fallback to full size images in channels/DMs - Update Magnific lightbox to use full size image when clicked - Update Glimmer lightbox to use full size image (enables zooming for chat images)
This commit is contained in:
parent
30d5e752d7
commit
8b46dc8bb5
|
@ -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 ||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
|||
<img
|
||||
class="chat-img-upload"
|
||||
data-orig-src={{@upload.short_url}}
|
||||
data-large-src={{@upload.url}}
|
||||
height={{this.size.height}}
|
||||
width={{this.size.width}}
|
||||
src={{@upload.url}}
|
||||
src={{this.imageUrl}}
|
||||
style={{this.imageStyle}}
|
||||
loading="lazy"
|
||||
tabindex="0"
|
||||
|
|
|
@ -161,7 +161,7 @@ export default {
|
|||
},
|
||||
callbacks: {
|
||||
elementParse: (item) => {
|
||||
item.src = item.el[0].src;
|
||||
item.src = item.el[0].dataset.largeSrc || item.el[0].src;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue