From f2bbf3693ff2e26428e9f084c90aff6c22182f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 21 Feb 2026 22:00:28 -0500 Subject: [PATCH] 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 --- .../language/en-GB/admin/settings/uploads.json | 2 +- .../openapi/components/schemas/UserObject.yaml | 4 ++++ public/openapi/read/user/userslug/edit.yaml | 4 ---- public/src/modules/accounts/picture.js | 15 ++++++++------- public/src/modules/pictureCropper.js | 16 ++++++++++------ src/controllers/accounts/edit.js | 2 -- src/controllers/accounts/helpers.js | 2 ++ src/image.js | 5 ++++- 8 files changed, 29 insertions(+), 21 deletions(-) diff --git a/public/language/en-GB/admin/settings/uploads.json b/public/language/en-GB/admin/settings/uploads.json index b08d56a5f8..9cd7b7d8f2 100644 --- a/public/language/en-GB/admin/settings/uploads.json +++ b/public/language/en-GB/admin/settings/uploads.json @@ -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", diff --git a/public/openapi/components/schemas/UserObject.yaml b/public/openapi/components/schemas/UserObject.yaml index 61d98b1f80..7ebb831b84 100644 --- a/public/openapi/components/schemas/UserObject.yaml +++ b/public/openapi/components/schemas/UserObject.yaml @@ -450,6 +450,10 @@ UserObjectFull: type: boolean allowProfileImageUploads: type: number + maximumProfileImageSize: + type: number + profileImageDimension: + type: number allowedProfileImageExtensions: type: string groups: diff --git a/public/openapi/read/user/userslug/edit.yaml b/public/openapi/read/user/userslug/edit.yaml index 32c476c99f..0832943fbe 100644 --- a/public/openapi/read/user/userslug/edit.yaml +++ b/public/openapi/read/user/userslug/edit.yaml @@ -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: diff --git a/public/src/modules/accounts/picture.js b/public/src/modules/accounts/picture.js index 6fe37df734..0bec1a24a9 100644 --- a/public/src/modules/accounts/picture.js +++ b/public/src/modules/accounts/picture.js @@ -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); diff --git a/public/src/modules/pictureCropper.js b/public/src/modules/pictureCropper.js index ce69c6b6c3..9dbb34af22 100644 --- a/public/src/modules/pictureCropper.js +++ b/public/src/modules/pictureCropper.js @@ -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 + ']]'); } diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js index 1192687a5f..6c1c977e0d 100644 --- a/src/controllers/accounts/edit.js +++ b/src/controllers/accounts/edit.js @@ -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'); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index af5963002e..ba08f7187b 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -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)); diff --git a/src/image.js b/src/image.js index 0d60fddc6a..b2a69d7e3f 100644 --- a/src/image.js +++ b/src/image.js @@ -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) {