From c91cdb85d89ff3754d47ce5ce6b72596148863d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20M=C3=A5nsson?= Date: Tue, 14 Oct 2025 11:25:18 +0200 Subject: [PATCH 01/15] ks-201: Add accessibility support for screen readers to announce the number of matching results in message search immediately after the search has been performed. --- .../components/MessageSearch.tsx | 17 ++++++++++++++++- packages/i18n/src/locales/en.i18n.json | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx index d07adce8fd586..a77db92f67437 100644 --- a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx +++ b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx @@ -3,6 +3,7 @@ import { MessageTypes } from '@rocket.chat/message-types'; import { useSetting, useTranslation, useUserPreference } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import { Fragment, memo, useState } from 'react'; +import { VisuallyHidden } from 'react-aria'; import { Virtuoso } from 'react-virtuoso'; import { ContextualbarEmptyContent } from '../../../../../components/Contextualbar'; @@ -36,7 +37,21 @@ const MessageSearch = ({ searchText, globalSearch }: MessageSearchProps): ReactE {messageSearchQuery.data && ( <> - {messageSearchQuery.data.length === 0 && } + {messageSearchQuery.data.length === 0 && ( + + + + )} + + {messageSearchQuery.data.length > 0 && ( + + {t('Search_Messages_Count', { count: messageSearchQuery.data.length })} + + )} + {messageSearchQuery.data.length > 0 && ( diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index dcdbe01b31e34..806ed82ad0820 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -4645,6 +4645,10 @@ "Search_Installed_Apps": "Search installed apps", "Search_Integrations": "Search Integrations", "Search_Messages": "Search Messages", + "Search_Messages_Count": { + "one": "Found {{count}} single result", + "other": "Found {{count}} results" + }, "Search_Page_Size": "Page Size", "Search_Premium_Apps": "Search Premium apps", "Search_Private_Groups": "Search Private Groups", From 9f5efcccd632d7022046243e7b5938f71d135412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20M=C3=A5nsson?= Date: Thu, 16 Oct 2025 12:16:32 +0200 Subject: [PATCH 02/15] ks-201: Fixed tabIndex prop as well as minor spelling errors. --- .../MessageSearchTab/components/MessageSearch.tsx | 2 +- packages/i18n/src/locales/en.i18n.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx index a77db92f67437..a32ad4561dd1e 100644 --- a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx +++ b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearch.tsx @@ -38,7 +38,7 @@ const MessageSearch = ({ searchText, globalSearch }: MessageSearchProps): ReactE {messageSearchQuery.data && ( <> {messageSearchQuery.data.length === 0 && ( - diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 806ed82ad0820..7a27c65dab893 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -315,7 +315,7 @@ "Accounts_TwoFactorAuthentication_Enforce_Password_Fallback_Description": "Users will be forced to enter their password, for important actions, if no other Two Factor Authentication method is enabled for that user and a password is set for him.", "Accounts_TwoFactorAuthentication_MaxDelta": "Maximum Delta", "Accounts_TwoFactorAuthentication_MaxDelta_Description": "The Maximum Delta determines how many tokens are valid at any given time. Tokens are generated every 30 seconds, and are valid for (30 * Maximum Delta) seconds. \nExample: With a Maximum Delta set to 10, each token can be used up to 300 seconds before or after it's timestamp. This is useful when the client's clock is not properly synced with the server.", - "Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts": "Maximun Invalid Email OTP Codes Allowed", + "Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts": "Maximum Invalid Email OTP Codes Allowed", "Accounts_TwoFactorAuthentication_Max_Invalid_Email_Code_Attempts_Description": "The system allows a maximum number of invalid email OTP codes, after which a new code is automatically generated. We highly recommend using this setting along with 'Block failed login attempts by Username'.", "Accounts_TwoFactorAuthentication_RememberFor": "Remember Two Factor for (seconds)", "Accounts_TwoFactorAuthentication_RememberFor_Description": "Do not request two factor authorization code if it was already provided before in the given time.", @@ -581,7 +581,7 @@ "Apps_License_Message_appId": "License hasn't been issued for this app", "Apps_License_Message_bundle": "License issued for a bundle that does not contain the app", "Apps_License_Message_expire": "License is no longer valid and needs to be renewed", - "Apps_License_Message_maxSeats": "License does not accomodate the current amount of active users. Please increase the number of seats", + "Apps_License_Message_maxSeats": "License does not accommodate the current amount of active users. Please increase the number of seats", "Apps_License_Message_publicKey": "There has been an error trying to decrypt the license. Please sync your workspace in the Connectivity Services and try again", "Apps_License_Message_renewal": "License has expired and needs to be renewed", "Apps_License_Message_seats": "License does not have enough seats to accommodate the current amount of active users. Please increase the number of seats", @@ -944,7 +944,7 @@ "Cancel_message_input": "Cancel", "Cancel_recording": "Cancel recording", "Cancel_subscription": "Cancel subscription", - "Cancel_subscription_message": "This workspace will downgrage to Community and lose free access to premium capabilities.

While you can keep using Rocket.Chat, your team will lose access to unlimited mobile push notifications, read receipts, marketplace apps <4>and other capabilities.", + "Cancel_subscription_message": "This workspace will downgrade to Community and lose free access to premium capabilities.

While you can keep using Rocket.Chat, your team will lose access to unlimited mobile push notifications, read receipts, marketplace apps <4>and other capabilities.", "Canceled": "Canceled", "Canned_Response_Created": "Canned Response created", "Canned_Response_Delete_Warning": "Deleting a canned response cannot be undone.", @@ -985,7 +985,7 @@ "Channel_to_listen_on": "Channel to listen on", "Channel_what_is_this_channel_about": "What is this channel about?", "Channels": "Channels", - "Channels_added": "Channels added sucessfully", + "Channels_added": "Channels added successfully", "Channels_are_where_your_team_communicate": "Channels are where your team communicate", "Channels_list": "List of public channels", "Chart": "Chart", @@ -6594,7 +6594,7 @@ "onboarding.form.registeredServerForm.registrationKeepInformed": "By submitting this form you consent to receive more information about Rocket.Chat products, events and updates, according to our <1>privacy policy. You may unsubscribe at any time.", "onboarding.form.registeredServerForm.title": "Register your workspace", "onboarding.form.standaloneServerForm.manuallyIntegrate": "Need to manually integrate with external services", - "onboarding.form.standaloneServerForm.publishOwnApp": "In order to send push notitications you need to compile and publish your own app to Google Play and App Store", + "onboarding.form.standaloneServerForm.publishOwnApp": "In order to send push notifications you need to compile and publish your own app to Google Play and App Store", "onboarding.form.standaloneServerForm.servicesUnavailable": "Some of the services will be unavailable or will require manual setup", "onboarding.form.standaloneServerForm.title": "Standalone Server Confirmation", "onboarding.page.alreadyHaveAccount": "Already have an account? <1>Manage your workspaces.", From 214a1130a4d69c2cdcbd24bf99581906e11ef79c Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:40:29 -0300 Subject: [PATCH 03/15] chore: improve voip session handling (#37094) --- packages/ui-voip/src/v2/useMediaSessionInstance.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui-voip/src/v2/useMediaSessionInstance.ts b/packages/ui-voip/src/v2/useMediaSessionInstance.ts index db55bcfafb181..b72b250e97e4c 100644 --- a/packages/ui-voip/src/v2/useMediaSessionInstance.ts +++ b/packages/ui-voip/src/v2/useMediaSessionInstance.ts @@ -97,7 +97,8 @@ class MediaSessionStore extends Emitter<{ change: void }> { private makeInstance(userId: string) { if (this.sessionInstance !== null) { - this.sessionInstance.disableStateReport(); + this.sessionInstance.endSession(); + this.sessionInstance = null; } if (!this._webrtcProcessorFactory || !this.sendSignalFn) { From e2580d206f60bc5b65bf8808bd53b99bbc6b9d25 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 14 Oct 2025 11:46:02 -0300 Subject: [PATCH 04/15] fix: Pagination not working properly in `UsersInRoleTable` (#37203) --- .changeset/slow-tomatoes-try.md | 5 +++++ .../UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/slow-tomatoes-try.md diff --git a/.changeset/slow-tomatoes-try.md b/.changeset/slow-tomatoes-try.md new file mode 100644 index 0000000000000..91b762e4f8bf6 --- /dev/null +++ b/.changeset/slow-tomatoes-try.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where pagination is not working properly in “users in role” table diff --git a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx index 64d4ae417847d..3695f4ef61490 100644 --- a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx +++ b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx @@ -115,7 +115,7 @@ const UsersInRoleTable = ({ rid, roleId, roleName, description }: UsersInRoleTab divider current={current} itemsPerPage={itemsPerPage} - count={users.length || 0} + count={data.total || 0} onSetItemsPerPage={onSetItemsPerPage} onSetCurrent={onSetCurrent} {...paginationProps} From 53a5aaa1d9749b7410d1a9de828171a83116d920 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 14 Oct 2025 14:46:44 -0300 Subject: [PATCH 05/15] fix: Message not being translated immediately in Omnichannel room (#37200) --- .changeset/lemon-kings-approve.md | 5 +++++ apps/meteor/app/autotranslate/client/lib/autotranslate.ts | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changeset/lemon-kings-approve.md diff --git a/.changeset/lemon-kings-approve.md b/.changeset/lemon-kings-approve.md new file mode 100644 index 0000000000000..1a6999c545273 --- /dev/null +++ b/.changeset/lemon-kings-approve.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where messages are not being translated immediately in omnichannel rooms diff --git a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts index 3cea977c8b4e4..76b45d04e18fa 100644 --- a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts +++ b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts @@ -148,7 +148,6 @@ export const createAutoTranslateMessageStreamHandler = (): ((message: ITranslate (record) => record._id === message._id, ({ autoTranslateFetching: _, ...record }) => ({ ...record, - autoTranslateShowInverse: true, }), ); delete AutoTranslate.messageIdsToWait[message._id]; From bd0b91735b0b0731545496b30f848b4a6259a12c Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Tue, 14 Oct 2025 15:40:53 -0300 Subject: [PATCH 06/15] feat: Expandable Message Composer (#36920) Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com> --- .../room/composer/messageBox/MessageBox.tsx | 43 +++- .../featurePreview/expandable-composer.png | Bin 0 -> 19912 bytes packages/i18n/src/locales/en.i18n.json | 2 + .../src/hooks/useFeaturePreviewList.ts | 12 +- .../MessageComposer.stories.tsx | 16 ++ .../MessageComposerInputExpandable.spec.tsx | 117 ++++++++++ .../MessageComposerInputExpandable.tsx | 55 +++++ .../MessageComposer.spec.tsx.snap | 205 ++++++++++++++++++ .../ui-composer/src/MessageComposer/index.ts | 2 + 9 files changed, 439 insertions(+), 13 deletions(-) create mode 100644 apps/meteor/public/images/featurePreview/expandable-composer.png create mode 100644 packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.spec.tsx create mode 100644 packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.tsx diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx index 8baf1cda2d8cf..da788b2051fae 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx @@ -1,7 +1,7 @@ /* eslint-disable complexity */ import { isRoomFederated, isRoomNativeFederated, type IMessage, type ISubscription } from '@rocket.chat/core-typings'; import { useContentBoxSize, useEffectEvent } from '@rocket.chat/fuselage-hooks'; -import { useSafeRefCallback } from '@rocket.chat/ui-client'; +import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn, useSafeRefCallback } from '@rocket.chat/ui-client'; import { MessageComposerAction, MessageComposerToolbarActions, @@ -11,6 +11,7 @@ import { MessageComposerActionsDivider, MessageComposerToolbarSubmit, MessageComposerButton, + MessageComposerInputExpandable, } from '@rocket.chat/ui-composer'; import { useTranslation, useUserPreference, useLayout, useSetting } from '@rocket.chat/ui-contexts'; import { useMutation } from '@tanstack/react-query'; @@ -423,17 +424,35 @@ const MessageBox = ({ {isRecordingVideo && } {isRecordingAudio && } - + + + + + + + + qic4{KhZ0C|3-0a^ zoXg(d-us-F`*7~VeL4?WnQJ9;ESYnTHRt$^f4Hi$Jl->kXDBErc<1_1-sw7{ppd-&>x24U{ngRqK~!gTd1;hV7}fUU4!VVuk`xL`MJ&#( z$x{>*C4={Gr9OI~?x*9Fr%$*pB46~&g}UwMfnrGtMn&5 z_3!V896${1TNk%T_xsJ#s>6jF@3h0_gR2Mg(t`=$qM5{`q4)1Q9dDTCXA_fUekPUH z+cCS~CI?tUbUw+PwMd)MY7l`m7X9NUjId&9&Y5jvt}1;IbDjmcMQ%}YdpkEb=d5{< z&epUPOls({KWCM1J!tyr&q*>HwGu9npSnNb>b_hd01tq z8U0&ZEzgmd-qoA2evUeOcetN^?9}hq(P~t(zBQ($TW< zxesZutW}Sb{?FsWs)*7ZIREayfX)B3#0aBBslLPF z-HrydlQ6>z^OBP5x5LA5%6ZRT**`58cA-1sSl4=^nLj6SaI46t(Sm+WYZ!D|Y&-aG zb1Evk<|#e2uKFztng`}56uSCHrYz>d4vp@I*bY@SGpVbfUlh)VTvz?e-Hb>Pkbd~kzmU~xssrJ9O^e%i>*1q0w#exxc_Ygk> z_P_PhAUDfnA~8qXcIV`gt)x^-e)s8Rit8Cmg)aWF5YZvA2dB1E@2(cEMutApIf+mF zn+xFO?Snp;_|V1H|hLvvOpleVOp&ryH)5TiX; z&EpE-HurnDPR&0DUqD*VDj`>Q7K{9w_Xuyp{nm%3waaJ!b5Y%Nw~G&TOTHUFnC2h0 zZ+jJwxE{s_S`XodceWCgqDqSC`5r5XyVpLq4Z#bQ7XOX^5y0Xh6B+!d*v7AH)%k9H z@$B&Cu9q0&RYoJRAD@H&!>$?E&7#C2`M0?LJjp&P6Pcc3ho8hat^KxMnc|{awZY%c;fUnSULZ zWh$*$Q&@+KsF3-^y2j7{hATbeb71>F*67Wj{NM6lBg6db*uOq9tm3$)3_LG(c>{^{ z@vTN8Q!GjywGEFW4{llyz0)mYsEq&ZwG4u{JDBs3`x%IKquXZa!r@!zYV=S4_|@Ki zl{*Baz-9gvE^*jx{GX2uQvId)~Y702e(~ zWSW%%l>akw6|u5TBU6ky!CELK9pBK{|LO5Wl7m+*XPdsW;TLiL9W;}J9a{cxyKiBK zTmRdx^y9C<`TqtV(C9i_qIB)n(0!<^zU>&3R5)%=glIWjFcuVWtz9R!)14akPRDTF(JYz3`;_qgZdhO>^I66u+Ii zLsjA}k~lPdA~KMOePcuR8$0*0fs7lPBhP7MPw4!sfH%vVfATX9ZhYWi{Kl&*qJyo9 zRfW;02NhF8M)HC30h{XzTQm43jSDpVzFBOk`^?{~=~M%9?kBToJgkJpL$0IutWgs! zYIU=**ssUHvxlqU=3*`z1ZR*2lqY)#7A+4uGzUJ7j`PW20 zI!DaGE;bJo z{4PQ6>o3|0TAIz?0*=T!uwFeOGF#ly1uJN|X z&TP?k@0|z9eeBYQcW46{Pznw$KpI=($K~1DG}1|lF*Pm0L7CFQ62;)DV308=`?A@P zvN+XOG6ghiBZ7%oyo+hIK%C_jurXD*k?y26R+_IDmQXqEi?c%-;ed|Q44)liE~8Y= z=1g}kgmEc{c2->0>hJ9xeRBWl)2{2PD;tr!inH|$gZu;2N)t%Mw$kR!=0|$1HWRO% zohsq0!ZG~m+mLBrPFE!&@2vL_)nLYbeno|-kH90tM26DBS#4ZX%AmV!j#J^6|`?~F0X^ZzkjQHj?wICxT&`Aot zFCN$D8iufTjcYAwTyK8BL2?zY?jKNtdjSJDAkrUZ@uW~Y0w>o%x60KPZLUH}^!X0( zrO#mtBs>unvfJocQ;w_Z>w&S`{`Dj{%{km|>@0s4I6x!y!2xf8M-h$M7$FcBo1B*T z=Z`Jzjpev57J_=To43l?=ssq*3^wrThtuN#))QxrjOSz@lVuT$IFW9UY1MJ_06<#+ z4b9oN$1#i*c%8hGy1`Cg5bxNj`}K;b@qYT|;8_L1!nWrtEjHI!6~+(K`%U4t7% zkG582J=?O{nxT8a@tnm7rjJ?g$%Cx|?397V@gi&153M&zbJk_qgWaErdBb^?o<^3K z=I8E-Ucj*~!@Vl3@ctxdC%jo3mAgZoshf5RS&elHTc@9JA7nMUnVp(1jlFNY(SA5F zy0jdk&pN$fkT{W1j=~4Fo3*=5ikFWCZ#G>KF?m(-T>_{l7Q`UoZZ01;K88`@Rl}$6 zj-^dHg$mIiw&{!9c$dD8<0n*QScGe9?Pfh#^tcpa5D?Mu_ULrcn6mNUzW3Jhf~3-=Z#*7# zL)x)_%md6omd57l8tIPr;Z~WvXVRoPAnizW95@D{ENDNqy8RXPEkxLRYR{M??9ZP` z+ru+Zyz@p%l8%@p$?NeL899psK;brxPu$>|dsk(x(X`>); z(FD2DNg(96ecNbKf-6OB^ZHB3{YMGaX1|fE4rfy|fT$QMylQPN%~dB4ih?Pk0hWI8 zGf|CFRa_S~o^RZBO}o`3z|MDJe+%3B9iN_9$x-ld?1NjmPF8!sjj|rQ?7!wd<(ZT&J+D`&!1gug*WqZvN_sxlh93$j(q##%OnT zYAj+cu-@EA?0+8XvSL#M!880-jBmc)tit3zFt>qe`va=jvViAeC1f4x9(+v+Imc*# z?U!K=0MEi8>wsD8(V^?GZQdukWI1V=EZI^l=K47EUjt0yClUpX;>RDTnxDS2&|&I1 zj;uS2A&LOQ-IB&08u&a$+O%vLlw2%csI$E-puA%(kJ0bOxUxvugg_2&{06Tk)Z|y? z`{<$xL{8rZlXSl3@R!Sd8u2579OL~UbFcG^R9<3IZ0H;EPNMM&n4s3EfBc~^@J zM9v38OB?_3Q8~^4^uqNW79k=uUQj(uMp}( z(YAb*m)E#@h7m4h(E|;()+WC7`Oj51^~#aY%ge&fnc^(`H+6vJ7t-oUR~J2mWD%Z~*r**&^+~U=N>$tp}orp%l?lniP ztX1!g6l*WYpR08JHI!I_;!Gz6qa$jOis6E_hb$j%X!6&A8Z5&&ZB@;5BL&!WKt+&f zl-Why7eiG>7l2ch|CWzaj16G0;05v=e&JM2bGYGDb2KBn6rJA$2l~CA?YM9^`o?Z1 zp>VuW>P)?z3GL?yoj+~DcUi0uVCxf5)kue>z_3peiz(U6D!`xi>W}RP7f^O}k~k12 z3SWq(h@oG&&iz7r+YA~r_?(t<5RwY6W2zXKx^zreKE$rcv%47T=Yx_T`~xh!4h7fa zC z)9w92%yPPd#6X$5U(cDgE$#4P{O>dX8zR&wp{UY0X&SBREjhsD`YKWwXk%RhNzPca zNXyzR`qgUPXqygo!QOwm#+ENXjqm8`5G4g8w?Gj`0mr}8mdi#LetIWg`Y==8YAIsk zse8p)b^1M^xlzwnvNSq#A4xT0tf@WtR@hfcPyuh@TGf-Pr@%enda| zN$IDWJq0~9>I80KX@8h^qillOWkO?Kl@;&aj!wT7E=Oku)yD=3dzz#ed)fi!;9%(n zr>G9$XVK3lSr{xi7ny&C zQv2*p&qkpr$1(3uVzL+4?PiWMb$UqyzaaV74&n@HBe7EQ&3`1_$s1}xm~GwaYq!ec zvuL8yLY>ON34yT4{isY2EA)@p1OYIo#gWkYk=0}6AXFz3`PDLJ{`g@}laDE=WJhSP z8%(;q>?N1dSFyrW1-V>S;$nxAY=*>B`$&WMUV(HrN#u;`KSyAY3U`>&u1i&w6p*oF z<5A>2wE^FV805MLrs;m@SoP;FZamDAKbdmd&DL7V!*KoaBzuopK>}yaRiFrCot3Wy z*hNASeM{+T$ba`f+u4<`uP0IPVp{8Utx-$V96LsGH*;1?cD6wLuIZ$shDQ+A=V4ll zRkUR)XWNmFaz&$V*QBxgtVuZDO6-y!Q$K$5-U^aAmA_ugQieqNe$NCSrS}-O1Lt!~ z@@Tg>Z8ruv(`1C&3h-f^8>X@Z-A@X3+H2>HrY!*Ka;yIG_)_L0_NV=QR4ojoYy%o+ znP(wL&vrviGDh zcuAWoFAL4|lYO^{ckWCGFz(3BA?mh_EyLKgq6OX1=N^xwD|zlv){Ob{ip#w z2_UN$0cC^Fj#c+dXSGc<;z5 z9YCd-W*^39t5Q+7Sx(C$+h?#&mTyA1k<0GIyrla>)ABp1u#qU>k8oS$ z&8hL++^F7$MUrnYp%};VRShu2MCkgYwEm$~c}5MkJFfJMRg8{D2 zdOE1KLg(s45$b51?!Jh@3VRNaQD*Eo0!WFE^cFqEvN89cWl;1P42j`9QlMyxF+1K{ zCE7*Ne&1&FY<3v6^y7v3lF2mqeZpD!VvW@@yWtD@*GRx&`$A3qT&g_xOzz5@a=Y&o zoPr5Mfg=p6=K8vq8|pdCOYy>uKShE+YfS#e7Bs&KL79~smpa&){c;j-Gzs6jGxfUq zEpngz5TFs2Vp4u8bLjpUkWo;MA_cZHk)Ir#4{(3&Rc|3YI&*t={w@US%=l+>$5 z-ZYRtm--SR*3$EhSu-j8m&Q^<#yX9 zsJ+0j)i60~Z4?+ZHH=NzNI-y{;KSriV?$G|7w4}b;BEXic1({assts3rb$EbELJf) zoZGM9N}&sN({w^C@-wh!9coo0#?DpG8`eK=u_te+Vf0se9>W|2FV$GN1d_FN4RA-Z zMjM`TfAJa-P9o!%^}3Dv(EXl~8KJ+nT05E^Xf=;i^^AuxS`|FM%o{jWOKiz5T>uBu zVs(tj)?+HHLEn)xwHzur@F0|JRSQ|=&biC^UV!*qx;~&m>2ddnyxSzSN7Gt>Hq2cG zU2`Is;cuavE6pRswwMLhKUlZzdF$0QIsNgdfHs*BXWJ||3qPB)&FqKMv59@r8^IjX zhDCTu-%UV_8o+lM|G_{&J`GsT@M|Pz^0xJWeZ*z_FMOqQ7Tk$8@mD0A` ztR6@n8a&d?A;3ISo~Rpn(QvS1bR4svD{;f8;CpoCI&~ZIa5Zgjspa=P?$KNCeG>+v zQ7ORH``&W1w}{QY#=zHsQxiQmg$vK!9gF;maNn(UZ+wh;ISX7fAqxQ_LJ9zHWdn9D zozdZOSxAUIN<{ytpxv8lcm52hz zYp$bPQH7*UGYFSAz5;!|0axnSA+E?L60vEdI&EnyP6CK{-=*Zl7+0>%!t2Sku7B(A zwVwZY-@uH4My|c`QqaZ;5INy$^4YL0sVNB}!pkEVb9VNA$AetB4jh=DwuFX$d8wg`tk{rmYtJNUa#L(85x=d!mrYztpOLvMs zSDO2k@XIJqJVmK*LxB@{?VZ*q7yk9|F?@OsO_jJvr8PJSgm0s)3iJ4`q2T*3B0rrF z9hm%*!3j+2t0Zu%eA8&$9s)JGjtY%2Bc(Wgv*l%hX%3r<49K;6vtUvkk{<4=D4jhI zFG=wpNVLSKOidc@uW=0k028&xX+>Dt;;me(!opnaNRx_eqx7AXjjCtn2%q%;N30*i zw?#C!dy@oD`lhSQ?gkCIw=#VK8&T_R<})?y)ddBV+^!f0X7a==&^xfCXgURY-8W&p zH8F039hKYi)OZZ)PRvYM0YUI@5*-T`WO_#^h3Seoh$VhjcPi@g(;GQAp9GO6r#)SKfvPrn5p!K} zSSoxf%5Gz5irQ*paP%%;$AB(#_^%L?nT(K&oksS#D4?+3!py3*bn!ULP>g%Ug_2kG zX+NX+bwhq4FP6`Eo~x&lA2*1q3KvflMnc$`~g_&iJ&+RMFB{v)e# zH-bJW)F~yUXVFGAmd`UEWv#YILz4Ooe z#-dsiHEiy9Msu=~zGSRL3E=(wW-5=HrG5RNdRaBZhv^{Srt)x^?n#~Eh?L47@x7PD z-d9v#eQ^SF8aQ})QU=aXXW7`_ioHP-#W41F4k94l96Z(uv=TaXK4 z4k#oC`zw5AI$cGBJePbuSiBdY88JLM5tZh;u}ed~vDpUN$k`+}#`;TGM9f<9@<)J$4=aqoD|S z*#-nh+|2gJ?qjMK7wl~|Y{A>1%Pk7~!H(4(QqVAAV7$mq#duCYFqr9vBd_bpO!uh= zUQ!%Gx=SJy7T$-vX)B+C61(DP(`AVlmBisdi*5B(!gS3Kf`4#v)U&LlT;0Flh1~Yz z+#?t7Cf)0;g1&twKdueW4$kF{%&xbH9W*xns3<2~-DjrA5Ow*9{r$egiShS}a}+ri zv{&(~wNSfw*Q(>tg)P4^G7JGfrGcbv3%!btLAgKmE>BZ>1wv9tbI5x=HDrEBVOM#qsuTQ?i+uNM8zvNXYvHs7JT`8Qdwht2Y$VHWxd z(fe0HP}Ie5rRn0BzR!0|rsc-#cg=@yl_G4S^^_TZM{CYNU@h)}WT|6YXP_)pH+OyFa9?JmRp)zsR0*2px*)xoX zjy)sJ#-@2*o``IZM4~9w5?xjvYq|^+7lj#YqNrCbCU<9m7;K*3mKZvZIS+#!r*ee3 zFTC%|Z(mfU>3^|An>rHt7AlXu95uUOh4R|_W3Y}Lm9R^Wdn<4j^*j{+<GBp5pk^C*0$_abC@$rS-;k8I~>M@gVWV!lSL zgHOH*{Fj7v9Uo5q1?Ma}VIko*`SsIcCF%LE{-2VsCP+}}djo$-K3e>n#`Ra~|B__G zO8f`Sg{4UThw1+REibt+y;T^5LmcE|somvcY22+ROWZbFOw3MZW#ClX26fBl7B`E3 z(_T|gjXpzSQ?hSFW5}GxAH_zHQREf%{)v zFTKfbyU7j#H`;J(KK$~4)I^l+$0Nq>#pl}e%TLO;J5Q~Fw^fV^tC`Wfd3$z#NRhcm zQqmRuHC=Fi`4cN>#)=>8}(3emUrc44$~6RtLA={zaXCrjNC(gcvbI}H zg>YvLfA9S=qkYQlGIG)Ws+N?@mn9|tI8gKvYKkf61n8fitsIIkB@#8+H_b!)eyE7; z=TS@4BmUq}`8%S5{okl^4L-ZMUs}!(2R?#~QVu8Kl4tSu{k?CvEq&E6_a5Rlr}_TA zUP@p)Ub`av5hQwLYe;5^t@xGcHBIh6bl#;yS`Lo6B&t6XCr0oz$1qHg(FgOg-y2Gt z^F1-;C6o28sT3(97alx0O1IP)8#m-`BDnFM^zQ)7bgK+3i5}u@p6%$$tvLAGNm3i< zfz<=XxVNpSNc9$+DjqoGzcdpoq`;5;vQ;{xeBNn#tGU&Z34D4o@1NZlb2UTKrl?oz(1 z;JQ49jO`L82#%{l)4hZm-JGDLcbhr+j{3&i>{LcuCOt&tTz^r|7H~^NW5E}$0T@Y_ z9H5*Gxcfoaf!x#d6M5)groUBuqE1L4@cb;O2+-V-K7ZZ&7>=Ugeg}}xv00*$YkuauXx6{p?Z;X7 zw_;v!tw*|Finl_2?M_uE8H=hRQs>foK_~IW_Nw{wsHuJnP^Zsr(jf^C!nS$)Z)$%B z4rH`EVofBNRKb~A45PCVCMI$b_44|tV$y|vw_xCA_Y9K9 zf80}wXTSA4#xOI18yp7flhi~HVC-ypS1anUcn?P2SZog9QQhvCwX~%G$cp}Accf^b za4>yEE|1Q$V65iCm`=Hm(d#b=eXL%o5MgnKh??NXC&;kX#qr^iWkz*X7PE#j-+Y|O zYOQ|=l~g`zH4Q(y8}X9*=pasHg%3d;b|V0e9#YQMO(pQeTzK8ZP=YCgZ>3_SO}}wr ze+KNAlm-O4yi_T?cpllPtcvl-2Qw=U-fh2LLw!UU4A|?uZwlfJ`;}OeRai#_cH5JB zG)h$_ztPm%ijsol#S&g><_p}#Sr2%Y7-&vF>f4gqmcTxwCp930sqn*U(B2QULN_x= zv}oUKEWZBOL?QjVhr~KV(eDKt>|?4K1EZ>w2mbq^B{VB7kyF$MW0bY_Ujp^LgKS>n zn|RysMRyHLf#7m?^rJVow}Rf=fH6fet-K_&eU#~ocvk!^wrVty;m>2!Z@jAa*`87B zub@DLMxgS#Yi6~21b}w8cNqT;VEL+Baed&A`6Yug;PQjlI0h_Bq}Y5OF8t!b&4*J>5E9ZJl3jQ960z zAaV8tdmXj%J4JiZlh5DwiQx<7_cliO`Za3f@SV@?JabUX<`Sf<^+O>g#yF*-wW43h ze&>$LfW#T$ZdL>*a(%P`^Q7gk6##&nVYL^7f(|V$T}pTP-AlGp=G}$N&bao|vc}@! zp>e1Qg(Sjj22*bj_nCtn!Tx6*KE=Y;G`_rH)VSbsuCgC;llF=#01eE98I~=|$G0Da zAZO8;(J)Z|2?ED>eZdB$V5M zD?wkE9T}IA_hE6RPSMdYb!@4Wj{e!EO*?JOxwc0b-U#iRyuQ^1ZA8~mif_Fj#I+)m z`fMS|A za0lK8pgjS>xFoN#=2SVgVwgh@}XlC#ufBM{0Cb&E(@mC;``J2SnS|TE&%^hWF9SY2*;S5~#b%b4q35>RPv|(fSGwK+p1@f^ z;)w@$ICB~Aoc>z{ujjg9(&gcS&4t2sd-9@^nQ?c@*1tj_=ZXr3n z@@5h-FI0~9vGBgwLf-XE%T6{dH~ds#*iLt{&}}#Ai~G6pyj!0xLcQr=QiT!30^(BD z@3w!nQD2~_cN%f z+ao+vVkiJm&BU_27jL_47W#@d;G{pTEUYe7bw8G9iYQa{~jeslE2k z`c;cs)WuEr`JZZ8PFn;yI27B=I8c`y6C<9t=QdoH9*7hQN`A06_SDzlR(+y6#S==v zs~@lHCCBo*xXXmISfW9~gRIX}RXGyubzCLK14WNmjlae-sYOyBf>o+*XE^A)#1|>7 zuy!dy7Ij)tr&04##*Hm=axM-P)XR7G_TC51dPklf_KKKNF$ICsi`v0%&k*{GQ*`zf zpsDP3nzrPfHl2$!LQZg=qi>eXu5dwp#(MnhMn!$PjqQE_&(2p{`=&P;i^{1dFk5rk zr`L_iCDK~Si?!e+J z41QB>g`T46^Mz7wws*jn4P8ZYWrpMqNtoov|RCv##NCqOP&QmmY_@N^dA zB<_p0l8Y)~T=6jG7VYT%aau)5LD8(=3vUJcLrw`eK1vYG{>RMwMLYQZDU<@Z4|Iu1Oin$=$>Jo!pc0Z79K5%ms=Hv%u%u$0@XV**dPumpmqn*cYfE05LB9!79}hF1N*qkeMN- zNHIAloTJHp6jsz_XwOM0XCb!7fa+l>#%yE?*0OJ~5c+|OHi+rzxnpcE7al^7N!^_7 zplwp^94>+zVCY}#%;cxyhASuA+gkXNc{I9Z^_4L(V6~pS^@r4a1Sq18n2%@vZeMA^ z3UA=#w0V~!_f;JJpgAwMDq+hdd!31lnxPxl{wq}h=EvSbCj8Xadg!N!SCxTVaFl@< zVQZ4@xV(SsVaJ}xG)8Fd1KZQl?VUu&b19{)Q&i8=*`z656ZNNUXAP_6JRztUDOWq&-|WT|o{x^`d*1=`D}n6A@yzxiylX$0R3YV|u9RV^;w7s4;T zLZyu2sW}+C*&nmro|VJC=uLxCT#1+Kqww9T16lV?;@6o2P9sOO$v!;JYSU9W`KdAX zhGmRD^9-j|*OSY{P{POl1sXOlS$%%;~#|(4$X*M0=S8=ywT83luQP;?Dp4`|@rCcY! zw#bk+@xJWW6!_H|i@x1V+f!4YJN#MUSp{WGelewA5mB*blW`}XCgqCTe~=6#PlP(8 z7_zJ8LwH_$?bj}!SoG5Rui~WpzcJ(X4~+1&Mj$v-i^e#EEFV``L;#W8+6roE)@NC{ zQ{SSj#^ZWbgsOB%DYuHE-c!+oh!rx@Bg5*GUqIr+CRorWV%+!!EDtRi_^O~Orer8v zner{Sp2^(O)@m4-VBitf_m`}vA_{Ql-#CFyFZxtp9Dc3naW^$5!|Y3J@Vui^v~mj4KI zC*J*vo;5^XX5XJ>OgIl`iL?OD8y_;uBGstu8M|LGV0uVbM0k@`QRyhZQ{Sw5?qc_` ze^saV^CF@4w|V{`R$$=iW`PW(n(+&wnupjwuiNjZkQ9a(M zmX5A>O?duY>-j8~V(GsLU@M&-TO79H?q&{3^hoZ054^N(D?Tq7C#d2GNd+;Wy5%h| zQd!QujIZD`ax_6UT}4*#x3qF%Lp;$N{vyvd>N%WnS&mpWEx?7)50e2NOdi#P3*vxr z6jW_P;}_E8U6YZZju-pL<-J!HI?hgM!e7jZr`sdCwxGq|$E27wb~1%;VjYcU9K~}! zpbVwmTR-=&Qognn{D@ttMV0>%_vBg#2aSEmg0^2WI8qIPiTL^b15sToL22c4)5$or zIe%8_gZ4x2KD|p&M0gAi#B64CgCl9`U~T`HiUqVy{lPh8xjvC{)_wKpoH&j8S+x2= zE;ceN#MlV_RHAi8B%Zz}YK(!X3V#SPHYN5F2e4WW>#w$8+guCkYtBiut&l;&_Ra1# zAD$0@d5Aj9KFwki@RxSr^^g9Ai7FGmDxA&EG9~>m8~qq##k>h)tFaiI?AWb(%ty3> z*NjPN=S<(I?}u!rCaTp?4P2b!HFhVdO@B*dRTi{(Z~%MQH9mz)gNfNNLb7jefq%l^ zob7(itw7l#=#q-aEsJ3Xn8k7q_#IQNdVimC#O7U-NL|G3^@izkT?yD5P%T0A_Dr=4Z{%wi$< zUB7zsy)m)gyKbA8nDwZ&T|Mj*<-ZsXJr2FSo{=cL>H?rY$iI<4i2mJV-v_yxBJX)S z8@Z}`Ew96)H?WzlL1od7E?0s6&O&G>LNxQV+~QuBi~uXM>KPucx5T}n`{Za?A#>zo zNG)w&M-J~~!dX=UDLENu`+=EBA%=z5=_~BbYbH0<{J6qPMIQO?Vwy3{-jH+ICjUU# z>T5Y&Kb@C--|mLPvK>`CZLG`qGP=m~lb5%W+Qi0f@&Zm!Ji5OJpA2`Ge^s1bO_fd$ z71cGXDzqVv`S?w|nlhdcZY_JAWzUkmZe_SS_Hz_M=L-965MH(EjDImY?4qdxA6Zd* zjIvcPecA!AuSZcQJpr4h=M7)6yL8svTg2&V<)mAcs~++iP#sk{7zbQqZy$kIMmnve z4H7wyKT{8ntlZglI7f}pn4w|ECG6VQkN6$by^?*ofY8Ko9kX;*ot7^@+Ds4+ee5K`lM4c5$d% z_f^g0cMVsWT>Fx>Lj@)K^&<~rVcB9fB=Gk%rRf~zr2D9LC@_1O>>M79h`@1)<=G%U zAJGAHY9Lab1bRv$|1~RYm$R&^D$e}i>03in_@-yo>H^b~E`%_yIF_vTjM+suMGueJ z7!963uWWwQO{AGO$sv+j&!H7EA1K%Nl0?UxsvX6I`!(4&2RcL)b0K-j_-VsnbdF6P-?Y>Uv*iQvC!4&B<@7##F#w^EG?Sje|jZD zd8AzD$s{REVxc-UNi+RR^s;swFQ=#Wt*1ZxU%@ts-H)WWx5qQXp8CBy=qI1_Gk{wQ zH3lM!hl^;Y2*&b3!eXU?C{BIMK;C=_=lv&Fl2`Qh{bkO<*;AkG`7p$nrOfM~hr#X7;wh z{VQKvPxn0N%?b0Hc+y$fBCbutF;=T?z6-zQ`_VVoV$1kqD~IQ3c=KK{+nJ40>D(SB z`^0M;@$}=Uv#wucBg9w%Q=zWwwsDKCE#JgV-hSR8M)(CcmfJy=kmH^24{=!~?bPg* zzM7g4^wlDy7%7ExCDS{vbwo|FjJT9ovn#85_I{BsVMwMB1D02CL-_}1(bE?m!&qE1 z@^4>jF$W$GnMZN4Bqaq?bA)T?_|oAOv2A8plYO zHxbM2K9E()yQy9vj8u{C9(UxkbuvAM77$?jpzrg`5;ai9m;bI#@xzFukn;`seY)oz zhrd@E}`+u0OA>c|VBg#h;2zfuRrg+4#SAZf?)5Tle#@|CY(U#Zw0yl?0G;u|EWLl%Bqo zjS>E9p94#OLj-#LRuL20gF5-#@-Ij<9wN`I^o5R^J`Xj+CLrL|qdYJQ<7vn9M-=nc zww|dhy5(AW@VSmyz5W|Rn$r*OjqWAFw&O}a6HzVT(GC~M@&8D&eZ~p=r3GB z1)+Zn#eOkH-3k}OXuob-a^)99h#hJX6Apkg%O62cTTQfN*vKzBoxg$49JFk1D76k?g+t zpZvx1p7wDpj6nol8{=QSFV2F4gWLA%J4YL(!$6q0gJ`kh%MS}K%U28i#86tH7k?8h zcf|Rl08OR-QJnF8u}sff>$vKG{V|gzeB1a@8dfhNajsjI-Cv6D*KX?x?J*R81VIY& zav5$S3+^?Di_;_^kJt1hT*Te1Rt$$eFLG>Su47DusKaRc_U`ae?99$*+`QMkZT^6~ z=bGb^H50^0R?m$^|I`qff`uDjQ&4kPiK=4soqkq>{#zdo<% z^oY5}E;A z@w0!-4<4EEW6xUjKAbmvu-^VnAYI)T0d$A!_aV>w=!S+`PW1j3Frx(;%Q?hs&+T2! z1uBQd&5gSI{4Il@T^}z@5L`xXUs_BQX~Nyi10;Czb(h$2KXvK?Df`jWHEqh0T9Cth zp?59G*zGm8Zg#jAd=e6e53#wL+?`I;?B*5ULMyAys%K;0Vg?u-k;_+-w~TH$kEO!O zM0Y!!-U`Rj%*BG$Wj)j9Jtv=zh_2qb4)71+KmyhyWQk$UiSbXNBvrtA42996NdphA z(d5XpKTeV@mp3AO9k;mg^LlTI#Ws@Q^t26LLu#@D9!i`BT5>vd71lQ7{ND`pVvTQ_ z7MFJ(6FW4Jx(&ZI77Hz^Id0gwCWFCcVD~7-b0YPe^LM|UDh|sKFecQk(n!H=`i!OPdfG6BHBY@ zk!>oQpfK+d6oZg9>`FrO~pCJr~d8}25@HtyFBUlNY_{{nR_|J<0(x| zC-%;IhHo|w9Ivah$X+(>eRVLr)p<(4y9^#2d&E0wCA3edI|N9*F$P2!1#{jTGKJ$& zJ~N})&JQq3sH|kOo@%J#At(V;*jv-TdUpT zu)SG_oMfxIE&G%O7CpuBo_nd+dD*dq7$%u_fRV>?u#(+DHJY-r12LVW{I7EdF@g2y2x;VH}tQ?c2&U8+LJeZfAuH8k# z4fh{v*PnIaRP9|-T}1*vQL&q}I1tb05h??AxZbcbn?>>oSXX9mZi&v2pMHv$0O7W! z_LB{upXd#K7j4P;vAKzU6+!+)Mf}`F(C7}PsUycSlY9I<5N>k6yO#<`U(Cb6H2-!m zru8P(5P8M}Ez!FJ=pb@m)2;Sv)^lT)0$FSI97b^f9?KLR&t0v}Id zQocl=K$Tnb0CdflFtsk3P>J||0#^vA_k79a-qDL0|EaR04lDHE2crgj*su5*=k-P1 zb7qi(G%+VQ^={7M4_a@yAMJpIWrKI*bY7|r)#*O~`=OZb!ZVN_>(C;vENkk{G8>|d zs{JnNvIxdD=i6G?j&a|iLq~kN+16Q}fY;QCMva+hypuX75vVB(RGlUS8dDbng+?2iI#EF+SzHsuT zzwZ1jOybX9{{5-GpMP@gH~r5jxp>QE3qN??1q;9ZpFfkl=BrOW?XSVlfB3bV7oLB9 zzVG|5A3fH2&gK65i{E-7h!@ThOL1c{i_|1q~bx4`s1V}scST%jN>{9JI zr-N+Sp&oO6Pb?4i{#f1HJk_l?=#4TN&0*Vf6`dDi+fZ5mQC2oFy@B^rK2q%+RM9(a z^t*gUOcp}jl*&)8Jj-BNt+EkwLdALjt@3hOBF|r%JqgZnFp58OUJzaj}zdNdn8u~zKqX`<%hC@@~hidVP+g)>3HnkV*hK=Mi3F(Ql%XfTgb|FYujLU z8-y#NENqu3({L#~ZG>2#;q8!4ol<9vGG9V0L;TFI_iY1K#I8nAtW{^c>UC~m;U*_) z!(a_y`@645UdJFQpsoSO>(M!2uVe~z@>IUVG!k=ZsP~*pli$H8FP6Hl&hGIy6tw>Ntp2&T?f_?4`ZEyTp2}N#CNQ zl9OxbB+6_qeW_rAuc_+*Gkw<4wb;+jJGZ}|sG>uv z>ejX?&S_4|hz&Rx$_cN>=eaEaxj`x+L^%u2ed3H)uRLc}H|SN!irmesuvvKFmDinG zSoj?$DpR$i1-(d;e$VG|SCz$meEFF#+Zo4Z^4g+lwBeB4NH(KoR+&w)Q81(LX|O-J zcW)+*tQYJZ%3&jGHkl=`5quHJGyAS?AfCF8HupLKYp76GIkQW{CCmp6n6NCWmXVd+ zR6W++LpTiQ7o-MDh;{H%GX`0?I>boY>r3;@a&0;TT&I2(<02d}_aEu$vMTz|8@OhV z9c3F%TGhNZ9Yh)gyc7HM!XNBw#{S&iAgG^>t+qnJmR;x&gizAnl(90M7n3VHAXh3^-PJF-a>_l!zIT{Z?RQ6&o)D`e zVuMt6wSg_qIy%IDpR2F7=}lfdv~+NmD`*%s-ID4TL+s#9xYA4r5vSwyJ_pCQGwb`h z?>OUC*WtY1)U~IVJq)wKonKchEG%4i^5n}ueabDo>9o1IA3WvUYv-JMjT1}YXkC?{ zLmM_MUIQgp)u5+BtkMA@fQb(da%l8MVw)T~To^#e%jQeAX0PROP@{yAUO|<*hk?8A z=);X>2@rINY<>GXT|Qm&z4X-uhGr2I=3vjzpkqbDhQUr?d~j)Q5iG15xFj&-d&eSe zs3E%v2e=N1#!kZo_0wjA&9eHo256J|rtDYI4}4dX$ix0!HR^}&vId%!vnB&$ET1ZQ z?aj1x9lKEm!^*8p00>NkS)4knV{F-7^~=>yC5+{r$E&dY6v5Jtl|BpU#eG~_jvoe5 zT*Yf`X*K&t^VT_|600000NkvXXu0mjfy6Ex> literal 0 HcmV?d00001 diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 7a27c65dab893..56a1d70030b8b 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -2029,6 +2029,8 @@ "Execute_Synchronization_Now": "Execute Synchronization Now", "Exit_Full_Screen": "Exit Full Screen", "Expand": "Expand", + "Expandable_message_composer": "Expandable Composer", + "Expandable_message_composer_description": "Expand the text input area to easily write and review longer messages without having to scroll up and down.", "Expand_group": "Expand {{group}}", "Expand_all": "Expand all", "Expand_view": "Expand view", diff --git a/packages/ui-client/src/hooks/useFeaturePreviewList.ts b/packages/ui-client/src/hooks/useFeaturePreviewList.ts index 840f3956c6059..f8fef33c53900 100644 --- a/packages/ui-client/src/hooks/useFeaturePreviewList.ts +++ b/packages/ui-client/src/hooks/useFeaturePreviewList.ts @@ -5,7 +5,8 @@ export type FeaturesAvailable = | 'enable-timestamp-message-parser' | 'contextualbarResizable' | 'newNavigation' - | 'secondarySidebar'; + | 'secondarySidebar' + | 'expandableMessageComposer'; export type FeaturePreviewProps = { name: FeaturesAvailable; @@ -73,6 +74,15 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [ value: true, }, }, + { + name: 'expandableMessageComposer', + i18n: 'Expandable_message_composer', + description: 'Expandable_message_composer_description', + imageUrl: 'images/featurePreview/expandable-composer.png', + group: 'Message', + value: false, + enabled: true, + }, ]; export const enabledDefaultFeatures = defaultFeaturesPreview.filter((feature) => feature.enabled); diff --git a/packages/ui-composer/src/MessageComposer/MessageComposer.stories.tsx b/packages/ui-composer/src/MessageComposer/MessageComposer.stories.tsx index b15ddb1a3ea0f..3c03bed370ea0 100644 --- a/packages/ui-composer/src/MessageComposer/MessageComposer.stories.tsx +++ b/packages/ui-composer/src/MessageComposer/MessageComposer.stories.tsx @@ -12,6 +12,7 @@ import { MessageComposerToolbarSubmit, MessageComposerSkeleton, MessageComposerHint, + MessageComposerInputExpandable, } from '.'; export default { @@ -48,6 +49,21 @@ export const Default: StoryFn = () => ( ); +export const Expandable: StoryFn = () => ( + + + + + + +); + export const ToolbarActions: StoryFn = () => ; export const WithHints: StoryFn = () => ( diff --git a/packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.spec.tsx b/packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.spec.tsx new file mode 100644 index 0000000000000..804c21b9bba3a --- /dev/null +++ b/packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.spec.tsx @@ -0,0 +1,117 @@ +import { render, screen, fireEvent } from '@testing-library/react'; + +import MessageComposerInputExpandable from './MessageComposerInputExpandable'; + +test('should show expand button when dimensions.blockSize > 100', () => { + render( + , + ); + + const expandButton = screen.getByRole('button'); + expect(expandButton).toBeInTheDocument(); + expect(expandButton).toHaveAttribute('title', 'Expand'); +}); + +test('should not show expand button when dimensions.blockSize <= 100', () => { + render( + , + ); + + const expandButton = screen.queryByRole('button'); + expect(expandButton).not.toBeInTheDocument(); +}); + +test('should expand input when expand button is clicked', () => { + render( + , + ); + + const expandButton = screen.getByRole('button'); + const textarea = screen.getByRole('textbox'); + + // Initially not expanded + expect(textarea).not.toHaveStyle({ height: '500px' }); + + // Click to expand + fireEvent.click(expandButton); + + // Should be expanded now + expect(textarea).toHaveStyle({ height: '500px' }); + expect(textarea).toHaveStyle({ maxHeight: '50vh' }); + expect(expandButton).toHaveAttribute('title', 'Collapse'); +}); + +test('should collapse input when collapse button is clicked', () => { + render( + , + ); + + const expandButton = screen.getByRole('button'); + const textarea = screen.getByRole('textbox'); + + // Expand first + fireEvent.click(expandButton); + expect(textarea).toHaveStyle({ height: '500px' }); + + // Click to collapse + fireEvent.click(expandButton); + + // Should be collapsed now + expect(textarea).not.toHaveStyle({ height: '500px' }); + expect(textarea).not.toHaveStyle({ maxHeight: '50vh' }); + expect(expandButton).toHaveAttribute('title', 'Expand'); +}); + +test('should auto-collapse when input is cleared', () => { + render( + , + ); + + const expandButton = screen.getByRole('button'); + const textarea = screen.getByRole('textbox'); + + // Expand first + fireEvent.click(expandButton); + expect(textarea).toHaveStyle({ height: '500px' }); + + // Type some text + fireEvent.change(textarea, { target: { value: 'Some text' } }); + expect(textarea).toHaveStyle({ height: '500px' }); + + // Clear the text + fireEvent.change(textarea, { target: { value: '' } }); + + // Should auto-collapse + expect(textarea).not.toHaveStyle({ height: '500px' }); + expect(textarea).not.toHaveStyle({ maxHeight: '50vh' }); +}); diff --git a/packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.tsx b/packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.tsx new file mode 100644 index 0000000000000..86ed216e699b3 --- /dev/null +++ b/packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.tsx @@ -0,0 +1,55 @@ +import { css } from '@rocket.chat/css-in-js'; +import { Box, IconButton } from '@rocket.chat/fuselage'; +import { useState, type ComponentProps, ChangeEvent, forwardRef } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MessageComposerInput from './MessageComposerInput'; + +export type ExpandComposerButtonProps = ComponentProps & { + dimensions: Readonly<{ + inlineSize: number; + blockSize: number; + }>; +}; + +const MessageComposerInputExpandable = forwardRef( + ({ dimensions, onChange, ...props }, ref) => { + const { t } = useTranslation(); + const [expanded, setExpanded] = useState(false); + + const handleChange = (event: ChangeEvent) => { + if (event.target.value.length === 0) { + setExpanded(false); + } + + onChange?.(event); + }; + + return ( + <> + {dimensions.blockSize > 100 && ( + + setExpanded(!expanded)} + /> + + )} + + + ); + }, +); + +MessageComposerInputExpandable.displayName = 'MessageComposerInputExpandable'; + +export default MessageComposerInputExpandable; diff --git a/packages/ui-composer/src/MessageComposer/__snapshots__/MessageComposer.spec.tsx.snap b/packages/ui-composer/src/MessageComposer/__snapshots__/MessageComposer.spec.tsx.snap index 4c69645109556..54d541276250d 100644 --- a/packages/ui-composer/src/MessageComposer/__snapshots__/MessageComposer.spec.tsx.snap +++ b/packages/ui-composer/src/MessageComposer/__snapshots__/MessageComposer.spec.tsx.snap @@ -189,6 +189,211 @@ exports[`renders Default without crashing 1`] = ` `; +exports[`renders Expandable without crashing 1`] = ` + +
+
+
+ +
+