diff --git a/.changeset/nervous-fireants-wash.md b/.changeset/nervous-fireants-wash.md new file mode 100644 index 000000000000..441202512864 --- /dev/null +++ b/.changeset/nervous-fireants-wash.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Allows default avatars to be generated with more than one inital (limited to first 3) when setting `Use Full Name Initials to Generate Default Avatar` is true. diff --git a/apps/meteor/server/routes/avatar/user.spec.ts b/apps/meteor/server/routes/avatar/user.spec.ts index f5e6d87dbb63..3ae8e3d48840 100644 --- a/apps/meteor/server/routes/avatar/user.spec.ts +++ b/apps/meteor/server/routes/avatar/user.spec.ts @@ -169,7 +169,9 @@ describe('#userAvatarById()', () => { await userAvatarById(request, response, next); expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; - expect(mocks.utils.serveSvgAvatarInRequestedFormat.calledWith({ nameOrUsername: 'Doe', req: request, res: response })).to.be.true; + expect( + mocks.utils.serveSvgAvatarInRequestedFormat.calledWith({ nameOrUsername: 'Doe', req: request, res: response, useAllInitials: true }), + ).to.be.true; }); }); @@ -286,6 +288,8 @@ describe('#userAvatarByUsername()', () => { await userAvatarByUsername(request, response, next); expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; - expect(mocks.utils.serveSvgAvatarInRequestedFormat.calledWith({ nameOrUsername: 'Doe', req: request, res: response })).to.be.true; + expect( + mocks.utils.serveSvgAvatarInRequestedFormat.calledWith({ nameOrUsername: 'Doe', req: request, res: response, useAllInitials: true }), + ).to.be.true; }); }); diff --git a/apps/meteor/server/routes/avatar/user.ts b/apps/meteor/server/routes/avatar/user.ts index 7b20287ddac3..a6870b7b8957 100644 --- a/apps/meteor/server/routes/avatar/user.ts +++ b/apps/meteor/server/routes/avatar/user.ts @@ -66,7 +66,7 @@ export const userAvatarByUsername = async function (request: IncomingMessage, re }); if (user?.name) { - serveSvgAvatarInRequestedFormat({ nameOrUsername: user.name, req, res }); + serveSvgAvatarInRequestedFormat({ nameOrUsername: user.name, req, res, useAllInitials: true }); return; } } @@ -126,7 +126,7 @@ export const userAvatarById = async function (request: IncomingMessage, res: Ser // Use real name for SVG letters if (settings.get('UI_Use_Name_Avatar') && user?.name) { - serveSvgAvatarInRequestedFormat({ nameOrUsername: user.name, req, res }); + serveSvgAvatarInRequestedFormat({ nameOrUsername: user.name, req, res, useAllInitials: true }); return; } diff --git a/apps/meteor/server/routes/avatar/utils.spec.ts b/apps/meteor/server/routes/avatar/utils.spec.ts index 3822ac8caa90..7f6bc2c28fe0 100644 --- a/apps/meteor/server/routes/avatar/utils.spec.ts +++ b/apps/meteor/server/routes/avatar/utils.spec.ts @@ -165,6 +165,29 @@ describe('#renderSvgLetters', () => { expect(renderSVGLetters('Bob', 32)).to.include('viewBox="0 0 32 32"'); expect(renderSVGLetters('yan', 64)).to.include('viewBox="0 0 64 64"'); }); + it('should return a default size of 125 for a single letter', () => { + expect(renderSVGLetters('a', 200)).to.include('font-size="125"'); + }); + it('should render a single letter when useAllInitials is false', () => { + expect(renderSVGLetters('arthur', 16, false)).to.include('>\nA\n'); + }); + it('should render a single letter when useAllInitials is true but username has no spaces', () => { + expect(renderSVGLetters('arthur', 16, true)).to.include('>\nA\n'); + }); + it('should render more than one letter when useAllInitials is true', () => { + expect(renderSVGLetters('arthur void', 16, true)).to.include('>\nAV\n'); + expect(renderSVGLetters('arthur void jackson', 16, true)).to.include('>\nAVJ\n'); + }); + it('should cap generated avatar to 3 letters at most', () => { + expect(renderSVGLetters('arthur void jackson billie', 16, true)).to.include('>\nAVJ\n'); + expect(renderSVGLetters('arthur void jackson billie jean', 16, true)).to.include('>\nAVJ\n'); + }); + it('should decrease the font size when username has more than 1 word', () => { + expect(renderSVGLetters('arthur void', 200, true)).to.include('font-size="100"'); + }); + it('should decrease the font size when username has 3 words', () => { + expect(renderSVGLetters('this is three_words', 200, true)).to.include('font-size="80"'); + }); }); describe('#setCacheAndDispositionHeaders', () => { diff --git a/apps/meteor/server/routes/avatar/utils.ts b/apps/meteor/server/routes/avatar/utils.ts index 892bd959558d..377369f4a450 100644 --- a/apps/meteor/server/routes/avatar/utils.ts +++ b/apps/meteor/server/routes/avatar/utils.ts @@ -18,6 +18,7 @@ const cookie = new Cookies(); export const MAX_SVG_AVATAR_SIZE = 1024; export const MIN_SVG_AVATAR_SIZE = 16; +const MAX_SVG_AVATAR_INITIALS = 3; export const serveAvatarFile = (file: IUpload, req: IIncomingMessage, res: ServerResponse, next: NextFunction) => { res.setHeader('Content-Security-Policy', "default-src 'none'"); @@ -56,13 +57,15 @@ export const serveSvgAvatarInRequestedFormat = ({ nameOrUsername, req, res, + useAllInitials = false, }: { nameOrUsername: string; req: IIncomingMessage; res: ServerResponse; + useAllInitials?: boolean; }) => { const size = getAvatarSizeFromRequest(req); - const avatar = renderSVGLetters(nameOrUsername, size); + const avatar = renderSVGLetters(nameOrUsername, size, useAllInitials); res.setHeader('Last-Modified', FALLBACK_LAST_MODIFIED); const { format } = req.query; @@ -125,7 +128,9 @@ const getFirstLetter = (name: string) => .substr(0, 1) .toUpperCase(); -export const renderSVGLetters = (username: string, viewSize = 200) => { +const getInitials = (name: string) => name.split(' ').slice(0, MAX_SVG_AVATAR_INITIALS).map(getFirstLetter).join(''); + +export const renderSVGLetters = (username: string, viewSize = 200, useAllInitials = false) => { let color = ''; let initials = ''; @@ -134,10 +139,11 @@ export const renderSVGLetters = (username: string, viewSize = 200) => { initials = username; } else { color = getAvatarColor(username); - initials = getFirstLetter(username); + initials = !useAllInitials ? getFirstLetter(username) : getInitials(username); } - const fontSize = viewSize / 1.6; + const reductionFactor = initials.length > 1 ? Math.pow(initials.length, 2) / 10 : 0; + const fontSize = viewSize / (1.6 + reductionFactor); return `\n\n\n${initials}\n\n`; };