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:
David Battersby 2023-12-06 14:59:18 +08:00 committed by GitHub
parent 30d5e752d7
commit 8b46dc8bb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 90 additions and 13 deletions

View File

@ -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 ||

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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;
},
},
});

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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

View File

@ -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,

View File

@ -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