fix: closes #7221 restrict cropperjs box to profileImageDimension form ACP

this also resizes the image down to that value and sends a small image to server
move profileImageDimenstion & maximumProfileImageSize from edit to all profile pages, on harmony profile pic can be changed from any page by clicking the avatar
change image.resize so it doesn't resize if image is already same width/height
This commit is contained in:
Barış Soner Uşaklı
2026-02-21 22:00:28 -05:00
parent bb7be8c587
commit f2bbf3693f
8 changed files with 29 additions and 21 deletions

View File

@@ -40,7 +40,7 @@
"default-avatar": "Custom Default Avatar",
"upload": "Upload",
"profile-image-dimension": "Profile Image Dimension",
"profile-image-dimension-help": "(in pixels, default: 128 pixels)",
"profile-image-dimension-help": "(in pixels, default: 200 pixels)",
"max-profile-image-size": "Maximum Profile Image File Size",
"max-profile-image-size-help": "(in kibibytes, default: 256 KiB)",
"max-cover-image-size": "Maximum Cover Image File Size",

View File

@@ -450,6 +450,10 @@ UserObjectFull:
type: boolean
allowProfileImageUploads:
type: number
maximumProfileImageSize:
type: number
profileImageDimension:
type: number
allowedProfileImageExtensions:
type: string
groups:

View File

@@ -23,8 +23,6 @@ get:
type: number
maximumAboutMeLength:
type: number
maximumProfileImageSize:
type: number
allowProfilePicture:
type: boolean
allowCoverPicture:
@@ -41,8 +39,6 @@ get:
type: boolean
allowSignature:
type: boolean
profileImageDimension:
type: number
defaultAvatar:
type: string
sso:

View File

@@ -169,15 +169,16 @@ define('accounts/picture', [
modal.modal('hide');
pictureCropper.show({
socketMethod: 'user.uploadCroppedPicture',
route: config.relative_path + '/api/user/' + ajaxify.data.userslug + '/uploadpicture',
aspectRatio: 1 / 1,
paramName: 'uid',
paramValue: ajaxify.data.theirid,
fileSize: ajaxify.data.maximumProfileImageSize,
allowSkippingCrop: false,
title: '[[user:upload-picture]]',
description: '[[user:upload-a-picture]]',
socketMethod: 'user.uploadCroppedPicture',
route: `${config.relative_path}/api/user/${ajaxify.data.userslug}/uploadpicture`,
aspectRatio: 1 / 1,
allowSkippingCrop: false,
paramName: 'uid',
paramValue: ajaxify.data.theirid,
restrictImageDimension: true,
imageDimension: ajaxify.data.profileImageDimension,
accept: ajaxify.data.allowedProfileImageExtensions,
}, function (url) {
onUploadComplete(url);

View File

@@ -101,12 +101,11 @@ define('pictureCropper', ['alerts'], function (alerts) {
});
cropperModal.find('.crop-btn').on('click', function () {
$(this).addClass('disabled');
const imageData = checkCORS(cropperTool, data);
if (!imageData) {
return;
}
$(this).addClass('disabled');
cropperModal.find('#upload-progress-bar').css('width', '0%');
cropperModal.find('#upload-progress-box').show().removeClass('hide');
@@ -160,7 +159,7 @@ define('pictureCropper', ['alerts'], function (alerts) {
params: socketData,
}, function (err, result) {
if (err) {
return alerts.error(err);
return callback(err);
}
if (socketData.progress + chunkSize < socketData.size) {
@@ -178,9 +177,14 @@ define('pictureCropper', ['alerts'], function (alerts) {
function checkCORS(cropperTool, data) {
let imageData;
try {
const opts = {};
if (data.restrictImageDimension) {
opts.width = data.imageDimension;
opts.height = data.imageDimension;
}
imageData = data.imageType ?
cropperTool.getCroppedCanvas().toDataURL(data.imageType) :
cropperTool.getCroppedCanvas().toDataURL();
cropperTool.getCroppedCanvas(opts).toDataURL(data.imageType) :
cropperTool.getCroppedCanvas(opts).toDataURL();
} catch (err) {
const corsErrors = [
'The operation is insecure.',
@@ -209,7 +213,7 @@ define('pictureCropper', ['alerts'], function (alerts) {
}
const file = fileInput[0].files[0];
const fileSize = data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false;
const fileSize = data.hasOwnProperty('fileSize') && data.fileSize ? parseInt(data.fileSize, 10) : false;
if (fileSize && file.size > fileSize * 1024) {
return showAlert('error', '[[error:file-too-big, ' + fileSize + ']]');
}

View File

@@ -35,12 +35,10 @@ editController.get = async function (req, res, next) {
userData.customUserFields = customUserFields;
userData.maximumSignatureLength = meta.config.maximumSignatureLength;
userData.maximumAboutMeLength = meta.config.maximumAboutMeLength;
userData.maximumProfileImageSize = meta.config.maximumProfileImageSize;
userData.allowMultipleBadges = meta.config.allowMultipleBadges === 1;
userData.allowAccountDelete = meta.config.allowAccountDelete === 1;
userData.allowAboutMe = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:aboutme'];
userData.allowSignature = canUseSignature && (!isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:signature']);
userData.profileImageDimension = meta.config.profileImageDimension;
userData.defaultAvatar = user.getDefaultAvatar();
userData.groups = _groups.filter(g => g && g.userTitleEnabled && !groups.isPrivilegeGroup(g.name) && g.name !== 'registered-users');

View File

@@ -97,6 +97,8 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
userData.allowCoverPicture = !userData.isSelf || !!meta.config['reputation:disabled'] || userData.reputation >= meta.config['min:rep:cover-picture'];
userData.allowProfileImageUploads = meta.config.allowProfileImageUploads;
userData.allowedProfileImageExtensions = user.getAllowedProfileImageExtensions().map(ext => `.${ext}`).join(', ');
userData.maximumProfileImageSize = meta.config.maximumProfileImageSize;
userData.profileImageDimension = meta.config.profileImageDimension;
userData.groups = Array.isArray(results.groups) && results.groups.length ? results.groups[0] : [];
userData.selectedGroup = userData.groups.filter(group => group && userData.groupTitleArray.includes(group.name))
.sort((a, b) => userData.groupTitleArray.indexOf(a.name) - userData.groupTitleArray.indexOf(b.name));

View File

@@ -51,7 +51,10 @@ image.resizeImage = async function (data) {
const metadata = await sharpImage.metadata();
sharpImage.rotate(); // auto-orients based on exif data
sharpImage.resize(data.hasOwnProperty('width') ? data.width : null, data.hasOwnProperty('height') ? data.height : null);
// don't resize if width/height not changing or not specificied
if ((data.width && metadata.width !== data.width) || (data.height && metadata.height !== data.height)) {
sharpImage.resize(data.width || null, data.height || null);
}
if (data.quality) {
switch (metadata.format) {