diff --git a/.changeset/bright-humans-cross.md b/.changeset/bright-humans-cross.md deleted file mode 100644 index aa0c4c658994..000000000000 --- a/.changeset/bright-humans-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -Federation actions like sending message in a federated DM, reacting in a federated chat, etc, will no longer work if the configuration is invalid. diff --git a/.changeset/brown-crabs-chew.md b/.changeset/brown-crabs-chew.md deleted file mode 100644 index 3291f18bf225..000000000000 --- a/.changeset/brown-crabs-chew.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@rocket.chat/uikit-playground': patch -'@rocket.chat/fuselage-ui-kit': patch -'@rocket.chat/ui-theming': patch -'@rocket.chat/ui-video-conf': patch -'@rocket.chat/ui-composer': patch -'@rocket.chat/gazzodown': patch -'@rocket.chat/ui-avatar': patch -'@rocket.chat/ui-client': patch -'@rocket.chat/meteor': patch ---- - -Bumped @rocket.chat/fuselage that fixes the Menu onPointerUp event behavior diff --git a/.changeset/brown-singers-appear.md b/.changeset/brown-singers-appear.md new file mode 100644 index 000000000000..8a9a69f225ac --- /dev/null +++ b/.changeset/brown-singers-appear.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/ui-client': minor +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +added `sidepanelNavigation` to feature preview list diff --git a/.changeset/calm-tigers-peel.md b/.changeset/calm-tigers-peel.md deleted file mode 100644 index 32feed6f7107..000000000000 --- a/.changeset/calm-tigers-peel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/ui-kit": patch ---- - -fix UiKit error message: Failed to resolve module: @rocket.chat/icons diff --git a/.changeset/cool-rocks-remember.md b/.changeset/cool-rocks-remember.md deleted file mode 100644 index 97af36e94320..000000000000 --- a/.changeset/cool-rocks-remember.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/model-typings": patch ---- - -Fixed login with third-party apps not working without the "Manage OAuth Apps" permission diff --git a/.changeset/empty-toys-smell.md b/.changeset/empty-toys-smell.md deleted file mode 100644 index 043d9c19567d..000000000000 --- a/.changeset/empty-toys-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Federated users can no longer be deleted. diff --git a/.changeset/fast-lobsters-turn.md b/.changeset/fast-lobsters-turn.md deleted file mode 100644 index ff1d97ea7289..000000000000 --- a/.changeset/fast-lobsters-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed an issue due to an endpoint pagination that was causing that when an agent have assigned more than 50 departments, the departments have a blank space instead of the name. diff --git a/.changeset/four-cherries-kneel.md b/.changeset/four-cherries-kneel.md new file mode 100644 index 000000000000..095d5af0aa76 --- /dev/null +++ b/.changeset/four-cherries-kneel.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Allow to use the token from `room.v` when requesting transcript instead of visitor token. Visitors may change their tokens at any time, rendering old conversations impossible to access for them (or for APIs depending on token) as the visitor token won't match the `room.v` token. diff --git a/.changeset/funny-boats-guess.md b/.changeset/funny-boats-guess.md deleted file mode 100644 index 076acff98329..000000000000 --- a/.changeset/funny-boats-guess.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/i18n": minor ---- - -Added a new Audit endpoint `audit/rooms.members` that allows users with `view-members-list-all-rooms` to fetch a list of the members of any room even if the user is not part of it. diff --git a/.changeset/gentle-bugs-think.md b/.changeset/gentle-bugs-think.md deleted file mode 100644 index fc4738f3043a..000000000000 --- a/.changeset/gentle-bugs-think.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Prevent `processRoomAbandonment` callback from erroring out when a room was inactive during a day Business Hours was not configured for. diff --git a/.changeset/gentle-news-wonder.md b/.changeset/gentle-news-wonder.md deleted file mode 100644 index dd3218003d7a..000000000000 --- a/.changeset/gentle-news-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": minor ---- - -Added a new 'Deactivated' tab to the users page, this tab lists users who have logged in for the first time but have been deactivated for any reason. Also added the UI code for the Active tab; diff --git a/.changeset/giant-spiders-pay.md b/.changeset/giant-spiders-pay.md deleted file mode 100644 index 1798cd2baaee..000000000000 --- a/.changeset/giant-spiders-pay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixes an issue where the Announcement modal with long words was adding a horizontal scrollbar diff --git a/.changeset/gorgeous-hotels-attend.md b/.changeset/gorgeous-hotels-attend.md deleted file mode 100644 index fd858d7ace86..000000000000 --- a/.changeset/gorgeous-hotels-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Stopped non channel members from dragging and dropping files in a channel they do not belong diff --git a/.changeset/great-humans-live.md b/.changeset/great-humans-live.md new file mode 100644 index 000000000000..1d97d9da23ae --- /dev/null +++ b/.changeset/great-humans-live.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed a Federation callback not awaiting db call diff --git a/.changeset/healthy-rivers-nail.md b/.changeset/healthy-rivers-nail.md new file mode 100644 index 000000000000..a8da9bec846e --- /dev/null +++ b/.changeset/healthy-rivers-nail.md @@ -0,0 +1,8 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/i18n": minor +"@rocket.chat/livechat": minor +--- + +Added new setting `Allow visitors to finish conversations` that allows admins to decide if omnichannel visitors can close a conversation or not. This doesn't affect agent's capabilities of room closing, neither apps using the livechat bridge to close rooms. +However, if currently your integration relies on `livechat/room.close` endpoint for closing conversations, it's advised to use the authenticated version `livechat/room.closeByUser` of it before turning off this setting. diff --git a/.changeset/heavy-snails-help.md b/.changeset/heavy-snails-help.md new file mode 100644 index 000000000000..fb10bac9ea8f --- /dev/null +++ b/.changeset/heavy-snails-help.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/rest-typings": minor +--- + +Implemented "omnichannel/contacts.update" endpoint to update contacts diff --git a/.changeset/khaki-cameras-glow.md b/.changeset/khaki-cameras-glow.md new file mode 100644 index 000000000000..87470d5c497b --- /dev/null +++ b/.changeset/khaki-cameras-glow.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where the retention policy warning keep displaying even if the retention is disabled inside the room diff --git a/.changeset/large-geese-ring.md b/.changeset/large-geese-ring.md deleted file mode 100644 index 9b36edf1c02d..000000000000 --- a/.changeset/large-geese-ring.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -Replaces an outdated banner with the Bubble component in order to display retention policy warning diff --git a/.changeset/lemon-steaks-provide.md b/.changeset/lemon-steaks-provide.md deleted file mode 100644 index 29f0289419a0..000000000000 --- a/.changeset/lemon-steaks-provide.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@rocket.chat/i18n': minor -'@rocket.chat/meteor': minor ---- - -Added an accordion for advanced settings on Create teams and channels diff --git a/.changeset/many-balloons-scream.md b/.changeset/many-balloons-scream.md new file mode 100644 index 000000000000..f017cdb81137 --- /dev/null +++ b/.changeset/many-balloons-scream.md @@ -0,0 +1,13 @@ +--- +'@rocket.chat/uikit-playground': minor +'@rocket.chat/fuselage-ui-kit': minor +'@rocket.chat/ui-theming': minor +'@rocket.chat/ui-video-conf': minor +'@rocket.chat/ui-composer': minor +'@rocket.chat/gazzodown': minor +'@rocket.chat/ui-avatar': minor +'@rocket.chat/ui-client': minor +'@rocket.chat/meteor': minor +--- + +Replaced new `SidebarV2` components under feature preview diff --git a/.changeset/many-rules-shout.md b/.changeset/many-rules-shout.md new file mode 100644 index 000000000000..eacb88108a0f --- /dev/null +++ b/.changeset/many-rules-shout.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates) diff --git a/.changeset/mighty-drinks-hide.md b/.changeset/mighty-drinks-hide.md new file mode 100644 index 000000000000..955d1ed760cc --- /dev/null +++ b/.changeset/mighty-drinks-hide.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/fuselage-ui-kit": patch +--- + +Fixes multiple selection for MultiStaticSelectElement in UiKit diff --git a/.changeset/nasty-tools-enjoy.md b/.changeset/nasty-tools-enjoy.md new file mode 100644 index 000000000000..b6e8dae3785a --- /dev/null +++ b/.changeset/nasty-tools-enjoy.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed a code issue on NPS service. It was passing `startAt` as the expiration date when creating a banner. diff --git a/.changeset/nasty-windows-smile.md b/.changeset/nasty-windows-smile.md deleted file mode 100644 index e80ec3db27a9..000000000000 --- a/.changeset/nasty-windows-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Allow apps to react/unreact to messages via bridge diff --git a/.changeset/new-mayflies-wait.md b/.changeset/new-mayflies-wait.md deleted file mode 100644 index 832db68cecd4..000000000000 --- a/.changeset/new-mayflies-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Deactivating users who federated will now be permanent. diff --git a/.changeset/ninety-hounds-exist.md b/.changeset/ninety-hounds-exist.md deleted file mode 100644 index 99882de12018..000000000000 --- a/.changeset/ninety-hounds-exist.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@rocket.chat/rest-typings': patch -'@rocket.chat/meteor': patch -'@rocket.chat/i18n': patch ---- - -Fix: Show correct user info actions for non-members in channels. diff --git a/.changeset/pink-swans-teach.md b/.changeset/pink-swans-teach.md new file mode 100644 index 000000000000..7c85572a78d5 --- /dev/null +++ b/.changeset/pink-swans-teach.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fixed retention policy max age settings not being respected after upgrade diff --git a/.changeset/popular-bottles-visit.md b/.changeset/popular-bottles-visit.md deleted file mode 100644 index 9e44e9dd7144..000000000000 --- a/.changeset/popular-bottles-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed an issue that caused UI to show an error when the call to get the Business Hour type from settings returned `undefined`. diff --git a/.changeset/proud-years-buy.md b/.changeset/proud-years-buy.md deleted file mode 100644 index 94f4ab0df736..000000000000 --- a/.changeset/proud-years-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/i18n': patch ---- - -Fixes a typo in german translation and fixes the broken hyperlink for Resend and Change Email diff --git a/.changeset/purple-dolls-serve.md b/.changeset/purple-dolls-serve.md deleted file mode 100644 index fc44faa60a38..000000000000 --- a/.changeset/purple-dolls-serve.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@rocket.chat/web-ui-registration': patch -'@rocket.chat/i18n': patch -'@rocket.chat/meteor': patch ---- - -Fixes an issue where creating a new user with an invalid username (containing special characters) resulted in an error message, but the user was still created. The user creation process now properly aborts when an invalid username is provided. diff --git a/.changeset/quiet-cherries-punch.md b/.changeset/quiet-cherries-punch.md new file mode 100644 index 000000000000..25c08506db41 --- /dev/null +++ b/.changeset/quiet-cherries-punch.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-services": minor +"@rocket.chat/rest-typings": minor +--- + +Return `parent` and `team` information when calling `rooms.info` endpoint diff --git a/.changeset/rich-pillows-hang.md b/.changeset/rich-pillows-hang.md deleted file mode 100644 index b714a5e6acd9..000000000000 --- a/.changeset/rich-pillows-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixes the `expanded` prop being accidentally forwarded to `ContextualbarHeader` diff --git a/.changeset/rich-toes-bow.md b/.changeset/rich-toes-bow.md new file mode 100644 index 000000000000..a670f0756f1e --- /dev/null +++ b/.changeset/rich-toes-bow.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Prevented uiInteraction to subscribe multiple times diff --git a/.changeset/rooms-table-ts.md b/.changeset/rooms-table-ts.md deleted file mode 100644 index b5055ad26f69..000000000000 --- a/.changeset/rooms-table-ts.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -Add "Created at" column to admin rooms table diff --git a/.changeset/rotten-camels-pretend.md b/.changeset/rotten-camels-pretend.md deleted file mode 100644 index 5145bbaa5050..000000000000 --- a/.changeset/rotten-camels-pretend.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/core-typings": patch ---- - -Fixed issue with system messages being counted as agents' first responses in livechat rooms (which caused the "best first response time" and "average first response time" metrics to be unreliable for all agents) diff --git a/.changeset/rotten-rabbits-brush.md b/.changeset/rotten-rabbits-brush.md new file mode 100644 index 000000000000..916f4cc8034a --- /dev/null +++ b/.changeset/rotten-rabbits-brush.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Resolves the issue where outgoing integrations failed to trigger after the version 6.12.0 upgrade by correcting the parameter order from the `afterSaveMessage` callback to listener functions. This ensures the correct room information is passed, restoring the functionality of outgoing webhooks, IRC bridge, Autotranslate, and Engagement Dashboard. diff --git a/.changeset/rude-dogs-burn.md b/.changeset/rude-dogs-burn.md deleted file mode 100644 index e81f00782083..000000000000 --- a/.changeset/rude-dogs-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed a behavior when updating messages that prevented the `customFields` prop from being updated if there were no changes to the `msg` property. Now, `customFields` will be always updated on message update even if `msg` doesn't change diff --git a/.changeset/short-drinks-itch.md b/.changeset/short-drinks-itch.md new file mode 100644 index 000000000000..ee57330ffc86 --- /dev/null +++ b/.changeset/short-drinks-itch.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/message-parser': patch +'@rocket.chat/peggy-loader': patch +--- + +Improved the performance of the message parser diff --git a/.changeset/six-beers-fry.md b/.changeset/six-beers-fry.md deleted file mode 100644 index 48409c2f8de5..000000000000 --- a/.changeset/six-beers-fry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -New button added to validate Matrix Federation configuration. A new field inside admin settings will reflect the configuration status being either 'Valid' or 'Invalid'. diff --git a/.changeset/smart-mice-attack.md b/.changeset/smart-mice-attack.md deleted file mode 100644 index 3ca47060ce5c..000000000000 --- a/.changeset/smart-mice-attack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed an issue where teams were being created with no room associated with it. diff --git a/.changeset/spicy-kings-think.md b/.changeset/spicy-kings-think.md deleted file mode 100644 index 9e8f3648b28c..000000000000 --- a/.changeset/spicy-kings-think.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixes multiple problems with the `processRoomAbandonment` hook. This hook is in charge of calculating the time a room has been abandoned (this means, the time that elapsed since a room was last answered by an agent until it was closed). However, when business hours were enabled and the user didn't open on one day, if an abandoned room happened to be abandoned _over_ the day there was no business hour configuration, then the process will error out. -Additionally, the values the code was calculating were not right. When business hours are enabled, this code should only count the abandonment time _while a business hour was open_. When rooms were left abandoned for days or weeks, this will also throw an error or output an invalid count. diff --git a/.changeset/strong-swans-double.md b/.changeset/strong-swans-double.md deleted file mode 100644 index db521aeeef0f..000000000000 --- a/.changeset/strong-swans-double.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@rocket.chat/uikit-playground': minor -'@rocket.chat/meteor': minor ---- - -Upgrades fuselage-toastbar version in order to add pause on hover functionality diff --git a/.changeset/strong-terms-love.md b/.changeset/strong-terms-love.md deleted file mode 100644 index 2535a466eb8e..000000000000 --- a/.changeset/strong-terms-love.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/model-typings": patch ---- - -Fixed issue with livechat analytics in a given date range considering conversation data from the following day diff --git a/.changeset/stupid-fishes-relate.md b/.changeset/stupid-fishes-relate.md deleted file mode 100644 index 82bfaa1cfd28..000000000000 --- a/.changeset/stupid-fishes-relate.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@rocket.chat/core-typings': minor -'@rocket.chat/i18n': minor -'@rocket.chat/meteor': minor ---- - -Added a new setting to enable/disable file encryption in an end to end encrypted room. diff --git a/.changeset/swift-maps-tickle.md b/.changeset/swift-maps-tickle.md deleted file mode 100644 index 076ead1cea4c..000000000000 --- a/.changeset/swift-maps-tickle.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@rocket.chat/core-services': minor -'@rocket.chat/model-typings': minor -'@rocket.chat/core-typings': minor -'@rocket.chat/rest-typings': minor -'@rocket.chat/meteor': minor ---- - -Added `sidepanel` field to `teams.create` and `rooms.saveRoomSettings` endpoints diff --git a/.changeset/tame-mayflies-press.md b/.changeset/tame-mayflies-press.md new file mode 100644 index 000000000000..e470306cbc25 --- /dev/null +++ b/.changeset/tame-mayflies-press.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": minor +--- + +Implemented sending email via apps diff --git a/.changeset/ten-bulldogs-clap.md b/.changeset/ten-bulldogs-clap.md deleted file mode 100644 index 15f88bb6bd97..000000000000 --- a/.changeset/ten-bulldogs-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -fixed an issue with the "follow message" button not changing state after click diff --git a/.changeset/thirty-dryers-help.md b/.changeset/thirty-dryers-help.md deleted file mode 100644 index 6d9a7dc205ae..000000000000 --- a/.changeset/thirty-dryers-help.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/livechat": patch ---- - -Fixed issue where `after-registration-triggers` would show up in a page when the user was not yet registered diff --git a/.changeset/tiny-geckos-kiss.md b/.changeset/tiny-geckos-kiss.md new file mode 100644 index 000000000000..d38150970310 --- /dev/null +++ b/.changeset/tiny-geckos-kiss.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Added a new setting which allows workspace admins to disable email two factor authentication for SSO (OAuth) users. If enabled, SSO users won't be asked for email two factor authentication. diff --git a/.changeset/twelve-windows-train.md b/.changeset/twelve-windows-train.md deleted file mode 100644 index 4c6ef548e650..000000000000 --- a/.changeset/twelve-windows-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixed: Custom fields in extraData now correctly added to extraRoomInfo by livechat.beforeRoom callback during livechat room creation. diff --git a/.changeset/two-bikes-crash.md b/.changeset/two-bikes-crash.md deleted file mode 100644 index a120435e4a48..000000000000 --- a/.changeset/two-bikes-crash.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixed an issue related to setting Accounts_ForgetUserSessionOnWindowClose, this setting was not working as expected. - -The new meteor 2.16 release introduced a new option to configure the Accounts package and choose between the local storage or session storage. They also changed how Meteor.\_localstorage works internally. Due to these changes in Meteor, our setting to use session storage wasn't working as expected. This PR fixes this issue and configures the Accounts package according to the workspace settings. diff --git a/.changeset/violet-radios-begin.md b/.changeset/violet-radios-begin.md deleted file mode 100644 index d11f23b47478..000000000000 --- a/.changeset/violet-radios-begin.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@rocket.chat/core-typings': minor -'@rocket.chat/i18n': minor -'@rocket.chat/meteor': minor ---- - -Fixed a bug related to uploading end to end encrypted file. - -E2EE files and uploads are uploaded as files of mime type `application/octet-stream` as we can't reveal the mime type of actual content since it is encrypted and has to be kept confidential. - -The server resolves the mime type of encrypted file as `application/octet-stream` but it wasn't playing nicely with existing settings related to whitelisted and blacklisted media types. - -E2EE files upload was getting blocked if `application/octet-stream` is not a whitelisted media type. - -Now this PR solves this issue by always accepting E2EE uploads even if `application/octet-stream` is not whitelisted but it will block the upload if `application/octet-stream` is black listed. diff --git a/.changeset/wet-hats-walk.md b/.changeset/wet-hats-walk.md new file mode 100644 index 000000000000..4c3565e75523 --- /dev/null +++ b/.changeset/wet-hats-walk.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed issue that caused an infinite loading state when uploading a private app to Rocket.Chat diff --git a/.changeset/wise-avocados-taste.md b/.changeset/wise-avocados-taste.md new file mode 100644 index 000000000000..c4c9bce010b8 --- /dev/null +++ b/.changeset/wise-avocados-taste.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where multi-step modals were closing unexpectedly diff --git a/.github/actions/meteor-build/action.yml b/.github/actions/meteor-build/action.yml index 525595146700..551a57d28a7c 100644 --- a/.github/actions/meteor-build/action.yml +++ b/.github/actions/meteor-build/action.yml @@ -133,3 +133,4 @@ runs: name: build path: /tmp/Rocket.Chat.tar.gz overwrite: true + include-hidden-files: true diff --git a/.github/workflows/ci-deploy-gh-pages-preview.yml b/.github/workflows/ci-deploy-gh-pages-preview.yml new file mode 100644 index 000000000000..17f247ddad94 --- /dev/null +++ b/.github/workflows/ci-deploy-gh-pages-preview.yml @@ -0,0 +1,41 @@ +# .github/workflows/ci-preview.yml +name: Deploy PR previews +concurrency: preview-${{ github.ref }} +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed + +jobs: + deploy-preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: rharkor/caching-for-turbo@v1.5 + if: github.event.action != 'closed' + + - name: Setup NodeJS + uses: ./.github/actions/setup-node + if: github.event.action != 'closed' + with: + node-version: 14.21.3 + cache-modules: true + install: true + + - name: Build + if: github.event.action != 'closed' + run: | + yarn turbo run build-preview + yarn turbo run .:build-preview-move + npx indexifier .preview --html --extensions .html > .preview/index.html + + - uses: rossjrw/pr-preview-action@v1 + with: + source-dir: .preview + preview-branch: gh-pages + umbrella-dir: pr-preview + action: auto diff --git a/.github/workflows/ci-deploy-gh-pages.yml b/.github/workflows/ci-deploy-gh-pages.yml new file mode 100644 index 000000000000..b381e05ae5d8 --- /dev/null +++ b/.github/workflows/ci-deploy-gh-pages.yml @@ -0,0 +1,38 @@ +# .github/workflows/ci-preview-deploy.yml +name: Deploy GitHub Pages +concurrency: preview-deploy-${{ github.ref }} +on: + push: + branches: + - main + - master + - develop +jobs: + deploy-preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: rharkor/caching-for-turbo@v1.5 + + - name: Setup NodeJS + uses: ./.github/actions/setup-node + with: + node-version: 14.21.3 + cache-modules: true + install: true + + - name: Build + run: | + yarn turbo run build-preview + yarn turbo run .:build-preview-move + npx indexifier .preview --html --extensions .html > .preview/index.html + mv .preview ${{ github.ref_name }} + mkdir .preview + mv ${{ github.ref_name }} .preview + + - name: Deploy + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: .preview + keep_files: true diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index e6c02b7b6417..a80a40419e9f 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -291,8 +291,9 @@ jobs: if: inputs.type == 'ui' && always() uses: actions/upload-artifact@v4 with: - name: playwright-test-trace-${{ matrix.mongodb-version }}-${{ matrix.shard }} + name: playwright-test-trace-${{ inputs.release }}-${{ matrix.mongodb-version }}-${{ matrix.shard }} path: ./apps/meteor/tests/e2e/.playwright* + include-hidden-files: true - name: Show server logs if E2E test failed if: failure() @@ -326,6 +327,7 @@ jobs: with: name: e2e-api-ee-coverage-${{ matrix.mongodb-version }}-${{ matrix.shard }} path: /tmp/coverage + include-hidden-files: true - name: Store e2e-ee-coverage if: inputs.type == 'ui' && inputs.release == 'ee' @@ -333,3 +335,4 @@ jobs: with: name: e2e-ee-coverage-${{ matrix.mongodb-version }}-${{ matrix.shard }} path: ./apps/meteor/coverage* + include-hidden-files: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 514dd6d1c518..40260f71d21f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -824,7 +824,7 @@ jobs: with: token: ${{ secrets.DISTRIBUTION_TOKEN }} event-type: new_release - repository: RocketChat/Release.Distributions + repository: RocketChat/public-releases client-payload: '{"tag": "${{ github.ref_name }}"}' docs-update: diff --git a/.gitignore b/.gitignore index 4e6e4bb29da9..dbad2c29a22c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,8 @@ yarn-error.log* .history .envrc +.preview + *.sublime-workspace **/.vim/ diff --git a/.yarn/patches/moleculer-npm-0.14.34-440e26767d.patch b/.yarn/patches/moleculer-npm-0.14.34-440e26767d.patch new file mode 100644 index 000000000000..005ab83cfb48 --- /dev/null +++ b/.yarn/patches/moleculer-npm-0.14.34-440e26767d.patch @@ -0,0 +1,13 @@ +diff --git a/index.d.ts b/index.d.ts +index f64eda7889619f2a1e7cbd5bb5e3533fc8aaa182..3d815a3b7aaf426235aa92422129079aa5826908 100644 +--- a/index.d.ts ++++ b/index.d.ts +@@ -725,7 +725,7 @@ declare namespace Moleculer { + this: T + ) => void | Promise; + +- interface ServiceSchema { ++ interface ServiceSchema> { + name: string; + version?: string | number; + settings?: S; diff --git a/.yarn/patches/react-i18next-npm-15.0.1-0812bb73aa.patch b/.yarn/patches/react-i18next-npm-15.0.1-0812bb73aa.patch new file mode 100644 index 000000000000..cf5a292c0253 --- /dev/null +++ b/.yarn/patches/react-i18next-npm-15.0.1-0812bb73aa.patch @@ -0,0 +1,99 @@ +diff --git a/dist/amd/react-i18next.js b/dist/amd/react-i18next.js +index 115ef3cc362e5ce38e875f9b35dfd1fe687cfd6c..2ba1b4d54295eeff49c8c650f214d7875d9219a5 100644 +--- a/dist/amd/react-i18next.js ++++ b/dist/amd/react-i18next.js +@@ -495,7 +495,7 @@ define(['exports', 'react'], (function (exports, react) { 'use strict'; + } + addUsedNamespaces(namespaces) { + namespaces.forEach(ns => { +- this.usedNamespaces[ns] ??= true; ++ this.usedNamespaces[ns] = this.usedNamespaces[ns] ?? true; + }); + } + getUsedNamespaces() { +diff --git a/dist/amd/react-i18next.min.js b/dist/amd/react-i18next.min.js +index c54c9114869feb14df7855be24237f85d64fe7e4..0cc2b09d6db9bfc6f94dffff4b9e0f3fd9108510 100644 +--- a/dist/amd/react-i18next.min.js ++++ b/dist/amd/react-i18next.min.js +@@ -1 +1 @@ +-define(["exports","react"],(function(e,n){"use strict";function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var s=t({area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),a=/\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function i(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(s[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var i=e.indexOf("--\x3e");return{type:"comment",comment:-1!==i?e.slice(4,i):""}}for(var r=new RegExp(a),o=null;null!==(o=r.exec(e));)if(o[0].trim())if(o[1]){var l=o[1].trim(),c=[l,""];l.indexOf("=")>-1&&(c=l.split("=")),n.attrs[c[0]]=c[1],r.lastIndex--}else o[2]&&(n.attrs[o[2]]=o[3].trim().substring(1,o[3].length-1));return n}var r=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,o=/^\s*$/,l=Object.create(null);var c=function(e,n){n||(n={}),n.components||(n.components=l);var t,s=[],a=[],c=-1,u=!1;if(0!==e.indexOf("<")){var p=e.indexOf("<");s.push({type:"text",content:-1===p?e:e.substring(0,p)})}return e.replace(r,(function(r,l){if(u){if(r!=="")return;u=!1}var p,d="/"!==r.charAt(1),f=r.startsWith("\x3c!--"),h=l+r.length,m=e.charAt(h);if(f){var g=i(r);return c<0?(s.push(g),s):((p=a[c]).children.push(g),s)}if(d&&(c++,"tag"===(t=i(r)).type&&n.components[t.name]&&(t.type="component",u=!0),t.voidElement||u||!m||"<"===m||t.children.push({type:"text",content:e.slice(h,e.indexOf("<",h))}),0===c&&s.push(t),(p=a[c-1])&&p.children.push(t),a[c]=t),(!d||t.voidElement)&&(c>-1&&(t.voidElement||t.name===r.slice(2,-1))&&(c--,t=-1===c?s:a[c]),!u&&"<"!==m&&m)){p=-1===c?s:a[c].children;var y=e.indexOf("<",h),v=e.slice(h,-1===y?void 0:y);o.test(v)&&(v=" "),(y>-1&&c+p.length>=0||" "!==v)&&p.push({type:"text",content:v})}})),s};const u=function(){if(console?.warn){for(var e=arguments.length,n=new Array(e),t=0;t()=>{if(e.isInitialized)n();else{const t=()=>{setTimeout((()=>{e.off("initialized",t)}),0),n()};e.on("initialized",t)}},h=(e,n,t)=>{e.loadNamespaces(n,f(e,t))},m=(e,n,t,s)=>{y(t)&&(t=[t]),t.forEach((n=>{e.options.ns.indexOf(n)<0&&e.options.ns.push(n)})),e.loadLanguages(n,f(e,s))},g=e=>e.displayName||e.name||(y(e)&&e.length>0?e:"Unknown"),y=e=>"string"==typeof e,v=e=>"object"==typeof e&&null!==e,x=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,b={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},E=e=>b[e];let O={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:e=>e.replace(x,E)};const N=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};O={...O,...e}},$=()=>O;let w;const I=e=>{w=e},k=()=>w,S=(e,n)=>{if(!e)return!1;const t=e.props?.children??e.children;return n?t.length>0:!!t},j=e=>{if(!e)return[];const n=e.props?.children??e.children;return e.props?.i18nIsDynamicList?R(n):n},R=e=>Array.isArray(e)?e:[e],C=(e,t)=>{if(!e)return"";let s="";const a=R(e),i=t?.transSupportBasicHtmlNodes?t.transKeepBasicHtmlNodesFor??[]:[];return a.forEach(((e,a)=>{if(y(e))s+=`${e}`;else if(n.isValidElement(e)){const{props:n,type:r}=e,o=Object.keys(n).length,l=i.indexOf(r)>-1,c=n.children;if(c||!l||o)if(!c&&(!l||o)||n.i18nIsDynamicList)s+=`<${a}>`;else if(l&&1===o&&y(c))s+=`<${r}>${c}`;else{const e=C(c,t);s+=`<${a}>${e}`}else s+=`<${r}/>`}else if(null===e)u("Trans: the passed in value is invalid - seems you passed in a null child.");else if(v(e)){const{format:n,...t}=e,a=Object.keys(t);if(1===a.length){const e=n?`${a[0]}, ${n}`:a[0];s+=`{{${e}}}`}else u("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",e)}else u("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",e)})),s},T=(e,t,s,a,i,r)=>{if(""===t)return[];const o=a.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(o.map((e=>`<${e}`)).join("|")).test(t);if(!e&&!l&&!r)return[t];const u={},p=e=>{R(e).forEach((e=>{y(e)||(S(e)?p(j(e)):v(e)&&!n.isValidElement(e)&&Object.assign(u,e))}))};p(e);const d=c(`<0>${t}`),f={...u,...i},h=(e,t,s)=>{const a=j(e),i=g(a,t.children,s);return(e=>Array.isArray(e)&&e.every(n.isValidElement))(a)&&0===i.length||e.props?.i18nIsDynamicList?a:i},m=(e,t,s,a,i)=>{e.dummy?(e.children=t,s.push(n.cloneElement(e,{key:a},i?void 0:t))):s.push(...n.Children.map([e],(e=>{const s={...e.props};return delete s.i18nIsDynamicList,n.createElement(e.type,{...s,key:a,ref:e.ref},i?null:t)})))},g=(t,i,c)=>{const u=R(t);return R(i).reduce(((t,i,p)=>{const d=i.children?.[0]?.content&&s.services.interpolator.interpolate(i.children[0].content,f,s.language);if("tag"===i.type){let r=u[parseInt(i.name,10)];1!==c.length||r||(r=c[0][i.name]),r||(r={});const x=0!==Object.keys(i.attrs).length?((e,n)=>{const t={...n};return t.props=Object.assign(e.props,n.props),t})({props:i.attrs},r):r,b=n.isValidElement(x),E=b&&S(i,!0)&&!i.voidElement,O=l&&v(x)&&x.dummy&&!b,N=v(e)&&Object.hasOwnProperty.call(e,i.name);if(y(x)){const e=s.services.interpolator.interpolate(x,f,s.language);t.push(e)}else if(S(x)||E){const e=h(x,i,c);m(x,e,t,p)}else if(O){const e=g(u,i.children,c);m(x,e,t,p)}else if(Number.isNaN(parseFloat(i.name)))if(N){const e=h(x,i,c);m(x,e,t,p,i.voidElement)}else if(a.transSupportBasicHtmlNodes&&o.indexOf(i.name)>-1)if(i.voidElement)t.push(n.createElement(i.name,{key:`${i.name}-${p}`}));else{const e=g(u,i.children,c);t.push(n.createElement(i.name,{key:`${i.name}-${p}`},e))}else if(i.voidElement)t.push(`<${i.name} />`);else{const e=g(u,i.children,c);t.push(`<${i.name}>${e}`)}else if(v(x)&&!b){const e=i.children[0]?d:null;e&&t.push(e)}else m(x,d,t,p,1!==i.children.length||!d)}else if("text"===i.type){const e=a.transWrapTextNodes,o=r?a.unescape(s.services.interpolator.interpolate(i.content,f,s.language)):s.services.interpolator.interpolate(i.content,f,s.language);e?t.push(n.createElement(e,{key:`${i.name}-${p}`},o)):t.push(o)}return t}),[])},x=g([{dummy:!0,children:e||[]}],d,R(e||[]));return j(x[0])};function P(e){let{children:t,count:s,parent:a,i18nKey:i,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:f,t:h,shouldUnescape:m,...g}=e;const v=f||k();if(!v)return d("You will need to pass in an i18next instance by using i18nextReactModule"),t;const x=h||v.t.bind(v)||(e=>e),b={...$(),...v.options?.react};let E=p||x.ns||v.options?.defaultNS;E=y(E)?[E]:E||["translation"];const O=C(t,b),N=c||O||b.transEmptyNodeValue||i,{hashTransKey:w}=b,I=i||(w?w(O||N):O||N);v.options?.interpolation?.defaultVariables&&(l=l&&Object.keys(l).length>0?{...l,...v.options.interpolation.defaultVariables}:{...v.options.interpolation.defaultVariables});const S=l||void 0!==s||!t?o.interpolation:{interpolation:{...o.interpolation,prefix:"#$?",suffix:"?$#"}},j={...o,context:r||o.context,count:s,...l,...S,defaultValue:N,ns:E},R=I?x(I,j):N;u&&Object.keys(u).forEach((e=>{const t=u[e];"function"==typeof t.type||!t.props||!t.props.children||R.indexOf(`${e}/>`)<0&&R.indexOf(`${e} />`)<0||(u[e]=n.createElement((function(){return n.createElement(n.Fragment,null,t)})))}));const P=T(u||t,R,v,b,j,m),L=a??b.defaultTransParent;return L?n.createElement(L,g,P):P}const L={type:"3rdParty",init(e){N(e.options.react),I(e)}},A=n.createContext();class V{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach((e=>{this.usedNamespaces[e]??=!0}))}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}const z=e=>async n=>({...await(e.getInitialProps?.(n))??{},...F()}),F=()=>{const e=k(),n=e.reportNamespaces?.getUsedNamespaces()??[],t={},s={};return e.languages.forEach((t=>{s[t]={},n.forEach((n=>{s[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=s,t.initialLanguage=e.language,t};const U=(e,n,t,s)=>e.getFixedT(n,t,s),B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{i18n:s}=t,{i18n:a,defaultNS:i}=n.useContext(A)||{},r=s||a||k();if(r&&!r.reportNamespaces&&(r.reportNamespaces=new V),!r){d("You will need to pass in an i18next instance by using initReactI18next");const e=(e,n)=>y(n)?n:v(n)&&y(n.defaultValue)?n.defaultValue:Array.isArray(e)?e[e.length-1]:e,n=[e,{},!1];return n.t=e,n.i18n={},n.ready=!1,n}r.options.react?.wait&&d("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");const o={...$(),...r.options.react,...t},{useSuspense:l,keyPrefix:c}=o;let u=e||i||r.options?.defaultNS;u=y(u)?[u]:u||["translation"],r.reportNamespaces.addUsedNamespaces?.(u);const p=(r.isInitialized||r.initializedStoreOnce)&&u.every((e=>function(e,n){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n.languages&&n.languages.length?n.hasLoadedNamespace(e,{lng:t.lng,precheck:(n,s)=>{if(t.bindI18n?.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!s(n.isLanguageChangingTo,e))return!1}}):(d("i18n.languages were undefined or empty",n.languages),!0)}(e,r,o))),f=((e,t,s,a)=>n.useCallback(U(e,t,s,a),[e,t,s,a]))(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),g=()=>f,x=()=>U(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),[b,E]=n.useState(g);let O=u.join();t.lng&&(O=`${t.lng}${O}`);const N=((e,t)=>{const s=n.useRef();return n.useEffect((()=>{s.current=e}),[e,t]),s.current})(O),w=n.useRef(!0);n.useEffect((()=>{const{bindI18n:e,bindI18nStore:n}=o;w.current=!0,p||l||(t.lng?m(r,t.lng,u,(()=>{w.current&&E(x)})):h(r,u,(()=>{w.current&&E(x)}))),p&&N&&N!==O&&w.current&&E(x);const s=()=>{w.current&&E(x)};return e&&r?.on(e,s),n&&r?.store.on(n,s),()=>{w.current=!1,r&&e?.split(" ").forEach((e=>r.off(e,s))),n&&r&&n.split(" ").forEach((e=>r.store.off(e,s)))}}),[r,O]),n.useEffect((()=>{w.current&&p&&E(g)}),[r,c,p]);const I=[b,r,p];if(I.t=b,I.i18n=r,I.ready=p,p)return I;if(!p&&!l)return I;throw new Promise((e=>{t.lng?m(r,t.lng,u,(()=>e())):h(r,u,(()=>e()))}))};const D=function(e,t){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{i18n:a}=s,{i18n:i}=n.useContext(A)||{},r=a||i||k();r.options?.isClone||(e&&!r.initializedStoreOnce&&(r.services.resourceStore.data=e,r.options.ns=Object.values(e).reduce(((e,n)=>(Object.keys(n).forEach((n=>{e.indexOf(n)<0&&e.push(n)})),e)),r.options.ns),r.initializedStoreOnce=!0,r.isInitialized=!0),t&&!r.initializedLanguageOnce&&(r.changeLanguage(t),r.initializedLanguageOnce=!0))};e.I18nContext=A,e.I18nextProvider=function(e){let{i18n:t,defaultNS:s,children:a}=e;const i=n.useMemo((()=>({i18n:t,defaultNS:s})),[t,s]);return n.createElement(A.Provider,{value:i},a)},e.Trans=function(e){let{children:t,count:s,parent:a,i18nKey:i,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:d,t:f,shouldUnescape:h,...m}=e;const{i18n:g,defaultNS:y}=n.useContext(A)||{},v=d||g||k(),x=f||v?.t.bind(v);return P({children:t,count:s,parent:a,i18nKey:i,context:r,tOptions:o,values:l,defaults:c,components:u,ns:p||x?.ns||y||v?.options?.defaultNS,i18n:v,t:f,shouldUnescape:h,...m})},e.TransWithoutContext=P,e.Translation=e=>{let{ns:n,children:t,...s}=e;const[a,i,r]=B(n,s);return t(a,{i18n:i,lng:i.language},r)},e.composeInitialProps=z,e.date=()=>"",e.getDefaults=$,e.getI18n=k,e.getInitialProps=F,e.initReactI18next=L,e.number=()=>"",e.plural=()=>"",e.select=()=>"",e.selectOrdinal=()=>"",e.setDefaults=N,e.setI18n=I,e.time=()=>"",e.useSSR=D,e.useTranslation=B,e.withSSR=()=>function(e){function t(t){let{initialI18nStore:s,initialLanguage:a,...i}=t;return D(s,a),n.createElement(e,{...i})}return t.getInitialProps=z(e),t.displayName=`withI18nextSSR(${g(e)})`,t.WrappedComponent=e,t},e.withTranslation=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(s){function a(a){let{forwardedRef:i,...r}=a;const[o,l,c]=B(e,{...r,keyPrefix:t.keyPrefix}),u={...r,t:o,i18n:l,tReady:c};return t.withRef&&i?u.ref=i:!t.withRef&&i&&(u.forwardedRef=i),n.createElement(s,u)}a.displayName=`withI18nextTranslation(${g(s)})`,a.WrappedComponent=s;return t.withRef?n.forwardRef(((e,t)=>n.createElement(a,Object.assign({},e,{forwardedRef:t})))):a}}})); ++define(["exports","react"],(function(e,n){"use strict";function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var s=t({area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),a=/\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function i(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(s[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var i=e.indexOf("--\x3e");return{type:"comment",comment:-1!==i?e.slice(4,i):""}}for(var r=new RegExp(a),o=null;null!==(o=r.exec(e));)if(o[0].trim())if(o[1]){var l=o[1].trim(),c=[l,""];l.indexOf("=")>-1&&(c=l.split("=")),n.attrs[c[0]]=c[1],r.lastIndex--}else o[2]&&(n.attrs[o[2]]=o[3].trim().substring(1,o[3].length-1));return n}var r=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,o=/^\s*$/,l=Object.create(null);var c=function(e,n){n||(n={}),n.components||(n.components=l);var t,s=[],a=[],c=-1,u=!1;if(0!==e.indexOf("<")){var p=e.indexOf("<");s.push({type:"text",content:-1===p?e:e.substring(0,p)})}return e.replace(r,(function(r,l){if(u){if(r!=="")return;u=!1}var p,d="/"!==r.charAt(1),f=r.startsWith("\x3c!--"),h=l+r.length,m=e.charAt(h);if(f){var g=i(r);return c<0?(s.push(g),s):((p=a[c]).children.push(g),s)}if(d&&(c++,"tag"===(t=i(r)).type&&n.components[t.name]&&(t.type="component",u=!0),t.voidElement||u||!m||"<"===m||t.children.push({type:"text",content:e.slice(h,e.indexOf("<",h))}),0===c&&s.push(t),(p=a[c-1])&&p.children.push(t),a[c]=t),(!d||t.voidElement)&&(c>-1&&(t.voidElement||t.name===r.slice(2,-1))&&(c--,t=-1===c?s:a[c]),!u&&"<"!==m&&m)){p=-1===c?s:a[c].children;var y=e.indexOf("<",h),v=e.slice(h,-1===y?void 0:y);o.test(v)&&(v=" "),(y>-1&&c+p.length>=0||" "!==v)&&p.push({type:"text",content:v})}})),s};const u=function(){if(console?.warn){for(var e=arguments.length,n=new Array(e),t=0;t()=>{if(e.isInitialized)n();else{const t=()=>{setTimeout((()=>{e.off("initialized",t)}),0),n()};e.on("initialized",t)}},h=(e,n,t)=>{e.loadNamespaces(n,f(e,t))},m=(e,n,t,s)=>{y(t)&&(t=[t]),t.forEach((n=>{e.options.ns.indexOf(n)<0&&e.options.ns.push(n)})),e.loadLanguages(n,f(e,s))},g=e=>e.displayName||e.name||(y(e)&&e.length>0?e:"Unknown"),y=e=>"string"==typeof e,v=e=>"object"==typeof e&&null!==e,x=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,b={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},E=e=>b[e];let O={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:e=>e.replace(x,E)};const N=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};O={...O,...e}},$=()=>O;let w;const I=e=>{w=e},k=()=>w,S=(e,n)=>{if(!e)return!1;const t=e.props?.children??e.children;return n?t.length>0:!!t},j=e=>{if(!e)return[];const n=e.props?.children??e.children;return e.props?.i18nIsDynamicList?R(n):n},R=e=>Array.isArray(e)?e:[e],C=(e,t)=>{if(!e)return"";let s="";const a=R(e),i=t?.transSupportBasicHtmlNodes?t.transKeepBasicHtmlNodesFor??[]:[];return a.forEach(((e,a)=>{if(y(e))s+=`${e}`;else if(n.isValidElement(e)){const{props:n,type:r}=e,o=Object.keys(n).length,l=i.indexOf(r)>-1,c=n.children;if(c||!l||o)if(!c&&(!l||o)||n.i18nIsDynamicList)s+=`<${a}>`;else if(l&&1===o&&y(c))s+=`<${r}>${c}`;else{const e=C(c,t);s+=`<${a}>${e}`}else s+=`<${r}/>`}else if(null===e)u("Trans: the passed in value is invalid - seems you passed in a null child.");else if(v(e)){const{format:n,...t}=e,a=Object.keys(t);if(1===a.length){const e=n?`${a[0]}, ${n}`:a[0];s+=`{{${e}}}`}else u("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",e)}else u("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",e)})),s},T=(e,t,s,a,i,r)=>{if(""===t)return[];const o=a.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(o.map((e=>`<${e}`)).join("|")).test(t);if(!e&&!l&&!r)return[t];const u={},p=e=>{R(e).forEach((e=>{y(e)||(S(e)?p(j(e)):v(e)&&!n.isValidElement(e)&&Object.assign(u,e))}))};p(e);const d=c(`<0>${t}`),f={...u,...i},h=(e,t,s)=>{const a=j(e),i=g(a,t.children,s);return(e=>Array.isArray(e)&&e.every(n.isValidElement))(a)&&0===i.length||e.props?.i18nIsDynamicList?a:i},m=(e,t,s,a,i)=>{e.dummy?(e.children=t,s.push(n.cloneElement(e,{key:a},i?void 0:t))):s.push(...n.Children.map([e],(e=>{const s={...e.props};return delete s.i18nIsDynamicList,n.createElement(e.type,{...s,key:a,ref:e.ref},i?null:t)})))},g=(t,i,c)=>{const u=R(t);return R(i).reduce(((t,i,p)=>{const d=i.children?.[0]?.content&&s.services.interpolator.interpolate(i.children[0].content,f,s.language);if("tag"===i.type){let r=u[parseInt(i.name,10)];1!==c.length||r||(r=c[0][i.name]),r||(r={});const x=0!==Object.keys(i.attrs).length?((e,n)=>{const t={...n};return t.props=Object.assign(e.props,n.props),t})({props:i.attrs},r):r,b=n.isValidElement(x),E=b&&S(i,!0)&&!i.voidElement,O=l&&v(x)&&x.dummy&&!b,N=v(e)&&Object.hasOwnProperty.call(e,i.name);if(y(x)){const e=s.services.interpolator.interpolate(x,f,s.language);t.push(e)}else if(S(x)||E){const e=h(x,i,c);m(x,e,t,p)}else if(O){const e=g(u,i.children,c);m(x,e,t,p)}else if(Number.isNaN(parseFloat(i.name)))if(N){const e=h(x,i,c);m(x,e,t,p,i.voidElement)}else if(a.transSupportBasicHtmlNodes&&o.indexOf(i.name)>-1)if(i.voidElement)t.push(n.createElement(i.name,{key:`${i.name}-${p}`}));else{const e=g(u,i.children,c);t.push(n.createElement(i.name,{key:`${i.name}-${p}`},e))}else if(i.voidElement)t.push(`<${i.name} />`);else{const e=g(u,i.children,c);t.push(`<${i.name}>${e}`)}else if(v(x)&&!b){const e=i.children[0]?d:null;e&&t.push(e)}else m(x,d,t,p,1!==i.children.length||!d)}else if("text"===i.type){const e=a.transWrapTextNodes,o=r?a.unescape(s.services.interpolator.interpolate(i.content,f,s.language)):s.services.interpolator.interpolate(i.content,f,s.language);e?t.push(n.createElement(e,{key:`${i.name}-${p}`},o)):t.push(o)}return t}),[])},x=g([{dummy:!0,children:e||[]}],d,R(e||[]));return j(x[0])};function P(e){let{children:t,count:s,parent:a,i18nKey:i,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:f,t:h,shouldUnescape:m,...g}=e;const v=f||k();if(!v)return d("You will need to pass in an i18next instance by using i18nextReactModule"),t;const x=h||v.t.bind(v)||(e=>e),b={...$(),...v.options?.react};let E=p||x.ns||v.options?.defaultNS;E=y(E)?[E]:E||["translation"];const O=C(t,b),N=c||O||b.transEmptyNodeValue||i,{hashTransKey:w}=b,I=i||(w?w(O||N):O||N);v.options?.interpolation?.defaultVariables&&(l=l&&Object.keys(l).length>0?{...l,...v.options.interpolation.defaultVariables}:{...v.options.interpolation.defaultVariables});const S=l||void 0!==s||!t?o.interpolation:{interpolation:{...o.interpolation,prefix:"#$?",suffix:"?$#"}},j={...o,context:r||o.context,count:s,...l,...S,defaultValue:N,ns:E},R=I?x(I,j):N;u&&Object.keys(u).forEach((e=>{const t=u[e];"function"==typeof t.type||!t.props||!t.props.children||R.indexOf(`${e}/>`)<0&&R.indexOf(`${e} />`)<0||(u[e]=n.createElement((function(){return n.createElement(n.Fragment,null,t)})))}));const P=T(u||t,R,v,b,j,m),L=a??b.defaultTransParent;return L?n.createElement(L,g,P):P}const L={type:"3rdParty",init(e){N(e.options.react),I(e)}},A=n.createContext();class V{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach((e=>{this.usedNamespaces[e]=this.usedNamespaces[e]??!0}))}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}const z=e=>async n=>({...await(e.getInitialProps?.(n))??{},...F()}),F=()=>{const e=k(),n=e.reportNamespaces?.getUsedNamespaces()??[],t={},s={};return e.languages.forEach((t=>{s[t]={},n.forEach((n=>{s[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=s,t.initialLanguage=e.language,t};const U=(e,n,t,s)=>e.getFixedT(n,t,s),B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{i18n:s}=t,{i18n:a,defaultNS:i}=n.useContext(A)||{},r=s||a||k();if(r&&!r.reportNamespaces&&(r.reportNamespaces=new V),!r){d("You will need to pass in an i18next instance by using initReactI18next");const e=(e,n)=>y(n)?n:v(n)&&y(n.defaultValue)?n.defaultValue:Array.isArray(e)?e[e.length-1]:e,n=[e,{},!1];return n.t=e,n.i18n={},n.ready=!1,n}r.options.react?.wait&&d("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");const o={...$(),...r.options.react,...t},{useSuspense:l,keyPrefix:c}=o;let u=e||i||r.options?.defaultNS;u=y(u)?[u]:u||["translation"],r.reportNamespaces.addUsedNamespaces?.(u);const p=(r.isInitialized||r.initializedStoreOnce)&&u.every((e=>function(e,n){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n.languages&&n.languages.length?n.hasLoadedNamespace(e,{lng:t.lng,precheck:(n,s)=>{if(t.bindI18n?.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!s(n.isLanguageChangingTo,e))return!1}}):(d("i18n.languages were undefined or empty",n.languages),!0)}(e,r,o))),f=((e,t,s,a)=>n.useCallback(U(e,t,s,a),[e,t,s,a]))(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),g=()=>f,x=()=>U(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),[b,E]=n.useState(g);let O=u.join();t.lng&&(O=`${t.lng}${O}`);const N=((e,t)=>{const s=n.useRef();return n.useEffect((()=>{s.current=e}),[e,t]),s.current})(O),w=n.useRef(!0);n.useEffect((()=>{const{bindI18n:e,bindI18nStore:n}=o;w.current=!0,p||l||(t.lng?m(r,t.lng,u,(()=>{w.current&&E(x)})):h(r,u,(()=>{w.current&&E(x)}))),p&&N&&N!==O&&w.current&&E(x);const s=()=>{w.current&&E(x)};return e&&r?.on(e,s),n&&r?.store.on(n,s),()=>{w.current=!1,r&&e?.split(" ").forEach((e=>r.off(e,s))),n&&r&&n.split(" ").forEach((e=>r.store.off(e,s)))}}),[r,O]),n.useEffect((()=>{w.current&&p&&E(g)}),[r,c,p]);const I=[b,r,p];if(I.t=b,I.i18n=r,I.ready=p,p)return I;if(!p&&!l)return I;throw new Promise((e=>{t.lng?m(r,t.lng,u,(()=>e())):h(r,u,(()=>e()))}))};const D=function(e,t){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{i18n:a}=s,{i18n:i}=n.useContext(A)||{},r=a||i||k();r.options?.isClone||(e&&!r.initializedStoreOnce&&(r.services.resourceStore.data=e,r.options.ns=Object.values(e).reduce(((e,n)=>(Object.keys(n).forEach((n=>{e.indexOf(n)<0&&e.push(n)})),e)),r.options.ns),r.initializedStoreOnce=!0,r.isInitialized=!0),t&&!r.initializedLanguageOnce&&(r.changeLanguage(t),r.initializedLanguageOnce=!0))};e.I18nContext=A,e.I18nextProvider=function(e){let{i18n:t,defaultNS:s,children:a}=e;const i=n.useMemo((()=>({i18n:t,defaultNS:s})),[t,s]);return n.createElement(A.Provider,{value:i},a)},e.Trans=function(e){let{children:t,count:s,parent:a,i18nKey:i,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:d,t:f,shouldUnescape:h,...m}=e;const{i18n:g,defaultNS:y}=n.useContext(A)||{},v=d||g||k(),x=f||v?.t.bind(v);return P({children:t,count:s,parent:a,i18nKey:i,context:r,tOptions:o,values:l,defaults:c,components:u,ns:p||x?.ns||y||v?.options?.defaultNS,i18n:v,t:f,shouldUnescape:h,...m})},e.TransWithoutContext=P,e.Translation=e=>{let{ns:n,children:t,...s}=e;const[a,i,r]=B(n,s);return t(a,{i18n:i,lng:i.language},r)},e.composeInitialProps=z,e.date=()=>"",e.getDefaults=$,e.getI18n=k,e.getInitialProps=F,e.initReactI18next=L,e.number=()=>"",e.plural=()=>"",e.select=()=>"",e.selectOrdinal=()=>"",e.setDefaults=N,e.setI18n=I,e.time=()=>"",e.useSSR=D,e.useTranslation=B,e.withSSR=()=>function(e){function t(t){let{initialI18nStore:s,initialLanguage:a,...i}=t;return D(s,a),n.createElement(e,{...i})}return t.getInitialProps=z(e),t.displayName=`withI18nextSSR(${g(e)})`,t.WrappedComponent=e,t},e.withTranslation=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(s){function a(a){let{forwardedRef:i,...r}=a;const[o,l,c]=B(e,{...r,keyPrefix:t.keyPrefix}),u={...r,t:o,i18n:l,tReady:c};return t.withRef&&i?u.ref=i:!t.withRef&&i&&(u.forwardedRef=i),n.createElement(s,u)}a.displayName=`withI18nextTranslation(${g(s)})`,a.WrappedComponent=s;return t.withRef?n.forwardRef(((e,t)=>n.createElement(a,Object.assign({},e,{forwardedRef:t})))):a}}})); +diff --git a/dist/commonjs/context.js b/dist/commonjs/context.js +index 5c4506e4d3424e4ffd167fd5bb696dabd67b662b..dcc11b798da72771953acce3c1368f006c3c1478 100644 +--- a/dist/commonjs/context.js ++++ b/dist/commonjs/context.js +@@ -46,7 +46,7 @@ class ReportNamespaces { + } + addUsedNamespaces(namespaces) { + namespaces.forEach(ns => { +- this.usedNamespaces[ns] ??= true; ++ this.usedNamespaces[ns] = this.usedNamespaces[ns] ?? true; + }); + } + getUsedNamespaces() { +diff --git a/dist/es/context.js b/dist/es/context.js +index 89afe45d6cf480079598dd183521b32f28f77f06..a953078f44008ee751f51257536a31ceba2fd672 100644 +--- a/dist/es/context.js ++++ b/dist/es/context.js +@@ -10,7 +10,7 @@ export class ReportNamespaces { + } + addUsedNamespaces(namespaces) { + namespaces.forEach(ns => { +- this.usedNamespaces[ns] ??= true; ++ this.usedNamespaces[ns] = this.usedNamespaces[ns] ?? true; + }); + } + getUsedNamespaces() { +diff --git a/dist/umd/react-i18next.js b/dist/umd/react-i18next.js +index 3723bd7c0f5762bdb09e3226ac86ff255cbf9859..9178ae9f7cdb776f51a64ff6c79eed1d18fbd836 100644 +--- a/dist/umd/react-i18next.js ++++ b/dist/umd/react-i18next.js +@@ -499,7 +499,7 @@ + } + addUsedNamespaces(namespaces) { + namespaces.forEach(ns => { +- this.usedNamespaces[ns] ??= true; ++ this.usedNamespaces[ns] = this.usedNamespaces[ns] ?? true; + }); + } + getUsedNamespaces() { +diff --git a/dist/umd/react-i18next.min.js b/dist/umd/react-i18next.min.js +index 2eef624040aab6b4b9ba4699bf7e4777842bf0a2..69e17753d545df9dc26aa3411b477a4dff5e8361 100644 +--- a/dist/umd/react-i18next.min.js ++++ b/dist/umd/react-i18next.min.js +@@ -1 +1 @@ +-!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).ReactI18next={},e.React)}(this,(function(e,n){"use strict";function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var s=t({area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),i=/\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function a(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(s[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var a=e.indexOf("--\x3e");return{type:"comment",comment:-1!==a?e.slice(4,a):""}}for(var r=new RegExp(i),o=null;null!==(o=r.exec(e));)if(o[0].trim())if(o[1]){var l=o[1].trim(),c=[l,""];l.indexOf("=")>-1&&(c=l.split("=")),n.attrs[c[0]]=c[1],r.lastIndex--}else o[2]&&(n.attrs[o[2]]=o[3].trim().substring(1,o[3].length-1));return n}var r=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,o=/^\s*$/,l=Object.create(null);var c=function(e,n){n||(n={}),n.components||(n.components=l);var t,s=[],i=[],c=-1,u=!1;if(0!==e.indexOf("<")){var p=e.indexOf("<");s.push({type:"text",content:-1===p?e:e.substring(0,p)})}return e.replace(r,(function(r,l){if(u){if(r!=="")return;u=!1}var p,d="/"!==r.charAt(1),f=r.startsWith("\x3c!--"),h=l+r.length,m=e.charAt(h);if(f){var g=a(r);return c<0?(s.push(g),s):((p=i[c]).children.push(g),s)}if(d&&(c++,"tag"===(t=a(r)).type&&n.components[t.name]&&(t.type="component",u=!0),t.voidElement||u||!m||"<"===m||t.children.push({type:"text",content:e.slice(h,e.indexOf("<",h))}),0===c&&s.push(t),(p=i[c-1])&&p.children.push(t),i[c]=t),(!d||t.voidElement)&&(c>-1&&(t.voidElement||t.name===r.slice(2,-1))&&(c--,t=-1===c?s:i[c]),!u&&"<"!==m&&m)){p=-1===c?s:i[c].children;var y=e.indexOf("<",h),x=e.slice(h,-1===y?void 0:y);o.test(x)&&(x=" "),(y>-1&&c+p.length>=0||" "!==x)&&p.push({type:"text",content:x})}})),s};const u=function(){if(console?.warn){for(var e=arguments.length,n=new Array(e),t=0;t()=>{if(e.isInitialized)n();else{const t=()=>{setTimeout((()=>{e.off("initialized",t)}),0),n()};e.on("initialized",t)}},h=(e,n,t)=>{e.loadNamespaces(n,f(e,t))},m=(e,n,t,s)=>{y(t)&&(t=[t]),t.forEach((n=>{e.options.ns.indexOf(n)<0&&e.options.ns.push(n)})),e.loadLanguages(n,f(e,s))},g=e=>e.displayName||e.name||(y(e)&&e.length>0?e:"Unknown"),y=e=>"string"==typeof e,x=e=>"object"==typeof e&&null!==e,b=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,v={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},E=e=>v[e];let O={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:e=>e.replace(b,E)};const N=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};O={...O,...e}},$=()=>O;let w;const I=e=>{w=e},k=()=>w,S=(e,n)=>{if(!e)return!1;const t=e.props?.children??e.children;return n?t.length>0:!!t},j=e=>{if(!e)return[];const n=e.props?.children??e.children;return e.props?.i18nIsDynamicList?R(n):n},R=e=>Array.isArray(e)?e:[e],T=(e,t)=>{if(!e)return"";let s="";const i=R(e),a=t?.transSupportBasicHtmlNodes?t.transKeepBasicHtmlNodesFor??[]:[];return i.forEach(((e,i)=>{if(y(e))s+=`${e}`;else if(n.isValidElement(e)){const{props:n,type:r}=e,o=Object.keys(n).length,l=a.indexOf(r)>-1,c=n.children;if(c||!l||o)if(!c&&(!l||o)||n.i18nIsDynamicList)s+=`<${i}>`;else if(l&&1===o&&y(c))s+=`<${r}>${c}`;else{const e=T(c,t);s+=`<${i}>${e}`}else s+=`<${r}/>`}else if(null===e)u("Trans: the passed in value is invalid - seems you passed in a null child.");else if(x(e)){const{format:n,...t}=e,i=Object.keys(t);if(1===i.length){const e=n?`${i[0]}, ${n}`:i[0];s+=`{{${e}}}`}else u("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",e)}else u("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",e)})),s},C=(e,t,s,i,a,r)=>{if(""===t)return[];const o=i.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(o.map((e=>`<${e}`)).join("|")).test(t);if(!e&&!l&&!r)return[t];const u={},p=e=>{R(e).forEach((e=>{y(e)||(S(e)?p(j(e)):x(e)&&!n.isValidElement(e)&&Object.assign(u,e))}))};p(e);const d=c(`<0>${t}`),f={...u,...a},h=(e,t,s)=>{const i=j(e),a=g(i,t.children,s);return(e=>Array.isArray(e)&&e.every(n.isValidElement))(i)&&0===a.length||e.props?.i18nIsDynamicList?i:a},m=(e,t,s,i,a)=>{e.dummy?(e.children=t,s.push(n.cloneElement(e,{key:i},a?void 0:t))):s.push(...n.Children.map([e],(e=>{const s={...e.props};return delete s.i18nIsDynamicList,n.createElement(e.type,{...s,key:i,ref:e.ref},a?null:t)})))},g=(t,a,c)=>{const u=R(t);return R(a).reduce(((t,a,p)=>{const d=a.children?.[0]?.content&&s.services.interpolator.interpolate(a.children[0].content,f,s.language);if("tag"===a.type){let r=u[parseInt(a.name,10)];1!==c.length||r||(r=c[0][a.name]),r||(r={});const b=0!==Object.keys(a.attrs).length?((e,n)=>{const t={...n};return t.props=Object.assign(e.props,n.props),t})({props:a.attrs},r):r,v=n.isValidElement(b),E=v&&S(a,!0)&&!a.voidElement,O=l&&x(b)&&b.dummy&&!v,N=x(e)&&Object.hasOwnProperty.call(e,a.name);if(y(b)){const e=s.services.interpolator.interpolate(b,f,s.language);t.push(e)}else if(S(b)||E){const e=h(b,a,c);m(b,e,t,p)}else if(O){const e=g(u,a.children,c);m(b,e,t,p)}else if(Number.isNaN(parseFloat(a.name)))if(N){const e=h(b,a,c);m(b,e,t,p,a.voidElement)}else if(i.transSupportBasicHtmlNodes&&o.indexOf(a.name)>-1)if(a.voidElement)t.push(n.createElement(a.name,{key:`${a.name}-${p}`}));else{const e=g(u,a.children,c);t.push(n.createElement(a.name,{key:`${a.name}-${p}`},e))}else if(a.voidElement)t.push(`<${a.name} />`);else{const e=g(u,a.children,c);t.push(`<${a.name}>${e}`)}else if(x(b)&&!v){const e=a.children[0]?d:null;e&&t.push(e)}else m(b,d,t,p,1!==a.children.length||!d)}else if("text"===a.type){const e=i.transWrapTextNodes,o=r?i.unescape(s.services.interpolator.interpolate(a.content,f,s.language)):s.services.interpolator.interpolate(a.content,f,s.language);e?t.push(n.createElement(e,{key:`${a.name}-${p}`},o)):t.push(o)}return t}),[])},b=g([{dummy:!0,children:e||[]}],d,R(e||[]));return j(b[0])};function P(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:f,t:h,shouldUnescape:m,...g}=e;const x=f||k();if(!x)return d("You will need to pass in an i18next instance by using i18nextReactModule"),t;const b=h||x.t.bind(x)||(e=>e),v={...$(),...x.options?.react};let E=p||b.ns||x.options?.defaultNS;E=y(E)?[E]:E||["translation"];const O=T(t,v),N=c||O||v.transEmptyNodeValue||a,{hashTransKey:w}=v,I=a||(w?w(O||N):O||N);x.options?.interpolation?.defaultVariables&&(l=l&&Object.keys(l).length>0?{...l,...x.options.interpolation.defaultVariables}:{...x.options.interpolation.defaultVariables});const S=l||void 0!==s||!t?o.interpolation:{interpolation:{...o.interpolation,prefix:"#$?",suffix:"?$#"}},j={...o,context:r||o.context,count:s,...l,...S,defaultValue:N,ns:E},R=I?b(I,j):N;u&&Object.keys(u).forEach((e=>{const t=u[e];"function"==typeof t.type||!t.props||!t.props.children||R.indexOf(`${e}/>`)<0&&R.indexOf(`${e} />`)<0||(u[e]=n.createElement((function(){return n.createElement(n.Fragment,null,t)})))}));const P=C(u||t,R,x,v,j,m),L=i??v.defaultTransParent;return L?n.createElement(L,g,P):P}const L={type:"3rdParty",init(e){N(e.options.react),I(e)}},A=n.createContext();class V{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach((e=>{this.usedNamespaces[e]??=!0}))}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}const z=e=>async n=>({...await(e.getInitialProps?.(n))??{},...F()}),F=()=>{const e=k(),n=e.reportNamespaces?.getUsedNamespaces()??[],t={},s={};return e.languages.forEach((t=>{s[t]={},n.forEach((n=>{s[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=s,t.initialLanguage=e.language,t};const U=(e,n,t,s)=>e.getFixedT(n,t,s),B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{i18n:s}=t,{i18n:i,defaultNS:a}=n.useContext(A)||{},r=s||i||k();if(r&&!r.reportNamespaces&&(r.reportNamespaces=new V),!r){d("You will need to pass in an i18next instance by using initReactI18next");const e=(e,n)=>y(n)?n:x(n)&&y(n.defaultValue)?n.defaultValue:Array.isArray(e)?e[e.length-1]:e,n=[e,{},!1];return n.t=e,n.i18n={},n.ready=!1,n}r.options.react?.wait&&d("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");const o={...$(),...r.options.react,...t},{useSuspense:l,keyPrefix:c}=o;let u=e||a||r.options?.defaultNS;u=y(u)?[u]:u||["translation"],r.reportNamespaces.addUsedNamespaces?.(u);const p=(r.isInitialized||r.initializedStoreOnce)&&u.every((e=>function(e,n){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n.languages&&n.languages.length?n.hasLoadedNamespace(e,{lng:t.lng,precheck:(n,s)=>{if(t.bindI18n?.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!s(n.isLanguageChangingTo,e))return!1}}):(d("i18n.languages were undefined or empty",n.languages),!0)}(e,r,o))),f=((e,t,s,i)=>n.useCallback(U(e,t,s,i),[e,t,s,i]))(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),g=()=>f,b=()=>U(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),[v,E]=n.useState(g);let O=u.join();t.lng&&(O=`${t.lng}${O}`);const N=((e,t)=>{const s=n.useRef();return n.useEffect((()=>{s.current=e}),[e,t]),s.current})(O),w=n.useRef(!0);n.useEffect((()=>{const{bindI18n:e,bindI18nStore:n}=o;w.current=!0,p||l||(t.lng?m(r,t.lng,u,(()=>{w.current&&E(b)})):h(r,u,(()=>{w.current&&E(b)}))),p&&N&&N!==O&&w.current&&E(b);const s=()=>{w.current&&E(b)};return e&&r?.on(e,s),n&&r?.store.on(n,s),()=>{w.current=!1,r&&e?.split(" ").forEach((e=>r.off(e,s))),n&&r&&n.split(" ").forEach((e=>r.store.off(e,s)))}}),[r,O]),n.useEffect((()=>{w.current&&p&&E(g)}),[r,c,p]);const I=[v,r,p];if(I.t=v,I.i18n=r,I.ready=p,p)return I;if(!p&&!l)return I;throw new Promise((e=>{t.lng?m(r,t.lng,u,(()=>e())):h(r,u,(()=>e()))}))};const D=function(e,t){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{i18n:i}=s,{i18n:a}=n.useContext(A)||{},r=i||a||k();r.options?.isClone||(e&&!r.initializedStoreOnce&&(r.services.resourceStore.data=e,r.options.ns=Object.values(e).reduce(((e,n)=>(Object.keys(n).forEach((n=>{e.indexOf(n)<0&&e.push(n)})),e)),r.options.ns),r.initializedStoreOnce=!0,r.isInitialized=!0),t&&!r.initializedLanguageOnce&&(r.changeLanguage(t),r.initializedLanguageOnce=!0))};e.I18nContext=A,e.I18nextProvider=function(e){let{i18n:t,defaultNS:s,children:i}=e;const a=n.useMemo((()=>({i18n:t,defaultNS:s})),[t,s]);return n.createElement(A.Provider,{value:a},i)},e.Trans=function(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:d,t:f,shouldUnescape:h,...m}=e;const{i18n:g,defaultNS:y}=n.useContext(A)||{},x=d||g||k(),b=f||x?.t.bind(x);return P({children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o,values:l,defaults:c,components:u,ns:p||b?.ns||y||x?.options?.defaultNS,i18n:x,t:f,shouldUnescape:h,...m})},e.TransWithoutContext=P,e.Translation=e=>{let{ns:n,children:t,...s}=e;const[i,a,r]=B(n,s);return t(i,{i18n:a,lng:a.language},r)},e.composeInitialProps=z,e.date=()=>"",e.getDefaults=$,e.getI18n=k,e.getInitialProps=F,e.initReactI18next=L,e.number=()=>"",e.plural=()=>"",e.select=()=>"",e.selectOrdinal=()=>"",e.setDefaults=N,e.setI18n=I,e.time=()=>"",e.useSSR=D,e.useTranslation=B,e.withSSR=()=>function(e){function t(t){let{initialI18nStore:s,initialLanguage:i,...a}=t;return D(s,i),n.createElement(e,{...a})}return t.getInitialProps=z(e),t.displayName=`withI18nextSSR(${g(e)})`,t.WrappedComponent=e,t},e.withTranslation=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(s){function i(i){let{forwardedRef:a,...r}=i;const[o,l,c]=B(e,{...r,keyPrefix:t.keyPrefix}),u={...r,t:o,i18n:l,tReady:c};return t.withRef&&a?u.ref=a:!t.withRef&&a&&(u.forwardedRef=a),n.createElement(s,u)}i.displayName=`withI18nextTranslation(${g(s)})`,i.WrappedComponent=s;return t.withRef?n.forwardRef(((e,t)=>n.createElement(i,Object.assign({},e,{forwardedRef:t})))):i}}})); ++!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).ReactI18next={},e.React)}(this,(function(e,n){"use strict";function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var s=t({area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),i=/\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function a(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(s[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var a=e.indexOf("--\x3e");return{type:"comment",comment:-1!==a?e.slice(4,a):""}}for(var r=new RegExp(i),o=null;null!==(o=r.exec(e));)if(o[0].trim())if(o[1]){var l=o[1].trim(),c=[l,""];l.indexOf("=")>-1&&(c=l.split("=")),n.attrs[c[0]]=c[1],r.lastIndex--}else o[2]&&(n.attrs[o[2]]=o[3].trim().substring(1,o[3].length-1));return n}var r=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,o=/^\s*$/,l=Object.create(null);var c=function(e,n){n||(n={}),n.components||(n.components=l);var t,s=[],i=[],c=-1,u=!1;if(0!==e.indexOf("<")){var p=e.indexOf("<");s.push({type:"text",content:-1===p?e:e.substring(0,p)})}return e.replace(r,(function(r,l){if(u){if(r!=="")return;u=!1}var p,d="/"!==r.charAt(1),f=r.startsWith("\x3c!--"),h=l+r.length,m=e.charAt(h);if(f){var g=a(r);return c<0?(s.push(g),s):((p=i[c]).children.push(g),s)}if(d&&(c++,"tag"===(t=a(r)).type&&n.components[t.name]&&(t.type="component",u=!0),t.voidElement||u||!m||"<"===m||t.children.push({type:"text",content:e.slice(h,e.indexOf("<",h))}),0===c&&s.push(t),(p=i[c-1])&&p.children.push(t),i[c]=t),(!d||t.voidElement)&&(c>-1&&(t.voidElement||t.name===r.slice(2,-1))&&(c--,t=-1===c?s:i[c]),!u&&"<"!==m&&m)){p=-1===c?s:i[c].children;var y=e.indexOf("<",h),x=e.slice(h,-1===y?void 0:y);o.test(x)&&(x=" "),(y>-1&&c+p.length>=0||" "!==x)&&p.push({type:"text",content:x})}})),s};const u=function(){if(console?.warn){for(var e=arguments.length,n=new Array(e),t=0;t()=>{if(e.isInitialized)n();else{const t=()=>{setTimeout((()=>{e.off("initialized",t)}),0),n()};e.on("initialized",t)}},h=(e,n,t)=>{e.loadNamespaces(n,f(e,t))},m=(e,n,t,s)=>{y(t)&&(t=[t]),t.forEach((n=>{e.options.ns.indexOf(n)<0&&e.options.ns.push(n)})),e.loadLanguages(n,f(e,s))},g=e=>e.displayName||e.name||(y(e)&&e.length>0?e:"Unknown"),y=e=>"string"==typeof e,x=e=>"object"==typeof e&&null!==e,b=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,v={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},E=e=>v[e];let O={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:e=>e.replace(b,E)};const N=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};O={...O,...e}},$=()=>O;let w;const I=e=>{w=e},k=()=>w,S=(e,n)=>{if(!e)return!1;const t=e.props?.children??e.children;return n?t.length>0:!!t},j=e=>{if(!e)return[];const n=e.props?.children??e.children;return e.props?.i18nIsDynamicList?R(n):n},R=e=>Array.isArray(e)?e:[e],T=(e,t)=>{if(!e)return"";let s="";const i=R(e),a=t?.transSupportBasicHtmlNodes?t.transKeepBasicHtmlNodesFor??[]:[];return i.forEach(((e,i)=>{if(y(e))s+=`${e}`;else if(n.isValidElement(e)){const{props:n,type:r}=e,o=Object.keys(n).length,l=a.indexOf(r)>-1,c=n.children;if(c||!l||o)if(!c&&(!l||o)||n.i18nIsDynamicList)s+=`<${i}>`;else if(l&&1===o&&y(c))s+=`<${r}>${c}`;else{const e=T(c,t);s+=`<${i}>${e}`}else s+=`<${r}/>`}else if(null===e)u("Trans: the passed in value is invalid - seems you passed in a null child.");else if(x(e)){const{format:n,...t}=e,i=Object.keys(t);if(1===i.length){const e=n?`${i[0]}, ${n}`:i[0];s+=`{{${e}}}`}else u("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",e)}else u("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",e)})),s},C=(e,t,s,i,a,r)=>{if(""===t)return[];const o=i.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(o.map((e=>`<${e}`)).join("|")).test(t);if(!e&&!l&&!r)return[t];const u={},p=e=>{R(e).forEach((e=>{y(e)||(S(e)?p(j(e)):x(e)&&!n.isValidElement(e)&&Object.assign(u,e))}))};p(e);const d=c(`<0>${t}`),f={...u,...a},h=(e,t,s)=>{const i=j(e),a=g(i,t.children,s);return(e=>Array.isArray(e)&&e.every(n.isValidElement))(i)&&0===a.length||e.props?.i18nIsDynamicList?i:a},m=(e,t,s,i,a)=>{e.dummy?(e.children=t,s.push(n.cloneElement(e,{key:i},a?void 0:t))):s.push(...n.Children.map([e],(e=>{const s={...e.props};return delete s.i18nIsDynamicList,n.createElement(e.type,{...s,key:i,ref:e.ref},a?null:t)})))},g=(t,a,c)=>{const u=R(t);return R(a).reduce(((t,a,p)=>{const d=a.children?.[0]?.content&&s.services.interpolator.interpolate(a.children[0].content,f,s.language);if("tag"===a.type){let r=u[parseInt(a.name,10)];1!==c.length||r||(r=c[0][a.name]),r||(r={});const b=0!==Object.keys(a.attrs).length?((e,n)=>{const t={...n};return t.props=Object.assign(e.props,n.props),t})({props:a.attrs},r):r,v=n.isValidElement(b),E=v&&S(a,!0)&&!a.voidElement,O=l&&x(b)&&b.dummy&&!v,N=x(e)&&Object.hasOwnProperty.call(e,a.name);if(y(b)){const e=s.services.interpolator.interpolate(b,f,s.language);t.push(e)}else if(S(b)||E){const e=h(b,a,c);m(b,e,t,p)}else if(O){const e=g(u,a.children,c);m(b,e,t,p)}else if(Number.isNaN(parseFloat(a.name)))if(N){const e=h(b,a,c);m(b,e,t,p,a.voidElement)}else if(i.transSupportBasicHtmlNodes&&o.indexOf(a.name)>-1)if(a.voidElement)t.push(n.createElement(a.name,{key:`${a.name}-${p}`}));else{const e=g(u,a.children,c);t.push(n.createElement(a.name,{key:`${a.name}-${p}`},e))}else if(a.voidElement)t.push(`<${a.name} />`);else{const e=g(u,a.children,c);t.push(`<${a.name}>${e}`)}else if(x(b)&&!v){const e=a.children[0]?d:null;e&&t.push(e)}else m(b,d,t,p,1!==a.children.length||!d)}else if("text"===a.type){const e=i.transWrapTextNodes,o=r?i.unescape(s.services.interpolator.interpolate(a.content,f,s.language)):s.services.interpolator.interpolate(a.content,f,s.language);e?t.push(n.createElement(e,{key:`${a.name}-${p}`},o)):t.push(o)}return t}),[])},b=g([{dummy:!0,children:e||[]}],d,R(e||[]));return j(b[0])};function P(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:f,t:h,shouldUnescape:m,...g}=e;const x=f||k();if(!x)return d("You will need to pass in an i18next instance by using i18nextReactModule"),t;const b=h||x.t.bind(x)||(e=>e),v={...$(),...x.options?.react};let E=p||b.ns||x.options?.defaultNS;E=y(E)?[E]:E||["translation"];const O=T(t,v),N=c||O||v.transEmptyNodeValue||a,{hashTransKey:w}=v,I=a||(w?w(O||N):O||N);x.options?.interpolation?.defaultVariables&&(l=l&&Object.keys(l).length>0?{...l,...x.options.interpolation.defaultVariables}:{...x.options.interpolation.defaultVariables});const S=l||void 0!==s||!t?o.interpolation:{interpolation:{...o.interpolation,prefix:"#$?",suffix:"?$#"}},j={...o,context:r||o.context,count:s,...l,...S,defaultValue:N,ns:E},R=I?b(I,j):N;u&&Object.keys(u).forEach((e=>{const t=u[e];"function"==typeof t.type||!t.props||!t.props.children||R.indexOf(`${e}/>`)<0&&R.indexOf(`${e} />`)<0||(u[e]=n.createElement((function(){return n.createElement(n.Fragment,null,t)})))}));const P=C(u||t,R,x,v,j,m),L=i??v.defaultTransParent;return L?n.createElement(L,g,P):P}const L={type:"3rdParty",init(e){N(e.options.react),I(e)}},A=n.createContext();class V{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach((e=>{this.usedNamespaces[e]=this.usedNamespaces[e]??!0}))}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}const z=e=>async n=>({...await(e.getInitialProps?.(n))??{},...F()}),F=()=>{const e=k(),n=e.reportNamespaces?.getUsedNamespaces()??[],t={},s={};return e.languages.forEach((t=>{s[t]={},n.forEach((n=>{s[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=s,t.initialLanguage=e.language,t};const U=(e,n,t,s)=>e.getFixedT(n,t,s),B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{i18n:s}=t,{i18n:i,defaultNS:a}=n.useContext(A)||{},r=s||i||k();if(r&&!r.reportNamespaces&&(r.reportNamespaces=new V),!r){d("You will need to pass in an i18next instance by using initReactI18next");const e=(e,n)=>y(n)?n:x(n)&&y(n.defaultValue)?n.defaultValue:Array.isArray(e)?e[e.length-1]:e,n=[e,{},!1];return n.t=e,n.i18n={},n.ready=!1,n}r.options.react?.wait&&d("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");const o={...$(),...r.options.react,...t},{useSuspense:l,keyPrefix:c}=o;let u=e||a||r.options?.defaultNS;u=y(u)?[u]:u||["translation"],r.reportNamespaces.addUsedNamespaces?.(u);const p=(r.isInitialized||r.initializedStoreOnce)&&u.every((e=>function(e,n){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n.languages&&n.languages.length?n.hasLoadedNamespace(e,{lng:t.lng,precheck:(n,s)=>{if(t.bindI18n?.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!s(n.isLanguageChangingTo,e))return!1}}):(d("i18n.languages were undefined or empty",n.languages),!0)}(e,r,o))),f=((e,t,s,i)=>n.useCallback(U(e,t,s,i),[e,t,s,i]))(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),g=()=>f,b=()=>U(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),[v,E]=n.useState(g);let O=u.join();t.lng&&(O=`${t.lng}${O}`);const N=((e,t)=>{const s=n.useRef();return n.useEffect((()=>{s.current=e}),[e,t]),s.current})(O),w=n.useRef(!0);n.useEffect((()=>{const{bindI18n:e,bindI18nStore:n}=o;w.current=!0,p||l||(t.lng?m(r,t.lng,u,(()=>{w.current&&E(b)})):h(r,u,(()=>{w.current&&E(b)}))),p&&N&&N!==O&&w.current&&E(b);const s=()=>{w.current&&E(b)};return e&&r?.on(e,s),n&&r?.store.on(n,s),()=>{w.current=!1,r&&e?.split(" ").forEach((e=>r.off(e,s))),n&&r&&n.split(" ").forEach((e=>r.store.off(e,s)))}}),[r,O]),n.useEffect((()=>{w.current&&p&&E(g)}),[r,c,p]);const I=[v,r,p];if(I.t=v,I.i18n=r,I.ready=p,p)return I;if(!p&&!l)return I;throw new Promise((e=>{t.lng?m(r,t.lng,u,(()=>e())):h(r,u,(()=>e()))}))};const D=function(e,t){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{i18n:i}=s,{i18n:a}=n.useContext(A)||{},r=i||a||k();r.options?.isClone||(e&&!r.initializedStoreOnce&&(r.services.resourceStore.data=e,r.options.ns=Object.values(e).reduce(((e,n)=>(Object.keys(n).forEach((n=>{e.indexOf(n)<0&&e.push(n)})),e)),r.options.ns),r.initializedStoreOnce=!0,r.isInitialized=!0),t&&!r.initializedLanguageOnce&&(r.changeLanguage(t),r.initializedLanguageOnce=!0))};e.I18nContext=A,e.I18nextProvider=function(e){let{i18n:t,defaultNS:s,children:i}=e;const a=n.useMemo((()=>({i18n:t,defaultNS:s})),[t,s]);return n.createElement(A.Provider,{value:a},i)},e.Trans=function(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:d,t:f,shouldUnescape:h,...m}=e;const{i18n:g,defaultNS:y}=n.useContext(A)||{},x=d||g||k(),b=f||x?.t.bind(x);return P({children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o,values:l,defaults:c,components:u,ns:p||b?.ns||y||x?.options?.defaultNS,i18n:x,t:f,shouldUnescape:h,...m})},e.TransWithoutContext=P,e.Translation=e=>{let{ns:n,children:t,...s}=e;const[i,a,r]=B(n,s);return t(i,{i18n:a,lng:a.language},r)},e.composeInitialProps=z,e.date=()=>"",e.getDefaults=$,e.getI18n=k,e.getInitialProps=F,e.initReactI18next=L,e.number=()=>"",e.plural=()=>"",e.select=()=>"",e.selectOrdinal=()=>"",e.setDefaults=N,e.setI18n=I,e.time=()=>"",e.useSSR=D,e.useTranslation=B,e.withSSR=()=>function(e){function t(t){let{initialI18nStore:s,initialLanguage:i,...a}=t;return D(s,i),n.createElement(e,{...a})}return t.getInitialProps=z(e),t.displayName=`withI18nextSSR(${g(e)})`,t.WrappedComponent=e,t},e.withTranslation=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(s){function i(i){let{forwardedRef:a,...r}=i;const[o,l,c]=B(e,{...r,keyPrefix:t.keyPrefix}),u={...r,t:o,i18n:l,tReady:c};return t.withRef&&a?u.ref=a:!t.withRef&&a&&(u.forwardedRef=a),n.createElement(s,u)}i.displayName=`withI18nextTranslation(${g(s)})`,i.WrappedComponent=s;return t.withRef?n.forwardRef(((e,t)=>n.createElement(i,Object.assign({},e,{forwardedRef:t})))):i}}})); +diff --git a/react-i18next.js b/react-i18next.js +index 3723bd7c0f5762bdb09e3226ac86ff255cbf9859..9178ae9f7cdb776f51a64ff6c79eed1d18fbd836 100644 +--- a/react-i18next.js ++++ b/react-i18next.js +@@ -499,7 +499,7 @@ + } + addUsedNamespaces(namespaces) { + namespaces.forEach(ns => { +- this.usedNamespaces[ns] ??= true; ++ this.usedNamespaces[ns] = this.usedNamespaces[ns] ?? true; + }); + } + getUsedNamespaces() { +diff --git a/react-i18next.min.js b/react-i18next.min.js +index 2eef624040aab6b4b9ba4699bf7e4777842bf0a2..69e17753d545df9dc26aa3411b477a4dff5e8361 100644 +--- a/react-i18next.min.js ++++ b/react-i18next.min.js +@@ -1 +1 @@ +-!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).ReactI18next={},e.React)}(this,(function(e,n){"use strict";function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var s=t({area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),i=/\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function a(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(s[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var a=e.indexOf("--\x3e");return{type:"comment",comment:-1!==a?e.slice(4,a):""}}for(var r=new RegExp(i),o=null;null!==(o=r.exec(e));)if(o[0].trim())if(o[1]){var l=o[1].trim(),c=[l,""];l.indexOf("=")>-1&&(c=l.split("=")),n.attrs[c[0]]=c[1],r.lastIndex--}else o[2]&&(n.attrs[o[2]]=o[3].trim().substring(1,o[3].length-1));return n}var r=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,o=/^\s*$/,l=Object.create(null);var c=function(e,n){n||(n={}),n.components||(n.components=l);var t,s=[],i=[],c=-1,u=!1;if(0!==e.indexOf("<")){var p=e.indexOf("<");s.push({type:"text",content:-1===p?e:e.substring(0,p)})}return e.replace(r,(function(r,l){if(u){if(r!=="")return;u=!1}var p,d="/"!==r.charAt(1),f=r.startsWith("\x3c!--"),h=l+r.length,m=e.charAt(h);if(f){var g=a(r);return c<0?(s.push(g),s):((p=i[c]).children.push(g),s)}if(d&&(c++,"tag"===(t=a(r)).type&&n.components[t.name]&&(t.type="component",u=!0),t.voidElement||u||!m||"<"===m||t.children.push({type:"text",content:e.slice(h,e.indexOf("<",h))}),0===c&&s.push(t),(p=i[c-1])&&p.children.push(t),i[c]=t),(!d||t.voidElement)&&(c>-1&&(t.voidElement||t.name===r.slice(2,-1))&&(c--,t=-1===c?s:i[c]),!u&&"<"!==m&&m)){p=-1===c?s:i[c].children;var y=e.indexOf("<",h),x=e.slice(h,-1===y?void 0:y);o.test(x)&&(x=" "),(y>-1&&c+p.length>=0||" "!==x)&&p.push({type:"text",content:x})}})),s};const u=function(){if(console?.warn){for(var e=arguments.length,n=new Array(e),t=0;t()=>{if(e.isInitialized)n();else{const t=()=>{setTimeout((()=>{e.off("initialized",t)}),0),n()};e.on("initialized",t)}},h=(e,n,t)=>{e.loadNamespaces(n,f(e,t))},m=(e,n,t,s)=>{y(t)&&(t=[t]),t.forEach((n=>{e.options.ns.indexOf(n)<0&&e.options.ns.push(n)})),e.loadLanguages(n,f(e,s))},g=e=>e.displayName||e.name||(y(e)&&e.length>0?e:"Unknown"),y=e=>"string"==typeof e,x=e=>"object"==typeof e&&null!==e,b=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,v={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},E=e=>v[e];let O={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:e=>e.replace(b,E)};const N=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};O={...O,...e}},$=()=>O;let w;const I=e=>{w=e},k=()=>w,S=(e,n)=>{if(!e)return!1;const t=e.props?.children??e.children;return n?t.length>0:!!t},j=e=>{if(!e)return[];const n=e.props?.children??e.children;return e.props?.i18nIsDynamicList?R(n):n},R=e=>Array.isArray(e)?e:[e],T=(e,t)=>{if(!e)return"";let s="";const i=R(e),a=t?.transSupportBasicHtmlNodes?t.transKeepBasicHtmlNodesFor??[]:[];return i.forEach(((e,i)=>{if(y(e))s+=`${e}`;else if(n.isValidElement(e)){const{props:n,type:r}=e,o=Object.keys(n).length,l=a.indexOf(r)>-1,c=n.children;if(c||!l||o)if(!c&&(!l||o)||n.i18nIsDynamicList)s+=`<${i}>`;else if(l&&1===o&&y(c))s+=`<${r}>${c}`;else{const e=T(c,t);s+=`<${i}>${e}`}else s+=`<${r}/>`}else if(null===e)u("Trans: the passed in value is invalid - seems you passed in a null child.");else if(x(e)){const{format:n,...t}=e,i=Object.keys(t);if(1===i.length){const e=n?`${i[0]}, ${n}`:i[0];s+=`{{${e}}}`}else u("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",e)}else u("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",e)})),s},C=(e,t,s,i,a,r)=>{if(""===t)return[];const o=i.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(o.map((e=>`<${e}`)).join("|")).test(t);if(!e&&!l&&!r)return[t];const u={},p=e=>{R(e).forEach((e=>{y(e)||(S(e)?p(j(e)):x(e)&&!n.isValidElement(e)&&Object.assign(u,e))}))};p(e);const d=c(`<0>${t}`),f={...u,...a},h=(e,t,s)=>{const i=j(e),a=g(i,t.children,s);return(e=>Array.isArray(e)&&e.every(n.isValidElement))(i)&&0===a.length||e.props?.i18nIsDynamicList?i:a},m=(e,t,s,i,a)=>{e.dummy?(e.children=t,s.push(n.cloneElement(e,{key:i},a?void 0:t))):s.push(...n.Children.map([e],(e=>{const s={...e.props};return delete s.i18nIsDynamicList,n.createElement(e.type,{...s,key:i,ref:e.ref},a?null:t)})))},g=(t,a,c)=>{const u=R(t);return R(a).reduce(((t,a,p)=>{const d=a.children?.[0]?.content&&s.services.interpolator.interpolate(a.children[0].content,f,s.language);if("tag"===a.type){let r=u[parseInt(a.name,10)];1!==c.length||r||(r=c[0][a.name]),r||(r={});const b=0!==Object.keys(a.attrs).length?((e,n)=>{const t={...n};return t.props=Object.assign(e.props,n.props),t})({props:a.attrs},r):r,v=n.isValidElement(b),E=v&&S(a,!0)&&!a.voidElement,O=l&&x(b)&&b.dummy&&!v,N=x(e)&&Object.hasOwnProperty.call(e,a.name);if(y(b)){const e=s.services.interpolator.interpolate(b,f,s.language);t.push(e)}else if(S(b)||E){const e=h(b,a,c);m(b,e,t,p)}else if(O){const e=g(u,a.children,c);m(b,e,t,p)}else if(Number.isNaN(parseFloat(a.name)))if(N){const e=h(b,a,c);m(b,e,t,p,a.voidElement)}else if(i.transSupportBasicHtmlNodes&&o.indexOf(a.name)>-1)if(a.voidElement)t.push(n.createElement(a.name,{key:`${a.name}-${p}`}));else{const e=g(u,a.children,c);t.push(n.createElement(a.name,{key:`${a.name}-${p}`},e))}else if(a.voidElement)t.push(`<${a.name} />`);else{const e=g(u,a.children,c);t.push(`<${a.name}>${e}`)}else if(x(b)&&!v){const e=a.children[0]?d:null;e&&t.push(e)}else m(b,d,t,p,1!==a.children.length||!d)}else if("text"===a.type){const e=i.transWrapTextNodes,o=r?i.unescape(s.services.interpolator.interpolate(a.content,f,s.language)):s.services.interpolator.interpolate(a.content,f,s.language);e?t.push(n.createElement(e,{key:`${a.name}-${p}`},o)):t.push(o)}return t}),[])},b=g([{dummy:!0,children:e||[]}],d,R(e||[]));return j(b[0])};function P(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:f,t:h,shouldUnescape:m,...g}=e;const x=f||k();if(!x)return d("You will need to pass in an i18next instance by using i18nextReactModule"),t;const b=h||x.t.bind(x)||(e=>e),v={...$(),...x.options?.react};let E=p||b.ns||x.options?.defaultNS;E=y(E)?[E]:E||["translation"];const O=T(t,v),N=c||O||v.transEmptyNodeValue||a,{hashTransKey:w}=v,I=a||(w?w(O||N):O||N);x.options?.interpolation?.defaultVariables&&(l=l&&Object.keys(l).length>0?{...l,...x.options.interpolation.defaultVariables}:{...x.options.interpolation.defaultVariables});const S=l||void 0!==s||!t?o.interpolation:{interpolation:{...o.interpolation,prefix:"#$?",suffix:"?$#"}},j={...o,context:r||o.context,count:s,...l,...S,defaultValue:N,ns:E},R=I?b(I,j):N;u&&Object.keys(u).forEach((e=>{const t=u[e];"function"==typeof t.type||!t.props||!t.props.children||R.indexOf(`${e}/>`)<0&&R.indexOf(`${e} />`)<0||(u[e]=n.createElement((function(){return n.createElement(n.Fragment,null,t)})))}));const P=C(u||t,R,x,v,j,m),L=i??v.defaultTransParent;return L?n.createElement(L,g,P):P}const L={type:"3rdParty",init(e){N(e.options.react),I(e)}},A=n.createContext();class V{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach((e=>{this.usedNamespaces[e]??=!0}))}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}const z=e=>async n=>({...await(e.getInitialProps?.(n))??{},...F()}),F=()=>{const e=k(),n=e.reportNamespaces?.getUsedNamespaces()??[],t={},s={};return e.languages.forEach((t=>{s[t]={},n.forEach((n=>{s[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=s,t.initialLanguage=e.language,t};const U=(e,n,t,s)=>e.getFixedT(n,t,s),B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{i18n:s}=t,{i18n:i,defaultNS:a}=n.useContext(A)||{},r=s||i||k();if(r&&!r.reportNamespaces&&(r.reportNamespaces=new V),!r){d("You will need to pass in an i18next instance by using initReactI18next");const e=(e,n)=>y(n)?n:x(n)&&y(n.defaultValue)?n.defaultValue:Array.isArray(e)?e[e.length-1]:e,n=[e,{},!1];return n.t=e,n.i18n={},n.ready=!1,n}r.options.react?.wait&&d("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");const o={...$(),...r.options.react,...t},{useSuspense:l,keyPrefix:c}=o;let u=e||a||r.options?.defaultNS;u=y(u)?[u]:u||["translation"],r.reportNamespaces.addUsedNamespaces?.(u);const p=(r.isInitialized||r.initializedStoreOnce)&&u.every((e=>function(e,n){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n.languages&&n.languages.length?n.hasLoadedNamespace(e,{lng:t.lng,precheck:(n,s)=>{if(t.bindI18n?.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!s(n.isLanguageChangingTo,e))return!1}}):(d("i18n.languages were undefined or empty",n.languages),!0)}(e,r,o))),f=((e,t,s,i)=>n.useCallback(U(e,t,s,i),[e,t,s,i]))(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),g=()=>f,b=()=>U(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),[v,E]=n.useState(g);let O=u.join();t.lng&&(O=`${t.lng}${O}`);const N=((e,t)=>{const s=n.useRef();return n.useEffect((()=>{s.current=e}),[e,t]),s.current})(O),w=n.useRef(!0);n.useEffect((()=>{const{bindI18n:e,bindI18nStore:n}=o;w.current=!0,p||l||(t.lng?m(r,t.lng,u,(()=>{w.current&&E(b)})):h(r,u,(()=>{w.current&&E(b)}))),p&&N&&N!==O&&w.current&&E(b);const s=()=>{w.current&&E(b)};return e&&r?.on(e,s),n&&r?.store.on(n,s),()=>{w.current=!1,r&&e?.split(" ").forEach((e=>r.off(e,s))),n&&r&&n.split(" ").forEach((e=>r.store.off(e,s)))}}),[r,O]),n.useEffect((()=>{w.current&&p&&E(g)}),[r,c,p]);const I=[v,r,p];if(I.t=v,I.i18n=r,I.ready=p,p)return I;if(!p&&!l)return I;throw new Promise((e=>{t.lng?m(r,t.lng,u,(()=>e())):h(r,u,(()=>e()))}))};const D=function(e,t){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{i18n:i}=s,{i18n:a}=n.useContext(A)||{},r=i||a||k();r.options?.isClone||(e&&!r.initializedStoreOnce&&(r.services.resourceStore.data=e,r.options.ns=Object.values(e).reduce(((e,n)=>(Object.keys(n).forEach((n=>{e.indexOf(n)<0&&e.push(n)})),e)),r.options.ns),r.initializedStoreOnce=!0,r.isInitialized=!0),t&&!r.initializedLanguageOnce&&(r.changeLanguage(t),r.initializedLanguageOnce=!0))};e.I18nContext=A,e.I18nextProvider=function(e){let{i18n:t,defaultNS:s,children:i}=e;const a=n.useMemo((()=>({i18n:t,defaultNS:s})),[t,s]);return n.createElement(A.Provider,{value:a},i)},e.Trans=function(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:d,t:f,shouldUnescape:h,...m}=e;const{i18n:g,defaultNS:y}=n.useContext(A)||{},x=d||g||k(),b=f||x?.t.bind(x);return P({children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o,values:l,defaults:c,components:u,ns:p||b?.ns||y||x?.options?.defaultNS,i18n:x,t:f,shouldUnescape:h,...m})},e.TransWithoutContext=P,e.Translation=e=>{let{ns:n,children:t,...s}=e;const[i,a,r]=B(n,s);return t(i,{i18n:a,lng:a.language},r)},e.composeInitialProps=z,e.date=()=>"",e.getDefaults=$,e.getI18n=k,e.getInitialProps=F,e.initReactI18next=L,e.number=()=>"",e.plural=()=>"",e.select=()=>"",e.selectOrdinal=()=>"",e.setDefaults=N,e.setI18n=I,e.time=()=>"",e.useSSR=D,e.useTranslation=B,e.withSSR=()=>function(e){function t(t){let{initialI18nStore:s,initialLanguage:i,...a}=t;return D(s,i),n.createElement(e,{...a})}return t.getInitialProps=z(e),t.displayName=`withI18nextSSR(${g(e)})`,t.WrappedComponent=e,t},e.withTranslation=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(s){function i(i){let{forwardedRef:a,...r}=i;const[o,l,c]=B(e,{...r,keyPrefix:t.keyPrefix}),u={...r,t:o,i18n:l,tReady:c};return t.withRef&&a?u.ref=a:!t.withRef&&a&&(u.forwardedRef=a),n.createElement(s,u)}i.displayName=`withI18nextTranslation(${g(s)})`,i.WrappedComponent=s;return t.withRef?n.forwardRef(((e,t)=>n.createElement(i,Object.assign({},e,{forwardedRef:t})))):i}}})); ++!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).ReactI18next={},e.React)}(this,(function(e,n){"use strict";function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var s=t({area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),i=/\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;function a(e){var n={type:"tag",name:"",voidElement:!1,attrs:{},children:[]},t=e.match(/<\/?([^\s]+?)[/\s>]/);if(t&&(n.name=t[1],(s[t[1]]||"/"===e.charAt(e.length-2))&&(n.voidElement=!0),n.name.startsWith("!--"))){var a=e.indexOf("--\x3e");return{type:"comment",comment:-1!==a?e.slice(4,a):""}}for(var r=new RegExp(i),o=null;null!==(o=r.exec(e));)if(o[0].trim())if(o[1]){var l=o[1].trim(),c=[l,""];l.indexOf("=")>-1&&(c=l.split("=")),n.attrs[c[0]]=c[1],r.lastIndex--}else o[2]&&(n.attrs[o[2]]=o[3].trim().substring(1,o[3].length-1));return n}var r=/<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g,o=/^\s*$/,l=Object.create(null);var c=function(e,n){n||(n={}),n.components||(n.components=l);var t,s=[],i=[],c=-1,u=!1;if(0!==e.indexOf("<")){var p=e.indexOf("<");s.push({type:"text",content:-1===p?e:e.substring(0,p)})}return e.replace(r,(function(r,l){if(u){if(r!=="")return;u=!1}var p,d="/"!==r.charAt(1),f=r.startsWith("\x3c!--"),h=l+r.length,m=e.charAt(h);if(f){var g=a(r);return c<0?(s.push(g),s):((p=i[c]).children.push(g),s)}if(d&&(c++,"tag"===(t=a(r)).type&&n.components[t.name]&&(t.type="component",u=!0),t.voidElement||u||!m||"<"===m||t.children.push({type:"text",content:e.slice(h,e.indexOf("<",h))}),0===c&&s.push(t),(p=i[c-1])&&p.children.push(t),i[c]=t),(!d||t.voidElement)&&(c>-1&&(t.voidElement||t.name===r.slice(2,-1))&&(c--,t=-1===c?s:i[c]),!u&&"<"!==m&&m)){p=-1===c?s:i[c].children;var y=e.indexOf("<",h),x=e.slice(h,-1===y?void 0:y);o.test(x)&&(x=" "),(y>-1&&c+p.length>=0||" "!==x)&&p.push({type:"text",content:x})}})),s};const u=function(){if(console?.warn){for(var e=arguments.length,n=new Array(e),t=0;t()=>{if(e.isInitialized)n();else{const t=()=>{setTimeout((()=>{e.off("initialized",t)}),0),n()};e.on("initialized",t)}},h=(e,n,t)=>{e.loadNamespaces(n,f(e,t))},m=(e,n,t,s)=>{y(t)&&(t=[t]),t.forEach((n=>{e.options.ns.indexOf(n)<0&&e.options.ns.push(n)})),e.loadLanguages(n,f(e,s))},g=e=>e.displayName||e.name||(y(e)&&e.length>0?e:"Unknown"),y=e=>"string"==typeof e,x=e=>"object"==typeof e&&null!==e,b=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,v={"&":"&","&":"&","<":"<","<":"<",">":">",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"},E=e=>v[e];let O={bindI18n:"languageChanged",bindI18nStore:"",transEmptyNodeValue:"",transSupportBasicHtmlNodes:!0,transWrapTextNodes:"",transKeepBasicHtmlNodesFor:["br","strong","i","p"],useSuspense:!0,unescape:e=>e.replace(b,E)};const N=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};O={...O,...e}},$=()=>O;let w;const I=e=>{w=e},k=()=>w,S=(e,n)=>{if(!e)return!1;const t=e.props?.children??e.children;return n?t.length>0:!!t},j=e=>{if(!e)return[];const n=e.props?.children??e.children;return e.props?.i18nIsDynamicList?R(n):n},R=e=>Array.isArray(e)?e:[e],T=(e,t)=>{if(!e)return"";let s="";const i=R(e),a=t?.transSupportBasicHtmlNodes?t.transKeepBasicHtmlNodesFor??[]:[];return i.forEach(((e,i)=>{if(y(e))s+=`${e}`;else if(n.isValidElement(e)){const{props:n,type:r}=e,o=Object.keys(n).length,l=a.indexOf(r)>-1,c=n.children;if(c||!l||o)if(!c&&(!l||o)||n.i18nIsDynamicList)s+=`<${i}>`;else if(l&&1===o&&y(c))s+=`<${r}>${c}`;else{const e=T(c,t);s+=`<${i}>${e}`}else s+=`<${r}/>`}else if(null===e)u("Trans: the passed in value is invalid - seems you passed in a null child.");else if(x(e)){const{format:n,...t}=e,i=Object.keys(t);if(1===i.length){const e=n?`${i[0]}, ${n}`:i[0];s+=`{{${e}}}`}else u("react-i18next: the passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.",e)}else u("Trans: the passed in value is invalid - seems you passed in a variable like {number} - please pass in variables for interpolation as full objects like {{number}}.",e)})),s},C=(e,t,s,i,a,r)=>{if(""===t)return[];const o=i.transKeepBasicHtmlNodesFor||[],l=t&&new RegExp(o.map((e=>`<${e}`)).join("|")).test(t);if(!e&&!l&&!r)return[t];const u={},p=e=>{R(e).forEach((e=>{y(e)||(S(e)?p(j(e)):x(e)&&!n.isValidElement(e)&&Object.assign(u,e))}))};p(e);const d=c(`<0>${t}`),f={...u,...a},h=(e,t,s)=>{const i=j(e),a=g(i,t.children,s);return(e=>Array.isArray(e)&&e.every(n.isValidElement))(i)&&0===a.length||e.props?.i18nIsDynamicList?i:a},m=(e,t,s,i,a)=>{e.dummy?(e.children=t,s.push(n.cloneElement(e,{key:i},a?void 0:t))):s.push(...n.Children.map([e],(e=>{const s={...e.props};return delete s.i18nIsDynamicList,n.createElement(e.type,{...s,key:i,ref:e.ref},a?null:t)})))},g=(t,a,c)=>{const u=R(t);return R(a).reduce(((t,a,p)=>{const d=a.children?.[0]?.content&&s.services.interpolator.interpolate(a.children[0].content,f,s.language);if("tag"===a.type){let r=u[parseInt(a.name,10)];1!==c.length||r||(r=c[0][a.name]),r||(r={});const b=0!==Object.keys(a.attrs).length?((e,n)=>{const t={...n};return t.props=Object.assign(e.props,n.props),t})({props:a.attrs},r):r,v=n.isValidElement(b),E=v&&S(a,!0)&&!a.voidElement,O=l&&x(b)&&b.dummy&&!v,N=x(e)&&Object.hasOwnProperty.call(e,a.name);if(y(b)){const e=s.services.interpolator.interpolate(b,f,s.language);t.push(e)}else if(S(b)||E){const e=h(b,a,c);m(b,e,t,p)}else if(O){const e=g(u,a.children,c);m(b,e,t,p)}else if(Number.isNaN(parseFloat(a.name)))if(N){const e=h(b,a,c);m(b,e,t,p,a.voidElement)}else if(i.transSupportBasicHtmlNodes&&o.indexOf(a.name)>-1)if(a.voidElement)t.push(n.createElement(a.name,{key:`${a.name}-${p}`}));else{const e=g(u,a.children,c);t.push(n.createElement(a.name,{key:`${a.name}-${p}`},e))}else if(a.voidElement)t.push(`<${a.name} />`);else{const e=g(u,a.children,c);t.push(`<${a.name}>${e}`)}else if(x(b)&&!v){const e=a.children[0]?d:null;e&&t.push(e)}else m(b,d,t,p,1!==a.children.length||!d)}else if("text"===a.type){const e=i.transWrapTextNodes,o=r?i.unescape(s.services.interpolator.interpolate(a.content,f,s.language)):s.services.interpolator.interpolate(a.content,f,s.language);e?t.push(n.createElement(e,{key:`${a.name}-${p}`},o)):t.push(o)}return t}),[])},b=g([{dummy:!0,children:e||[]}],d,R(e||[]));return j(b[0])};function P(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:f,t:h,shouldUnescape:m,...g}=e;const x=f||k();if(!x)return d("You will need to pass in an i18next instance by using i18nextReactModule"),t;const b=h||x.t.bind(x)||(e=>e),v={...$(),...x.options?.react};let E=p||b.ns||x.options?.defaultNS;E=y(E)?[E]:E||["translation"];const O=T(t,v),N=c||O||v.transEmptyNodeValue||a,{hashTransKey:w}=v,I=a||(w?w(O||N):O||N);x.options?.interpolation?.defaultVariables&&(l=l&&Object.keys(l).length>0?{...l,...x.options.interpolation.defaultVariables}:{...x.options.interpolation.defaultVariables});const S=l||void 0!==s||!t?o.interpolation:{interpolation:{...o.interpolation,prefix:"#$?",suffix:"?$#"}},j={...o,context:r||o.context,count:s,...l,...S,defaultValue:N,ns:E},R=I?b(I,j):N;u&&Object.keys(u).forEach((e=>{const t=u[e];"function"==typeof t.type||!t.props||!t.props.children||R.indexOf(`${e}/>`)<0&&R.indexOf(`${e} />`)<0||(u[e]=n.createElement((function(){return n.createElement(n.Fragment,null,t)})))}));const P=C(u||t,R,x,v,j,m),L=i??v.defaultTransParent;return L?n.createElement(L,g,P):P}const L={type:"3rdParty",init(e){N(e.options.react),I(e)}},A=n.createContext();class V{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach((e=>{this.usedNamespaces[e]=this.usedNamespaces[e]??!0}))}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}}const z=e=>async n=>({...await(e.getInitialProps?.(n))??{},...F()}),F=()=>{const e=k(),n=e.reportNamespaces?.getUsedNamespaces()??[],t={},s={};return e.languages.forEach((t=>{s[t]={},n.forEach((n=>{s[t][n]=e.getResourceBundle(t,n)||{}}))})),t.initialI18nStore=s,t.initialLanguage=e.language,t};const U=(e,n,t,s)=>e.getFixedT(n,t,s),B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{i18n:s}=t,{i18n:i,defaultNS:a}=n.useContext(A)||{},r=s||i||k();if(r&&!r.reportNamespaces&&(r.reportNamespaces=new V),!r){d("You will need to pass in an i18next instance by using initReactI18next");const e=(e,n)=>y(n)?n:x(n)&&y(n.defaultValue)?n.defaultValue:Array.isArray(e)?e[e.length-1]:e,n=[e,{},!1];return n.t=e,n.i18n={},n.ready=!1,n}r.options.react?.wait&&d("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");const o={...$(),...r.options.react,...t},{useSuspense:l,keyPrefix:c}=o;let u=e||a||r.options?.defaultNS;u=y(u)?[u]:u||["translation"],r.reportNamespaces.addUsedNamespaces?.(u);const p=(r.isInitialized||r.initializedStoreOnce)&&u.every((e=>function(e,n){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n.languages&&n.languages.length?n.hasLoadedNamespace(e,{lng:t.lng,precheck:(n,s)=>{if(t.bindI18n?.indexOf("languageChanging")>-1&&n.services.backendConnector.backend&&n.isLanguageChangingTo&&!s(n.isLanguageChangingTo,e))return!1}}):(d("i18n.languages were undefined or empty",n.languages),!0)}(e,r,o))),f=((e,t,s,i)=>n.useCallback(U(e,t,s,i),[e,t,s,i]))(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),g=()=>f,b=()=>U(r,t.lng||null,"fallback"===o.nsMode?u:u[0],c),[v,E]=n.useState(g);let O=u.join();t.lng&&(O=`${t.lng}${O}`);const N=((e,t)=>{const s=n.useRef();return n.useEffect((()=>{s.current=e}),[e,t]),s.current})(O),w=n.useRef(!0);n.useEffect((()=>{const{bindI18n:e,bindI18nStore:n}=o;w.current=!0,p||l||(t.lng?m(r,t.lng,u,(()=>{w.current&&E(b)})):h(r,u,(()=>{w.current&&E(b)}))),p&&N&&N!==O&&w.current&&E(b);const s=()=>{w.current&&E(b)};return e&&r?.on(e,s),n&&r?.store.on(n,s),()=>{w.current=!1,r&&e?.split(" ").forEach((e=>r.off(e,s))),n&&r&&n.split(" ").forEach((e=>r.store.off(e,s)))}}),[r,O]),n.useEffect((()=>{w.current&&p&&E(g)}),[r,c,p]);const I=[v,r,p];if(I.t=v,I.i18n=r,I.ready=p,p)return I;if(!p&&!l)return I;throw new Promise((e=>{t.lng?m(r,t.lng,u,(()=>e())):h(r,u,(()=>e()))}))};const D=function(e,t){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{i18n:i}=s,{i18n:a}=n.useContext(A)||{},r=i||a||k();r.options?.isClone||(e&&!r.initializedStoreOnce&&(r.services.resourceStore.data=e,r.options.ns=Object.values(e).reduce(((e,n)=>(Object.keys(n).forEach((n=>{e.indexOf(n)<0&&e.push(n)})),e)),r.options.ns),r.initializedStoreOnce=!0,r.isInitialized=!0),t&&!r.initializedLanguageOnce&&(r.changeLanguage(t),r.initializedLanguageOnce=!0))};e.I18nContext=A,e.I18nextProvider=function(e){let{i18n:t,defaultNS:s,children:i}=e;const a=n.useMemo((()=>({i18n:t,defaultNS:s})),[t,s]);return n.createElement(A.Provider,{value:a},i)},e.Trans=function(e){let{children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o={},values:l,defaults:c,components:u,ns:p,i18n:d,t:f,shouldUnescape:h,...m}=e;const{i18n:g,defaultNS:y}=n.useContext(A)||{},x=d||g||k(),b=f||x?.t.bind(x);return P({children:t,count:s,parent:i,i18nKey:a,context:r,tOptions:o,values:l,defaults:c,components:u,ns:p||b?.ns||y||x?.options?.defaultNS,i18n:x,t:f,shouldUnescape:h,...m})},e.TransWithoutContext=P,e.Translation=e=>{let{ns:n,children:t,...s}=e;const[i,a,r]=B(n,s);return t(i,{i18n:a,lng:a.language},r)},e.composeInitialProps=z,e.date=()=>"",e.getDefaults=$,e.getI18n=k,e.getInitialProps=F,e.initReactI18next=L,e.number=()=>"",e.plural=()=>"",e.select=()=>"",e.selectOrdinal=()=>"",e.setDefaults=N,e.setI18n=I,e.time=()=>"",e.useSSR=D,e.useTranslation=B,e.withSSR=()=>function(e){function t(t){let{initialI18nStore:s,initialLanguage:i,...a}=t;return D(s,i),n.createElement(e,{...a})}return t.getInitialProps=z(e),t.displayName=`withI18nextSSR(${g(e)})`,t.WrappedComponent=e,t},e.withTranslation=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return function(s){function i(i){let{forwardedRef:a,...r}=i;const[o,l,c]=B(e,{...r,keyPrefix:t.keyPrefix}),u={...r,t:o,i18n:l,tReady:c};return t.withRef&&a?u.ref=a:!t.withRef&&a&&(u.forwardedRef=a),n.createElement(s,u)}i.displayName=`withI18nextTranslation(${g(s)})`,i.WrappedComponent=s;return t.withRef?n.forwardRef(((e,t)=>n.createElement(i,Object.assign({},e,{forwardedRef:t})))):i}}})); +diff --git a/src/context.js b/src/context.js +index 167af9c50f47e34f7473df03bb2bb1e369725934..9b9a5570b0b765c9d7809f912dba2db759ebb68d 100644 +--- a/src/context.js ++++ b/src/context.js +@@ -14,7 +14,7 @@ export class ReportNamespaces { + + addUsedNamespaces(namespaces) { + namespaces.forEach((ns) => { +- this.usedNamespaces[ns] ??= true; ++ this.usedNamespaces[ns] = this.usedNamespaces[ns] ?? true; + }); + } + diff --git a/.yarn/patches/typia-npm-5.3.3-21d3e18463.patch b/.yarn/patches/typia-npm-6.9.0-2fd4d85f25.patch similarity index 64% rename from .yarn/patches/typia-npm-5.3.3-21d3e18463.patch rename to .yarn/patches/typia-npm-6.9.0-2fd4d85f25.patch index 1487a9f4712d..22a02fa15957 100644 --- a/.yarn/patches/typia-npm-5.3.3-21d3e18463.patch +++ b/.yarn/patches/typia-npm-6.9.0-2fd4d85f25.patch @@ -1,22 +1,22 @@ diff --git a/lib/factories/internal/metadata/iterate_metadata_intersection.js b/lib/factories/internal/metadata/iterate_metadata_intersection.js -index 260670b8ea37b63dcacadeffa26450f81087c90e..f07b44b16099d896ab40c46f03df86ee2f2c1a90 100644 +index da05ef3ac6f397d68b8fa10285f62d1794c57579..0f2b9b26e01cebd0b9c04a2db8857f911cfbaf57 100644 --- a/lib/factories/internal/metadata/iterate_metadata_intersection.js +++ b/lib/factories/internal/metadata/iterate_metadata_intersection.js -@@ -247,7 +247,7 @@ var iterate_metadata_intersection = function (checker) { +@@ -181,7 +181,7 @@ var iterate_metadata_intersection = function (checker) { var tags = MetadataTypeTagFactory_1.MetadataTypeTagFactory.analyze(errors)(target)(objects.map(function (om) { return om.objects; }).flat(), explore); if (tags.length) if (target === "array") - meta.arrays.at(-1).tags.push(tags); + meta.arrays.slice(-1)[0].tags.push(tags); - else if (booleanLiteral === null) + else if (atomics.size) meta.atomics.find(function (a) { return a.type === target; }).tags.push(tags); - else { + else if (constants.length) { diff --git a/lib/programmers/CheckerProgrammer.js b/lib/programmers/CheckerProgrammer.js -index bbec09f22798d144b96f59bb946e7e32e3438c05..dc13cb47b72358b8e6165b768cff2360db2bd617 100644 +index 662e6ff51c91598229c88f691b7ce07200957167..4028a50cc92ee293e98b13822293d0fb35c64c1f 100644 --- a/lib/programmers/CheckerProgrammer.js +++ b/lib/programmers/CheckerProgrammer.js -@@ -458,8 +458,8 @@ var CheckerProgrammer; - ? "".concat(explore.postfix.slice(0, -1), "[").concat(index, "]\"") +@@ -529,8 +529,8 @@ var CheckerProgrammer; + ? "".concat((0, postfix_of_tuple_1.postfix_of_tuple)(explore.postfix), "[").concat(index, "]\"") : "\"[".concat(index, "]\"") })); }); - var rest = tuple.elements.length && tuple.elements.at(-1).rest !== null @@ -27,7 +27,7 @@ index bbec09f22798d144b96f59bb946e7e32e3438c05..dc13cb47b72358b8e6165b768cff2360 var arrayLength = typescript_1.default.factory.createPropertyAccessExpression(input, "length"); return config.combiner(explore)("and")(input, __spreadArray(__spreadArray(__spreadArray([], __read((rest === null diff --git a/lib/programmers/TypiaProgrammer.js b/lib/programmers/TypiaProgrammer.js -index c75ab1bc077b788e36c32834ea4916c22df34500..99452220e2dd090d4d562296e26fb266faa617f9 100644 +index db244cb5e40fd68e15b7a06936b0f92802e5e4c9..c2f64885f67cc543c62b0b09e141c88684f99253 100644 --- a/lib/programmers/TypiaProgrammer.js +++ b/lib/programmers/TypiaProgrammer.js @@ -165,7 +165,7 @@ var TypiaProgrammer; @@ -39,34 +39,53 @@ index c75ab1bc077b788e36c32834ea4916c22df34500..99452220e2dd090d4d562296e26fb266 })() : [0, 0], 2), line = _k[0], pos = _k[1]; console.error("".concat(file, ":").concat(line, ":").concat(pos, " - ").concat(category, " TS").concat(diag.code, ": ").concat(diag.messageText)); -diff --git a/lib/programmers/internal/application_tuple.js b/lib/programmers/internal/application_tuple.js -index 5c1853a3a1692f95fe702bdae9813e958f54ac4c..04ca996c2ffe72d1cd5969e45a38e91937b975b1 100644 ---- a/lib/programmers/internal/application_tuple.js -+++ b/lib/programmers/internal/application_tuple.js -@@ -22,15 +22,15 @@ var application_tuple = function (options) { - var schema = __assign(__assign({ type: "array", items: tuple.type.elements.map(function (meta, i) { - var _a; - return (0, application_schema_1.application_schema)(options)(false)(components)((_a = meta.rest) !== null && _a !== void 0 ? _a : meta)(__assign(__assign({}, attribute), { "x-typia-rest": i === tuple.type.elements.length - 1 && meta.rest !== null, "x-typia-required": meta.required, "x-typia-optional": meta.optional })); -- }) }, attribute), { minItems: !!((_a = tuple.type.elements.at(-1)) === null || _a === void 0 ? void 0 : _a.rest) -+ }) }, attribute), { minItems: !!((_a = tuple.type.elements.slice(-1)[0]) === null || _a === void 0 ? void 0 : _a.rest) - ? tuple.type.elements.length - 1 -- : tuple.type.elements.filter(function (x) { return !x.optional; }).length, maxItems: !!((_b = tuple.type.elements.at(-1)) === null || _b === void 0 ? void 0 : _b.rest) -+ : tuple.type.elements.filter(function (x) { return !x.optional; }).length, maxItems: !!((_b = tuple.type.elements.slice(-1)[0]) === null || _b === void 0 ? void 0 : _b.rest) - ? undefined - : tuple.type.elements.length }); - if (options.purpose === "ajv") - if (tuple.type.elements.length === 0) - return schema; -- else if (!((_c = tuple.type.elements.at(-1)) === null || _c === void 0 ? void 0 : _c.rest)) -+ else if (!((_c = tuple.type.elements.slice(-1)[0]) === null || _c === void 0 ? void 0 : _c.rest)) - return schema; - var wrapper = __assign(__assign({}, schema), { items: (0, application_schema_1.application_schema)(options)(false)(components)(tuple.type.elements.reduce(function (x, y) { var _a, _b; return Metadata_1.Metadata.merge((_a = x.rest) !== null && _a !== void 0 ? _a : x, (_b = y.rest) !== null && _b !== void 0 ? _b : y); }, Metadata_1.Metadata.initialize()))(tuple.type.recursive ? {} : attribute), "x-typia-tuple": schema, minItems: schema.minItems, maxItems: schema.maxItems }); - return wrapper; +diff --git a/lib/programmers/internal/application_v30_tuple.js b/lib/programmers/internal/application_v30_tuple.js +index 94e8827fd94df3792c1d7b2cdacac604175ba8ca..c9a3a1bcdd978b97a080ed2c467188d808d10c8f 100644 +--- a/lib/programmers/internal/application_v30_tuple.js ++++ b/lib/programmers/internal/application_v30_tuple.js +@@ -21,9 +21,9 @@ var application_v30_tuple = function (components) { + return function (tuple) { + return function (attribute) { + var _a, _b; +- return (__assign(__assign({}, attribute), { type: "array", items: (0, application_v30_schema_1.application_v30_schema)(false)(components)(tuple.type.recursive ? {} : attribute)(tuple.type.elements.reduce(function (x, y) { var _a, _b; return Metadata_1.Metadata.merge((_a = x.rest) !== null && _a !== void 0 ? _a : x, (_b = y.rest) !== null && _b !== void 0 ? _b : y); }, Metadata_1.Metadata.initialize())), minItems: !!((_a = tuple.type.elements.at(-1)) === null || _a === void 0 ? void 0 : _a.rest) ++ return (__assign(__assign({}, attribute), { type: "array", items: (0, application_v30_schema_1.application_v30_schema)(false)(components)(tuple.type.recursive ? {} : attribute)(tuple.type.elements.reduce(function (x, y) { var _a, _b; return Metadata_1.Metadata.merge((_a = x.rest) !== null && _a !== void 0 ? _a : x, (_b = y.rest) !== null && _b !== void 0 ? _b : y); }, Metadata_1.Metadata.initialize())), minItems: !!((_a = tuple.type.elements.slice(-1)[0]) === null || _a === void 0 ? void 0 : _a.rest) + ? tuple.type.elements.length - 1 +- : tuple.type.elements.filter(function (x) { return !x.optional; }).length, maxItems: !!((_b = tuple.type.elements.at(-1)) === null || _b === void 0 ? void 0 : _b.rest) ++ : tuple.type.elements.filter(function (x) { return !x.optional; }).length, maxItems: !!((_b = tuple.type.elements.slice(-1)[0]) === null || _b === void 0 ? void 0 : _b.rest) + ? undefined + : tuple.type.elements.length })); + }; +diff --git a/lib/programmers/internal/application_v31_tuple.js b/lib/programmers/internal/application_v31_tuple.js +index af7bbbe1536244eec93d7311e289fc8b34245c0f..e7f126b72a3480f761f57e4d43826a26b8b06e3f 100644 +--- a/lib/programmers/internal/application_v31_tuple.js ++++ b/lib/programmers/internal/application_v31_tuple.js +@@ -7,7 +7,7 @@ exports.application_v31_tuple = void 0; + var application_v31_tuple = function (generator) { + return function (tuple) { + var _a, _b; +- var tail = (_b = (_a = tuple.type.elements.at(-1)) === null || _a === void 0 ? void 0 : _a.rest) !== null && _b !== void 0 ? _b : null; ++ var tail = (_b = (_a = tuple.type.elements.slice(-1)[0]) === null || _a === void 0 ? void 0 : _a.rest) !== null && _b !== void 0 ? _b : null; + var prefixItems = tuple.type.isRest() + ? tuple.type.elements.slice(0, -1) + : tuple.type.elements; +diff --git a/lib/programmers/internal/decode_union_object.js b/lib/programmers/internal/decode_union_object.js +index c283bdfdcfb99f26954e6d76d35ccef4f78ddcee..7cdef1aeec80238014cf9457ca4351f02e99b638 100644 +--- a/lib/programmers/internal/decode_union_object.js ++++ b/lib/programmers/internal/decode_union_object.js +@@ -72,7 +72,7 @@ var iterate = function (escaper) { + ? typescript_1.default.factory.createIfStatement(b.condition, typescript_1.default.factory.createReturnStatement(b.value), undefined) + : typescript_1.default.factory.createReturnStatement(b.value); + }); +- if (branches.at(-1).condition !== null) ++ if (branches.slice(-1)[0].condition !== null) + statements.push(escaper(input, expected)); + return typescript_1.default.factory.createBlock(statements, true); + }; diff --git a/lib/programmers/json/JsonStringifyProgrammer.js b/lib/programmers/json/JsonStringifyProgrammer.js -index ce0ae787164f7eba68ef35b05232b4b94ad8e7d7..8f70cfc8c8e9d82cd1ec5004ca5637487f57b3bc 100644 +index 77f2812cb509f5b9093dc311f8ae8558e4469a74..54a6c4e22f9cfa87cd30f8ade50e6cb8ccda9957 100644 --- a/lib/programmers/json/JsonStringifyProgrammer.js +++ b/lib/programmers/json/JsonStringifyProgrammer.js -@@ -424,10 +424,10 @@ var JsonStringifyProgrammer; +@@ -469,10 +469,10 @@ var JsonStringifyProgrammer; var rest = (function () { if (tuple.elements.length === 0) return null; @@ -80,10 +99,10 @@ index ce0ae787164f7eba68ef35b05232b4b94ad8e7d7..8f70cfc8c8e9d82cd1ec5004ca563748 })(); return StringifyJoinder_1.StringifyJoiner.tuple(children, rest); diff --git a/lib/programmers/misc/MiscCloneProgrammer.js b/lib/programmers/misc/MiscCloneProgrammer.js -index 3db6bc92637284468c5fe47ef59f51a9b41d06eb..0b3fa9deaaadf28d4f348225c0d44f49700c1bca 100644 +index 38bec89fba26db7a9c3ee71abd49086692c7e78b..9b9d7a9b0606da78224ce083bcbd7e07cd6ce82e 100644 --- a/lib/programmers/misc/MiscCloneProgrammer.js +++ b/lib/programmers/misc/MiscCloneProgrammer.js -@@ -291,11 +291,11 @@ var MiscCloneProgrammer; +@@ -318,11 +318,11 @@ var MiscCloneProgrammer; var rest = (function () { if (tuple.elements.length === 0) return null; @@ -98,10 +117,10 @@ index 3db6bc92637284468c5fe47ef59f51a9b41d06eb..0b3fa9deaaadf28d4f348225c0d44f49 return CloneJoiner_1.CloneJoiner.tuple(children, rest); }; diff --git a/lib/programmers/misc/MiscPruneProgrammer.js b/lib/programmers/misc/MiscPruneProgrammer.js -index 8440aaba9e449dae2468e96dfd7035ac7e170cfc..e97ceec78427b7eed08db23cc4775fdb10c6b2ff 100644 +index d4a9c66c0b5508b4ffdc4b7684364a9d748f6508..33f9356bab69faffadb8c2b49997d2c3ca429810 100644 --- a/lib/programmers/misc/MiscPruneProgrammer.js +++ b/lib/programmers/misc/MiscPruneProgrammer.js -@@ -272,11 +272,11 @@ var MiscPruneProgrammer; +@@ -305,11 +305,11 @@ var MiscPruneProgrammer; var rest = (function () { if (tuple.elements.length === 0) return null; @@ -116,10 +135,10 @@ index 8440aaba9e449dae2468e96dfd7035ac7e170cfc..e97ceec78427b7eed08db23cc4775fdb return PruneJoiner_1.PruneJoiner.tuple(children, rest); }; diff --git a/lib/programmers/notations/NotationGeneralProgrammer.js b/lib/programmers/notations/NotationGeneralProgrammer.js -index 6e0b582a802180d7671c00b999469e7e59193b30..f11cc1d523875a040d3e27ce9a850b083c5d0275 100644 +index dfe64a21a5a81a7e887ab5ae02d73e6d3bdddb03..aa4b6070ed6a83c49018384e3a0c1dbd4a7c540b 100644 --- a/lib/programmers/notations/NotationGeneralProgrammer.js +++ b/lib/programmers/notations/NotationGeneralProgrammer.js -@@ -301,11 +301,11 @@ var NotationGeneralProgrammer; +@@ -328,11 +328,11 @@ var NotationGeneralProgrammer; var rest = (function () { if (tuple.elements.length === 0) return null; @@ -134,36 +153,36 @@ index 6e0b582a802180d7671c00b999469e7e59193b30..f11cc1d523875a040d3e27ce9a850b08 return NotationJoiner_1.NotationJoiner.tuple(children, rest); }; diff --git a/lib/transformers/CallExpressionTransformer.js b/lib/transformers/CallExpressionTransformer.js -index 2c5a23879d171ee271ebf6857dc9c65ec29c0ea7..96a40845614f6c54fe8e4ebc48a7d8efeba52a41 100644 +index f7be23d98552526d8c2348ba0f3d32b8d31e522b..133af0caa37ffcf7330811ef7bda79e75b9ecd89 100644 --- a/lib/transformers/CallExpressionTransformer.js +++ b/lib/transformers/CallExpressionTransformer.js -@@ -101,7 +101,7 @@ var CallExpressionTransformer; - var location = path_1.default.resolve(declaration.getSourceFile().fileName); - if (isTarget(location) === false) - return expression; +@@ -129,7 +129,7 @@ var CallExpressionTransformer; + // TRANSFORMATION + //---- + // FUNCTION NAME - var module = location.split(path_1.default.sep).at(-1).split(".")[0]; + var module = location.split(path_1.default.sep).slice(-1)[0].split(".")[0]; var name = project.checker.getTypeAtLocation(declaration).symbol.name; + // FIND TRANSFORMER var functor = (_b = FUNCTORS[module]) === null || _b === void 0 ? void 0 : _b[name]; - if (functor === undefined) diff --git a/src/factories/internal/metadata/iterate_metadata_intersection.ts b/src/factories/internal/metadata/iterate_metadata_intersection.ts -index f46caa25c987092597073e046ae3b9e8130bd994..1eedd727c74f173a5b98a9572b865e058885811d 100644 +index c4d93a961385c09abb6f448896ad4636b648703b..99b1547e6a501caf28a452390fe721cd47dc2ca1 100644 --- a/src/factories/internal/metadata/iterate_metadata_intersection.ts +++ b/src/factories/internal/metadata/iterate_metadata_intersection.ts -@@ -214,7 +214,7 @@ export const iterate_metadata_intersection = +@@ -188,7 +188,7 @@ export const iterate_metadata_intersection = target, )(objects.map((om) => om.objects).flat(), explore); if (tags.length) - if (target === "array") meta.arrays.at(-1)!.tags.push(tags); + if (target === "array") meta.arrays.slice(-1)[0]!.tags.push(tags); - else if (booleanLiteral === null) + else if (atomics.size) meta.atomics.find((a) => a.type === target)!.tags.push(tags); - else { + else if (constants.length) { diff --git a/src/programmers/CheckerProgrammer.ts b/src/programmers/CheckerProgrammer.ts -index 892748b80755b89d1449f4d515aa3166534c6b19..8cb5ce35fe6f918545c82066f0583dead2661c89 100644 +index 40f46e8212a5173e19e0214422086655de908933..18a1a2371cac697e1b621a8fbc6b090b691e3072 100644 --- a/src/programmers/CheckerProgrammer.ts +++ b/src/programmers/CheckerProgrammer.ts -@@ -702,14 +702,14 @@ export namespace CheckerProgrammer { +@@ -785,14 +785,14 @@ export namespace CheckerProgrammer { ), ); const rest: ts.Expression | null = @@ -181,47 +200,66 @@ index 892748b80755b89d1449f4d515aa3166534c6b19..8cb5ce35fe6f918545c82066f0583dea ...explore, start: tuple.elements.length - 1, diff --git a/src/programmers/TypiaProgrammer.ts b/src/programmers/TypiaProgrammer.ts -index e01eccf62eccd73e1f0720db897f539256a6bbc1..cae5eb6fc702d359d4886acefdb68d42691edf97 100644 +index 67d9fc138483832aae43574fd2753bbe40a27d14..4397517aa620a8733c51917eebfa467f2922bf6d 100644 --- a/src/programmers/TypiaProgrammer.ts +++ b/src/programmers/TypiaProgrammer.ts @@ -101,7 +101,7 @@ export namespace TypiaProgrammer { - .file!.text.substring(0, diag.start) - .split("\n"); - if (lines.length === 0) return [0, 0]; -- return [lines.length, lines.at(-1)!.length + 1]; -+ return [lines.length, lines.slice(-1)[0]!.length + 1]; - })() - : [0, 0]; - console.error( -diff --git a/src/programmers/internal/application_tuple.ts b/src/programmers/internal/application_tuple.ts -index 5e10b9051e4a846f298aa8f086109e8d6bb38bf9..a8e24d5c2a2a4a4d5d1dc49eb45b4784654a4b66 100644 ---- a/src/programmers/internal/application_tuple.ts -+++ b/src/programmers/internal/application_tuple.ts -@@ -28,16 +28,16 @@ export const application_tuple = - }), + .file!.text.substring(0, diag.start) + .split("\n"); + if (lines.length === 0) return [0, 0]; +- return [lines.length, lines.at(-1)!.length + 1]; ++ return [lines.length, lines.slice(-1)[0]!.length + 1]; + })() + : [0, 0]; + console.error( +diff --git a/src/programmers/internal/application_v30_tuple.ts b/src/programmers/internal/application_v30_tuple.ts +index 0431273d439c6069a1bb5fbfd7808c5e8d0ea255..4b27cfc3ebde7ce9dcdb5161d34e70004a327b1e 100644 +--- a/src/programmers/internal/application_v30_tuple.ts ++++ b/src/programmers/internal/application_v30_tuple.ts +@@ -24,10 +24,10 @@ export const application_v30_tuple = + Metadata.initialize(), ), - ...attribute, -- minItems: !!tuple.type.elements.at(-1)?.rest -+ minItems: !!tuple.type.elements.slice(-1)[0]?.rest - ? tuple.type.elements.length - 1 - : tuple.type.elements.filter((x) => !x.optional).length, -- maxItems: !!tuple.type.elements.at(-1)?.rest -+ maxItems: !!tuple.type.elements.slice(-1)[0]?.rest - ? undefined - : tuple.type.elements.length, - }; - if (options.purpose === "ajv") - if (tuple.type.elements.length === 0) return schema; -- else if (!tuple.type.elements.at(-1)?.rest) return schema; -+ else if (!tuple.type.elements.slice(-1)[0]?.rest) return schema; - - const wrapper: IJsonSchema.IArray = { - ...schema, + ), +- minItems: !!tuple.type.elements.at(-1)?.rest ++ minItems: !!tuple.type.elements.slice(-1)[0]?.rest + ? tuple.type.elements.length - 1 + : tuple.type.elements.filter((x) => !x.optional).length, +- maxItems: !!tuple.type.elements.at(-1)?.rest ++ maxItems: !!tuple.type.elements.slice(-1)[0]?.rest + ? undefined! + : tuple.type.elements.length, + }); +diff --git a/src/programmers/internal/application_v31_tuple.ts b/src/programmers/internal/application_v31_tuple.ts +index 426abad3423de706c368b35b2cf6b4845f6f3d91..be15ec0fda1725bf1ea882c9901e0f837c052fde 100644 +--- a/src/programmers/internal/application_v31_tuple.ts ++++ b/src/programmers/internal/application_v31_tuple.ts +@@ -9,7 +9,7 @@ import { MetadataTuple } from "../../schemas/metadata/MetadataTuple"; + export const application_v31_tuple = + (generator: (meta: Metadata) => OpenApi.IJsonSchema) => + (tuple: MetadataTuple): OpenApi.IJsonSchema.ITuple => { +- const tail: Metadata | null = tuple.type.elements.at(-1)?.rest ?? null; ++ const tail: Metadata | null = tuple.type.elements.slice(-1)[0]?.rest ?? null; + const prefixItems: Metadata[] = tuple.type.isRest() + ? tuple.type.elements.slice(0, -1) + : tuple.type.elements; +diff --git a/src/programmers/internal/decode_union_object.ts b/src/programmers/internal/decode_union_object.ts +index f60bf69f91f35dee2efc807475fd6eca71aab052..ef8d51160f7eb9853ec0c827827aaa6c65db1587 100644 +--- a/src/programmers/internal/decode_union_object.ts ++++ b/src/programmers/internal/decode_union_object.ts +@@ -87,7 +87,7 @@ const iterate = + ) + : ts.factory.createReturnStatement(b.value), + ); +- if (branches.at(-1)!.condition !== null) ++ if (branches.slice(-1)[0]!.condition !== null) + statements.push(escaper(input, expected)); + return ts.factory.createBlock(statements, true); + }; diff --git a/src/programmers/json/JsonStringifyProgrammer.ts b/src/programmers/json/JsonStringifyProgrammer.ts -index c317cec2c78e984a6e64c7bf287d0c67e530e309..5974830c62dbd2b865aa2e64e2e757283258d872 100644 +index 558e5bf4b79f5cc28791ba0f5e40ebe6599361ce..c43d77b5d15343af90f7362d1757fc758659d5c9 100644 --- a/src/programmers/json/JsonStringifyProgrammer.ts +++ b/src/programmers/json/JsonStringifyProgrammer.ts -@@ -543,7 +543,7 @@ export namespace JsonStringifyProgrammer { +@@ -560,7 +560,7 @@ export namespace JsonStringifyProgrammer { ); const rest = (() => { if (tuple.elements.length === 0) return null; @@ -230,7 +268,7 @@ index c317cec2c78e984a6e64c7bf287d0c67e530e309..5974830c62dbd2b865aa2e64e2e75728 if (last.rest === null) return null; const code = decode(project)(config)(importer)( -@@ -552,7 +552,7 @@ export namespace JsonStringifyProgrammer { +@@ -569,7 +569,7 @@ export namespace JsonStringifyProgrammer { undefined, [ExpressionFactory.number(tuple.elements.length - 1)], ), @@ -240,10 +278,10 @@ index c317cec2c78e984a6e64c7bf287d0c67e530e309..5974830c62dbd2b865aa2e64e2e75728 ...explore, start: tuple.elements.length - 1, diff --git a/src/programmers/misc/MiscCloneProgrammer.ts b/src/programmers/misc/MiscCloneProgrammer.ts -index 94d768b0a0738c0caccd711671351d1f22fa3848..739fd5f2baf4d2eb8e9dd65d73179242a0244707 100644 +index c8c9cb90085eff7dd68ac332a362674ca33d93d8..76a810816bc4ad57ff5e1787a33cd7f01471ffbe 100644 --- a/src/programmers/misc/MiscCloneProgrammer.ts +++ b/src/programmers/misc/MiscCloneProgrammer.ts -@@ -343,7 +343,7 @@ export namespace MiscCloneProgrammer { +@@ -395,7 +395,7 @@ export namespace MiscCloneProgrammer { const rest = (() => { if (tuple.elements.length === 0) return null; @@ -252,7 +290,7 @@ index 94d768b0a0738c0caccd711671351d1f22fa3848..739fd5f2baf4d2eb8e9dd65d73179242 const rest: Metadata | null = last.rest; if (rest === null) return null; -@@ -353,7 +353,7 @@ export namespace MiscCloneProgrammer { +@@ -405,7 +405,7 @@ export namespace MiscCloneProgrammer { undefined, [ExpressionFactory.number(tuple.elements.length - 1)], ), @@ -262,10 +300,10 @@ index 94d768b0a0738c0caccd711671351d1f22fa3848..739fd5f2baf4d2eb8e9dd65d73179242 ...explore, start: tuple.elements.length - 1, diff --git a/src/programmers/misc/MiscPruneProgrammer.ts b/src/programmers/misc/MiscPruneProgrammer.ts -index ed1465267066e382ae6696a25a806c2489597593..661f3cd93ae66070c978bd3e8d2b8db07189fe47 100644 +index 1051204a00041ddfb39da9de77b35ab8b44b75ee..73db30fdc06955f43dc2f563cbfc174e50a1bc6f 100644 --- a/src/programmers/misc/MiscPruneProgrammer.ts +++ b/src/programmers/misc/MiscPruneProgrammer.ts -@@ -310,7 +310,7 @@ export namespace MiscPruneProgrammer { +@@ -347,7 +347,7 @@ export namespace MiscPruneProgrammer { const rest = (() => { if (tuple.elements.length === 0) return null; @@ -274,7 +312,7 @@ index ed1465267066e382ae6696a25a806c2489597593..661f3cd93ae66070c978bd3e8d2b8db0 const rest: Metadata | null = last.rest; if (rest === null || filter(rest) === false) return null; -@@ -320,7 +320,7 @@ export namespace MiscPruneProgrammer { +@@ -357,7 +357,7 @@ export namespace MiscPruneProgrammer { undefined, [ExpressionFactory.number(tuple.elements.length - 1)], ), @@ -284,10 +322,10 @@ index ed1465267066e382ae6696a25a806c2489597593..661f3cd93ae66070c978bd3e8d2b8db0 ...explore, start: tuple.elements.length - 1, diff --git a/src/programmers/notations/NotationGeneralProgrammer.ts b/src/programmers/notations/NotationGeneralProgrammer.ts -index bd49b1e34002b1a1ec4f5444a8f91fa0ab794360..71d676de290986045910602ab10c6ef09a19c07d 100644 +index d8b2e22a8f9f3bfcb8f3e7987edaa7bf19a48720..96632214cb02b1534dc1987a0f892a3bf41e892f 100644 --- a/src/programmers/notations/NotationGeneralProgrammer.ts +++ b/src/programmers/notations/NotationGeneralProgrammer.ts -@@ -353,7 +353,7 @@ export namespace NotationGeneralProgrammer { +@@ -411,7 +411,7 @@ export namespace NotationGeneralProgrammer { const rest = (() => { if (tuple.elements.length === 0) return null; @@ -296,7 +334,7 @@ index bd49b1e34002b1a1ec4f5444a8f91fa0ab794360..71d676de290986045910602ab10c6ef0 const rest: Metadata | null = last.rest; if (rest === null) return null; -@@ -363,7 +363,7 @@ export namespace NotationGeneralProgrammer { +@@ -421,7 +421,7 @@ export namespace NotationGeneralProgrammer { undefined, [ExpressionFactory.number(tuple.elements.length - 1)], ), @@ -306,15 +344,15 @@ index bd49b1e34002b1a1ec4f5444a8f91fa0ab794360..71d676de290986045910602ab10c6ef0 ...explore, start: tuple.elements.length - 1, diff --git a/src/transformers/CallExpressionTransformer.ts b/src/transformers/CallExpressionTransformer.ts -index c58a1b143ce4f204bb249a4858c9d16a26f97408..9e9ffcf73e4c01aa6ac8c213669fdcd50e0181b9 100644 +index 99a4604f1a1944336b4d93dc61390269c346c243..c31ba72f1d206c441d6c237314a1c4d5c808dd09 100644 --- a/src/transformers/CallExpressionTransformer.ts +++ b/src/transformers/CallExpressionTransformer.ts -@@ -111,7 +111,7 @@ export namespace CallExpressionTransformer { - // TRANSFORMATION - //---- - // FUNCTION NAME -- const module: string = location.split(path.sep).at(-1)!.split(".")[0]!; -+ const module: string = location.split(path.sep).slice(-1)[0]!.split(".")[0]!; - const { name } = project.checker.getTypeAtLocation(declaration).symbol; - - // FIND TRANSFORMER +@@ -131,7 +131,7 @@ export namespace CallExpressionTransformer { + // TRANSFORMATION + //---- + // FUNCTION NAME +- const module: string = location.split(path.sep).at(-1)!.split(".")[0]!; ++ const module: string = location.split(path.sep).slice(-1)[0]!.split(".")[0]!; + const { name } = project.checker.getTypeAtLocation(declaration).symbol; + + // FIND TRANSFORMER diff --git a/README.md b/README.md index 56e38c111e97..6461ad602516 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Free for 30 days. Afterward, choose between continuing to host on our secure clo You can follow these instructions to setup a dev environment: - Install **Node 14.x (LTS)** either [manually](https://nodejs.org/dist/latest-v14.x/) or using a tool like [nvm](https://github.com/creationix/nvm) or [volta](https://volta.sh/) (recommended) -- Install **Meteor** ([version here](apps/meteor/.meteor/release)): https://www.meteor.com/developers/install +- Install **Meteor** ([version here](apps/meteor/.meteor/release)): https://docs.meteor.com/about/install.html - Install **yarn**: https://yarnpkg.com/getting-started/install - Clone this repo: `git clone https://github.com/RocketChat/Rocket.Chat.git` - Run `yarn` to install dependencies diff --git a/_templates/package/new/package.json.ejs.t b/_templates/package/new/package.json.ejs.t index 6bee52f55927..b2827ee3fb89 100644 --- a/_templates/package/new/package.json.ejs.t +++ b/_templates/package/new/package.json.ejs.t @@ -19,6 +19,7 @@ to: packages/<%= name %>/package.json "test": "jest", "build": "rm -rf dist && tsc -p tsconfig.json", "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput" + "build-preview": "mkdir -p ../../.preview && cp -r ./dist ../../.preview/<%= name.toLowerCase() %>" }, "main": "./dist/index.js", "typings": "./dist/index.d.ts", diff --git a/apps/meteor/CHANGELOG.md b/apps/meteor/CHANGELOG.md index f466c34da838..97a246abaca6 100644 --- a/apps/meteor/CHANGELOG.md +++ b/apps/meteor/CHANGELOG.md @@ -1,5 +1,535 @@ # @rocket.chat/meteor +## 6.12.0 + +### Minor Changes + +- ([#32535](https://github.com/RocketChat/Rocket.Chat/pull/32535)) Federation actions like sending message in a federated DM, reacting in a federated chat, etc, will no longer work if the configuration is invalid. + +- ([#32916](https://github.com/RocketChat/Rocket.Chat/pull/32916)) Added a new Audit endpoint `audit/rooms.members` that allows users with `view-members-list-all-rooms` to fetch a list of the members of any room even if the user is not part of it. + +- ([#32032](https://github.com/RocketChat/Rocket.Chat/pull/32032)) Added a new 'Deactivated' tab to the users page, this tab lists users who have logged in for the first time but have been deactivated for any reason. Also added the UI code for the Active tab; + +- ([#33044](https://github.com/RocketChat/Rocket.Chat/pull/33044)) Replaces an outdated banner with the Bubble component in order to display retention policy warning + +- ([#32867](https://github.com/RocketChat/Rocket.Chat/pull/32867)) Added an accordion for advanced settings on Create teams and channels + +- ([#32709](https://github.com/RocketChat/Rocket.Chat/pull/32709) by [@heet434](https://github.com/heet434)) Add "Created at" column to admin rooms table + +- ([#32535](https://github.com/RocketChat/Rocket.Chat/pull/32535)) New button added to validate Matrix Federation configuration. A new field inside admin settings will reflect the configuration status being either 'Valid' or 'Invalid'. + +- ([#32969](https://github.com/RocketChat/Rocket.Chat/pull/32969)) Upgrades fuselage-toastbar version in order to add pause on hover functionality + +- ([#33003](https://github.com/RocketChat/Rocket.Chat/pull/33003)) Added a new setting to enable/disable file encryption in an end to end encrypted room. + +- ([#32868](https://github.com/RocketChat/Rocket.Chat/pull/32868)) Added `sidepanel` field to `teams.create` and `rooms.saveRoomSettings` endpoints + +- ([#33003](https://github.com/RocketChat/Rocket.Chat/pull/33003)) Fixed a bug related to uploading end to end encrypted file. + + E2EE files and uploads are uploaded as files of mime type `application/octet-stream` as we can't reveal the mime type of actual content since it is encrypted and has to be kept confidential. + + The server resolves the mime type of encrypted file as `application/octet-stream` but it wasn't playing nicely with existing settings related to whitelisted and blacklisted media types. + + E2EE files upload was getting blocked if `application/octet-stream` is not a whitelisted media type. + + Now this PR solves this issue by always accepting E2EE uploads even if `application/octet-stream` is not whitelisted but it will block the upload if `application/octet-stream` is black listed. + +### Patch Changes + +- ([#32968](https://github.com/RocketChat/Rocket.Chat/pull/32968)) Bumped @rocket.chat/fuselage that fixes the Menu onPointerUp event behavior + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- ([#33159](https://github.com/RocketChat/Rocket.Chat/pull/33159)) Improves Omnichannel queue page performance + +- ([#32986](https://github.com/RocketChat/Rocket.Chat/pull/32986)) Fixed login with third-party apps not working without the "Manage OAuth Apps" permission + +- ([#32852](https://github.com/RocketChat/Rocket.Chat/pull/32852)) Federated users can no longer be deleted. + +- ([#33033](https://github.com/RocketChat/Rocket.Chat/pull/33033) by [@csuadev](https://github.com/csuadev)) Fixed an issue due to an endpoint pagination that was causing that when an agent have assigned more than 50 departments, the departments have a blank space instead of the name. + +- ([#33058](https://github.com/RocketChat/Rocket.Chat/pull/33058)) Prevent `processRoomAbandonment` callback from erroring out when a room was inactive during a day Business Hours was not configured for. + +- ([#24889](https://github.com/RocketChat/Rocket.Chat/pull/24889) by [@Shivansh2287](https://github.com/Shivansh2287)) Fixes an issue where the Announcement modal with long words was adding a horizontal scrollbar + +- ([#32940](https://github.com/RocketChat/Rocket.Chat/pull/32940)) Stopped non channel members from dragging and dropping files in a channel they do not belong + +- ([#33001](https://github.com/RocketChat/Rocket.Chat/pull/33001)) Allow apps to react/unreact to messages via bridge + +- ([#32809](https://github.com/RocketChat/Rocket.Chat/pull/32809)) Deactivating users who federated will now be permanent. + +- ([#31525](https://github.com/RocketChat/Rocket.Chat/pull/31525)) Fix: Show correct user info actions for non-members in channels. + +- ([#33136](https://github.com/RocketChat/Rocket.Chat/pull/33136)) Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates) + +- ([#32931](https://github.com/RocketChat/Rocket.Chat/pull/32931)) Fixed an issue that caused UI to show an error when the call to get the Business Hour type from settings returned `undefined`. + +- ([#32743](https://github.com/RocketChat/Rocket.Chat/pull/32743)) Fixes an issue where creating a new user with an invalid username (containing special characters) resulted in an error message, but the user was still created. The user creation process now properly aborts when an invalid username is provided. + +- ([#33109](https://github.com/RocketChat/Rocket.Chat/pull/33109)) Fixes the `expanded` prop being accidentally forwarded to `ContextualbarHeader` + +- ([#32846](https://github.com/RocketChat/Rocket.Chat/pull/32846)) Fixed issue with system messages being counted as agents' first responses in livechat rooms (which caused the "best first response time" and "average first response time" metrics to be unreliable for all agents) + +- ([#32791](https://github.com/RocketChat/Rocket.Chat/pull/32791)) Fixed a behavior when updating messages that prevented the `customFields` prop from being updated if there were no changes to the `msg` property. Now, `customFields` will be always updated on message update even if `msg` doesn't change + +- ([#33169](https://github.com/RocketChat/Rocket.Chat/pull/33169)) Fixed issue that prevented sending encrypted filed from the mobile app + +- ([#33101](https://github.com/RocketChat/Rocket.Chat/pull/33101)) Fixed an issue where teams were being created with no room associated with it. + +- ([#33036](https://github.com/RocketChat/Rocket.Chat/pull/33036)) Fixes multiple problems with the `processRoomAbandonment` hook. This hook is in charge of calculating the time a room has been abandoned (this means, the time that elapsed since a room was last answered by an agent until it was closed). However, when business hours were enabled and the user didn't open on one day, if an abandoned room happened to be abandoned _over_ the day there was no business hour configuration, then the process will error out. + Additionally, the values the code was calculating were not right. When business hours are enabled, this code should only count the abandonment time _while a business hour was open_. When rooms were left abandoned for days or weeks, this will also throw an error or output an invalid count. +- ([#33174](https://github.com/RocketChat/Rocket.Chat/pull/33174)) Restored tooltips to the unit edit department field selected options + +- ([#33054](https://github.com/RocketChat/Rocket.Chat/pull/33054)) Fixed issue with livechat analytics in a given date range considering conversation data from the following day + +- ([#32981](https://github.com/RocketChat/Rocket.Chat/pull/32981)) fixed an issue with the "follow message" button not changing state after click + +- ([#32928](https://github.com/RocketChat/Rocket.Chat/pull/32928)) Fixed issue where `after-registration-triggers` would show up in a page when the user was not yet registered + +- ([#33047](https://github.com/RocketChat/Rocket.Chat/pull/33047)) Fixed: Custom fields in extraData now correctly added to extraRoomInfo by livechat.beforeRoom callback during livechat room creation. + +- ([#33040](https://github.com/RocketChat/Rocket.Chat/pull/33040)) Fixed an issue related to setting Accounts_ForgetUserSessionOnWindowClose, this setting was not working as expected. + + The new meteor 2.16 release introduced a new option to configure the Accounts package and choose between the local storage or session storage. They also changed how Meteor.\_localstorage works internally. Due to these changes in Meteor, our setting to use session storage wasn't working as expected. This PR fixes this issue and configures the Accounts package according to the workspace settings. + +- ([#33158](https://github.com/RocketChat/Rocket.Chat/pull/33158)) Fixes an issue where multi-step modals were closing unexpectedly + +-
Updated dependencies [8ea6517c4e, c11f3722df, 7f88158036, 127866ce97, 0c919db7b4, b764c415dc, 1f061a1aa5, dd37ea1b35, 7937ff741a, a14c0678bb, 58c0efc732, e28be46db7, 58c0efc732]: + + - @rocket.chat/fuselage-ui-kit@10.0.0 + - @rocket.chat/ui-theming@0.2.1 + - @rocket.chat/ui-video-conf@10.0.0 + - @rocket.chat/ui-composer@0.2.1 + - @rocket.chat/gazzodown@10.0.0 + - @rocket.chat/ui-avatar@6.0.0 + - @rocket.chat/ui-client@10.0.0 + - @rocket.chat/ui-kit@0.36.1 + - @rocket.chat/model-typings@0.7.0 + - @rocket.chat/i18n@0.7.0 + - @rocket.chat/rest-typings@6.12.0 + - @rocket.chat/web-ui-registration@10.0.0 + - @rocket.chat/core-typings@6.12.0 + - @rocket.chat/core-services@0.6.0 + - @rocket.chat/omnichannel-services@0.3.3 + - @rocket.chat/apps@0.1.6 + - @rocket.chat/models@0.2.3 + - @rocket.chat/ui-contexts@10.0.0 + - @rocket.chat/presence@0.2.6 + - @rocket.chat/api-client@0.2.6 + - @rocket.chat/license@0.2.6 + - @rocket.chat/pdf-worker@0.2.3 + - @rocket.chat/cron@0.1.6 + - @rocket.chat/instance-status@0.1.6 + - @rocket.chat/server-cloud-communication@0.0.2 +
+ +## 6.12.0-rc.6 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.12.0-rc.6 + - @rocket.chat/rest-typings@6.12.0-rc.6 + - @rocket.chat/license@0.2.6-rc.6 + - @rocket.chat/omnichannel-services@0.3.3-rc.6 + - @rocket.chat/pdf-worker@0.2.3-rc.6 + - @rocket.chat/presence@0.2.6-rc.6 + - @rocket.chat/api-client@0.2.6-rc.6 + - @rocket.chat/apps@0.1.6-rc.6 + - @rocket.chat/core-services@0.6.0-rc.6 + - @rocket.chat/cron@0.1.6-rc.6 + - @rocket.chat/fuselage-ui-kit@10.0.0-rc.6 + - @rocket.chat/gazzodown@10.0.0-rc.6 + - @rocket.chat/model-typings@0.7.0-rc.6 + - @rocket.chat/ui-contexts@10.0.0-rc.6 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.3-rc.6 + - @rocket.chat/ui-theming@0.2.1-rc.0 + - @rocket.chat/ui-avatar@6.0.0-rc.6 + - @rocket.chat/ui-client@10.0.0-rc.6 + - @rocket.chat/ui-video-conf@10.0.0-rc.6 + - @rocket.chat/web-ui-registration@10.0.0-rc.6 + - @rocket.chat/instance-status@0.1.6-rc.6 +
+ +## 6.12.0-rc.5 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- ([#33158](https://github.com/RocketChat/Rocket.Chat/pull/33158)) Fixes an issue where multi-step modals were closing unexpectedly + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.12.0-rc.5 + - @rocket.chat/rest-typings@6.12.0-rc.5 + - @rocket.chat/license@0.2.6-rc.5 + - @rocket.chat/omnichannel-services@0.3.3-rc.5 + - @rocket.chat/pdf-worker@0.2.3-rc.5 + - @rocket.chat/presence@0.2.6-rc.5 + - @rocket.chat/api-client@0.2.6-rc.5 + - @rocket.chat/apps@0.1.6-rc.5 + - @rocket.chat/core-services@0.6.0-rc.5 + - @rocket.chat/cron@0.1.6-rc.5 + - @rocket.chat/fuselage-ui-kit@10.0.0-rc.5 + - @rocket.chat/gazzodown@10.0.0-rc.5 + - @rocket.chat/model-typings@0.7.0-rc.5 + - @rocket.chat/ui-contexts@10.0.0-rc.5 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.3-rc.5 + - @rocket.chat/ui-theming@0.2.1-rc.0 + - @rocket.chat/ui-avatar@6.0.0-rc.5 + - @rocket.chat/ui-client@10.0.0-rc.5 + - @rocket.chat/ui-video-conf@10.0.0-rc.5 + - @rocket.chat/web-ui-registration@10.0.0-rc.5 + - @rocket.chat/instance-status@0.1.6-rc.5 +
+ +## 6.12.0-rc.4 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.12.0-rc.4 + - @rocket.chat/rest-typings@6.12.0-rc.4 + - @rocket.chat/license@0.2.6-rc.4 + - @rocket.chat/omnichannel-services@0.3.3-rc.4 + - @rocket.chat/pdf-worker@0.2.3-rc.4 + - @rocket.chat/presence@0.2.6-rc.4 + - @rocket.chat/api-client@0.2.6-rc.4 + - @rocket.chat/apps@0.1.6-rc.4 + - @rocket.chat/core-services@0.6.0-rc.4 + - @rocket.chat/cron@0.1.6-rc.4 + - @rocket.chat/fuselage-ui-kit@10.0.0-rc.4 + - @rocket.chat/gazzodown@10.0.0-rc.4 + - @rocket.chat/model-typings@0.7.0-rc.4 + - @rocket.chat/ui-contexts@10.0.0-rc.4 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.3-rc.4 + - @rocket.chat/ui-theming@0.2.1-rc.0 + - @rocket.chat/ui-avatar@6.0.0-rc.4 + - @rocket.chat/ui-client@10.0.0-rc.4 + - @rocket.chat/ui-video-conf@10.0.0-rc.4 + - @rocket.chat/web-ui-registration@10.0.0-rc.4 + - @rocket.chat/instance-status@0.1.6-rc.4 +
+ +## 6.11.2 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +## 6.12.0-rc.3 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- ([#33159](https://github.com/RocketChat/Rocket.Chat/pull/33159)) Improves Omnichannel queue page performance + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.12.0-rc.3 + - @rocket.chat/rest-typings@6.12.0-rc.3 + - @rocket.chat/license@0.2.5-rc.3 + - @rocket.chat/omnichannel-services@0.3.2-rc.3 + - @rocket.chat/pdf-worker@0.2.2-rc.3 + - @rocket.chat/presence@0.2.5-rc.3 + - @rocket.chat/api-client@0.2.5-rc.3 + - @rocket.chat/apps@0.1.5-rc.3 + - @rocket.chat/core-services@0.6.0-rc.3 + - @rocket.chat/cron@0.1.5-rc.3 + - @rocket.chat/fuselage-ui-kit@10.0.0-rc.3 + - @rocket.chat/gazzodown@10.0.0-rc.3 + - @rocket.chat/model-typings@0.7.0-rc.3 + - @rocket.chat/ui-contexts@10.0.0-rc.3 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.2-rc.3 + - @rocket.chat/ui-theming@0.2.1-rc.0 + - @rocket.chat/ui-avatar@6.0.0-rc.3 + - @rocket.chat/ui-client@10.0.0-rc.3 + - @rocket.chat/ui-video-conf@10.0.0-rc.3 + - @rocket.chat/web-ui-registration@10.0.0-rc.3 + - @rocket.chat/instance-status@0.1.5-rc.3 +
+ +## 6.12.0-rc.2 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- ([#33169](https://github.com/RocketChat/Rocket.Chat/pull/33169)) Fixed issue that prevented sending encrypted filed from the mobile app + +- ([#33174](https://github.com/RocketChat/Rocket.Chat/pull/33174)) Restored tooltips to the unit edit department field selected options + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.12.0-rc.2 + - @rocket.chat/rest-typings@6.12.0-rc.2 + - @rocket.chat/license@0.2.5-rc.2 + - @rocket.chat/omnichannel-services@0.3.2-rc.2 + - @rocket.chat/pdf-worker@0.2.2-rc.2 + - @rocket.chat/presence@0.2.5-rc.2 + - @rocket.chat/api-client@0.2.5-rc.2 + - @rocket.chat/apps@0.1.5-rc.2 + - @rocket.chat/core-services@0.6.0-rc.2 + - @rocket.chat/cron@0.1.5-rc.2 + - @rocket.chat/fuselage-ui-kit@10.0.0-rc.2 + - @rocket.chat/gazzodown@10.0.0-rc.2 + - @rocket.chat/model-typings@0.7.0-rc.2 + - @rocket.chat/ui-contexts@10.0.0-rc.2 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.2-rc.2 + - @rocket.chat/ui-theming@0.2.1-rc.0 + - @rocket.chat/ui-avatar@6.0.0-rc.2 + - @rocket.chat/ui-client@10.0.0-rc.2 + - @rocket.chat/ui-video-conf@10.0.0-rc.2 + - @rocket.chat/web-ui-registration@10.0.0-rc.2 + - @rocket.chat/instance-status@0.1.5-rc.2 +
+ +## 6.12.0-rc.1 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- ([#33136](https://github.com/RocketChat/Rocket.Chat/pull/33136)) Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates) + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.12.0-rc.1 + - @rocket.chat/rest-typings@6.12.0-rc.1 + - @rocket.chat/license@0.2.5-rc.1 + - @rocket.chat/omnichannel-services@0.3.2-rc.1 + - @rocket.chat/pdf-worker@0.2.2-rc.1 + - @rocket.chat/presence@0.2.5-rc.1 + - @rocket.chat/api-client@0.2.5-rc.1 + - @rocket.chat/apps@0.1.5-rc.1 + - @rocket.chat/core-services@0.6.0-rc.1 + - @rocket.chat/cron@0.1.5-rc.1 + - @rocket.chat/fuselage-ui-kit@10.0.0-rc.1 + - @rocket.chat/gazzodown@10.0.0-rc.1 + - @rocket.chat/model-typings@0.7.0-rc.1 + - @rocket.chat/ui-contexts@10.0.0-rc.1 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.2-rc.1 + - @rocket.chat/ui-theming@0.2.1-rc.0 + - @rocket.chat/ui-avatar@6.0.0-rc.1 + - @rocket.chat/ui-client@10.0.0-rc.1 + - @rocket.chat/ui-video-conf@10.0.0-rc.1 + - @rocket.chat/web-ui-registration@10.0.0-rc.1 + - @rocket.chat/instance-status@0.1.5-rc.1 +
+ +## 6.12.0-rc.0 + +### Minor Changes + +- ([#32535](https://github.com/RocketChat/Rocket.Chat/pull/32535)) Federation actions like sending message in a federated DM, reacting in a federated chat, etc, will no longer work if the configuration is invalid. + +- ([#32916](https://github.com/RocketChat/Rocket.Chat/pull/32916)) Added a new Audit endpoint `audit/rooms.members` that allows users with `view-members-list-all-rooms` to fetch a list of the members of any room even if the user is not part of it. + +- ([#32032](https://github.com/RocketChat/Rocket.Chat/pull/32032)) Added a new 'Deactivated' tab to the users page, this tab lists users who have logged in for the first time but have been deactivated for any reason. Also added the UI code for the Active tab; + +- ([#33044](https://github.com/RocketChat/Rocket.Chat/pull/33044)) Replaces an outdated banner with the Bubble component in order to display retention policy warning + +- ([#32867](https://github.com/RocketChat/Rocket.Chat/pull/32867)) Added an accordion for advanced settings on Create teams and channels + +- ([#32709](https://github.com/RocketChat/Rocket.Chat/pull/32709) by [@heet434](https://github.com/heet434)) Add "Created at" column to admin rooms table + +- ([#32535](https://github.com/RocketChat/Rocket.Chat/pull/32535)) New button added to validate Matrix Federation configuration. A new field inside admin settings will reflect the configuration status being either 'Valid' or 'Invalid'. + +- ([#32969](https://github.com/RocketChat/Rocket.Chat/pull/32969)) Upgrades fuselage-toastbar version in order to add pause on hover functionality + +- ([#33003](https://github.com/RocketChat/Rocket.Chat/pull/33003)) Added a new setting to enable/disable file encryption in an end to end encrypted room. + +- ([#32868](https://github.com/RocketChat/Rocket.Chat/pull/32868)) Added `sidepanel` field to `teams.create` and `rooms.saveRoomSettings` endpoints + +- ([#33003](https://github.com/RocketChat/Rocket.Chat/pull/33003)) Fixed a bug related to uploading end to end encrypted file. + + E2EE files and uploads are uploaded as files of mime type `application/octet-stream` as we can't reveal the mime type of actual content since it is encrypted and has to be kept confidential. + + The server resolves the mime type of encrypted file as `application/octet-stream` but it wasn't playing nicely with existing settings related to whitelisted and blacklisted media types. + + E2EE files upload was getting blocked if `application/octet-stream` is not a whitelisted media type. + + Now this PR solves this issue by always accepting E2EE uploads even if `application/octet-stream` is not whitelisted but it will block the upload if `application/octet-stream` is black listed. + +### Patch Changes + +- ([#32968](https://github.com/RocketChat/Rocket.Chat/pull/32968)) Bumped @rocket.chat/fuselage that fixes the Menu onPointerUp event behavior + +- ([#32986](https://github.com/RocketChat/Rocket.Chat/pull/32986)) Fixed login with third-party apps not working without the "Manage OAuth Apps" permission + +- ([#32852](https://github.com/RocketChat/Rocket.Chat/pull/32852)) Federated users can no longer be deleted. + +- ([#33033](https://github.com/RocketChat/Rocket.Chat/pull/33033)) Fixed an issue due to an endpoint pagination that was causing that when an agent have assigned more than 50 departments, the departments have a blank space instead of the name. + +- ([#33058](https://github.com/RocketChat/Rocket.Chat/pull/33058)) Prevent `processRoomAbandonment` callback from erroring out when a room was inactive during a day Business Hours was not configured for. + +- ([#24889](https://github.com/RocketChat/Rocket.Chat/pull/24889) by [@Shivansh2287](https://github.com/Shivansh2287)) Fixes an issue where the Announcement modal with long words was adding a horizontal scrollbar + +- ([#32940](https://github.com/RocketChat/Rocket.Chat/pull/32940)) Stopped non channel members from dragging and dropping files in a channel they do not belong + +- ([#33001](https://github.com/RocketChat/Rocket.Chat/pull/33001)) Allow apps to react/unreact to messages via bridge + +- ([#32809](https://github.com/RocketChat/Rocket.Chat/pull/32809)) Deactivating users who federated will now be permanent. + +- ([#31525](https://github.com/RocketChat/Rocket.Chat/pull/31525)) Fix: Show correct user info actions for non-members in channels. + +- ([#32931](https://github.com/RocketChat/Rocket.Chat/pull/32931)) Fixed an issue that caused UI to show an error when the call to get the Business Hour type from settings returned `undefined`. + +- ([#32743](https://github.com/RocketChat/Rocket.Chat/pull/32743)) Fixes an issue where creating a new user with an invalid username (containing special characters) resulted in an error message, but the user was still created. The user creation process now properly aborts when an invalid username is provided. + +- ([#33109](https://github.com/RocketChat/Rocket.Chat/pull/33109)) Fixes the `expanded` prop being accidentally forwarded to `ContextualbarHeader` + +- ([#32846](https://github.com/RocketChat/Rocket.Chat/pull/32846)) Fixed issue with system messages being counted as agents' first responses in livechat rooms (which caused the "best first response time" and "average first response time" metrics to be unreliable for all agents) + +- ([#32791](https://github.com/RocketChat/Rocket.Chat/pull/32791)) Fixed a behavior when updating messages that prevented the `customFields` prop from being updated if there were no changes to the `msg` property. Now, `customFields` will be always updated on message update even if `msg` doesn't change + +- ([#33101](https://github.com/RocketChat/Rocket.Chat/pull/33101)) Fixed an issue where teams were being created with no room associated with it. + +- ([#33036](https://github.com/RocketChat/Rocket.Chat/pull/33036)) Fixes multiple problems with the `processRoomAbandonment` hook. This hook is in charge of calculating the time a room has been abandoned (this means, the time that elapsed since a room was last answered by an agent until it was closed). However, when business hours were enabled and the user didn't open on one day, if an abandoned room happened to be abandoned _over_ the day there was no business hour configuration, then the process will error out. + Additionally, the values the code was calculating were not right. When business hours are enabled, this code should only count the abandonment time _while a business hour was open_. When rooms were left abandoned for days or weeks, this will also throw an error or output an invalid count. +- ([#33054](https://github.com/RocketChat/Rocket.Chat/pull/33054)) Fixed issue with livechat analytics in a given date range considering conversation data from the following day + +- ([#32981](https://github.com/RocketChat/Rocket.Chat/pull/32981)) fixed an issue with the "follow message" button not changing state after click + +- ([#32928](https://github.com/RocketChat/Rocket.Chat/pull/32928)) Fixed issue where `after-registration-triggers` would show up in a page when the user was not yet registered + +- ([#33047](https://github.com/RocketChat/Rocket.Chat/pull/33047)) Fixed: Custom fields in extraData now correctly added to extraRoomInfo by livechat.beforeRoom callback during livechat room creation. + +- ([#33040](https://github.com/RocketChat/Rocket.Chat/pull/33040)) Fixed an issue related to setting Accounts_ForgetUserSessionOnWindowClose, this setting was not working as expected. + + The new meteor 2.16 release introduced a new option to configure the Accounts package and choose between the local storage or session storage. They also changed how Meteor.\_localstorage works internally. Due to these changes in Meteor, our setting to use session storage wasn't working as expected. This PR fixes this issue and configures the Accounts package according to the workspace settings. + +-
Updated dependencies [8ea6517c4e, c11f3722df, 7f88158036, 127866ce97, 0c919db7b4, b764c415dc, 1f061a1aa5, dd37ea1b35, 7937ff741a, a14c0678bb, 58c0efc732, e28be46db7, 58c0efc732]: + + - @rocket.chat/fuselage-ui-kit@10.0.0-rc.0 + - @rocket.chat/ui-theming@0.2.1-rc.0 + - @rocket.chat/ui-video-conf@10.0.0-rc.0 + - @rocket.chat/ui-composer@0.2.1-rc.0 + - @rocket.chat/gazzodown@10.0.0-rc.0 + - @rocket.chat/ui-avatar@6.0.0-rc.0 + - @rocket.chat/ui-client@10.0.0-rc.0 + - @rocket.chat/ui-kit@0.36.1-rc.0 + - @rocket.chat/model-typings@0.7.0-rc.0 + - @rocket.chat/i18n@0.7.0-rc.0 + - @rocket.chat/rest-typings@6.12.0-rc.0 + - @rocket.chat/web-ui-registration@10.0.0-rc.0 + - @rocket.chat/core-typings@6.12.0-rc.0 + - @rocket.chat/core-services@0.6.0-rc.0 + - @rocket.chat/omnichannel-services@0.3.1-rc.0 + - @rocket.chat/apps@0.1.4-rc.0 + - @rocket.chat/models@0.2.1-rc.0 + - @rocket.chat/ui-contexts@10.0.0-rc.0 + - @rocket.chat/presence@0.2.4-rc.0 + - @rocket.chat/api-client@0.2.4-rc.0 + - @rocket.chat/license@0.2.4-rc.0 + - @rocket.chat/pdf-worker@0.2.1-rc.0 + - @rocket.chat/cron@0.1.4-rc.0 + - @rocket.chat/instance-status@0.1.4-rc.0 + - @rocket.chat/server-cloud-communication@0.0.2 +
+ +- Bump @rocket.chat/meteor version. + +- ([#33084](https://github.com/RocketChat/Rocket.Chat/pull/33084) by [@dionisio-bot](https://github.com/dionisio-bot)) Prevent `processRoomAbandonment` callback from erroring out when a room was inactive during a day Business Hours was not configured for. + +- ([#33153](https://github.com/RocketChat/Rocket.Chat/pull/33153) by [@dionisio-bot](https://github.com/dionisio-bot)) Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates) + +- ([#33185](https://github.com/RocketChat/Rocket.Chat/pull/33185) by [@dionisio-bot](https://github.com/dionisio-bot)) Restored tooltips to the unit edit department field selected options + +- ([#33129](https://github.com/RocketChat/Rocket.Chat/pull/33129) by [@dionisio-bot](https://github.com/dionisio-bot)) Fixed an issue related to setting Accounts_ForgetUserSessionOnWindowClose, this setting was not working as expected. + + The new meteor 2.16 release introduced a new option to configure the Accounts package and choose between the local storage or session storage. They also changed how Meteor.\_localstorage works internally. Due to these changes in Meteor, our setting to use session storage wasn't working as expected. This PR fixes this issue and configures the Accounts package according to the workspace settings. + +- ([#33178](https://github.com/RocketChat/Rocket.Chat/pull/33178) by [@dionisio-bot](https://github.com/dionisio-bot)) Fixes an issue where multi-step modals were closing unexpectedly + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.2 + - @rocket.chat/rest-typings@6.11.2 + - @rocket.chat/api-client@0.2.5 + - @rocket.chat/license@0.2.5 + - @rocket.chat/omnichannel-services@0.3.2 + - @rocket.chat/pdf-worker@0.2.2 + - @rocket.chat/presence@0.2.5 + - @rocket.chat/apps@0.1.5 + - @rocket.chat/core-services@0.5.2 + - @rocket.chat/cron@0.1.5 + - @rocket.chat/fuselage-ui-kit@9.0.2 + - @rocket.chat/gazzodown@9.0.2 + - @rocket.chat/model-typings@0.6.2 + - @rocket.chat/ui-contexts@9.0.2 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.2 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.2 + - @rocket.chat/ui-client@9.0.2 + - @rocket.chat/ui-video-conf@9.0.2 + - @rocket.chat/web-ui-registration@9.0.2 + - @rocket.chat/instance-status@0.1.5 +
+ +## 6.11.1 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- ([#33062](https://github.com/RocketChat/Rocket.Chat/pull/33062)) Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates) + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.1 + - @rocket.chat/rest-typings@6.11.1 + - @rocket.chat/api-client@0.2.4 + - @rocket.chat/license@0.2.4 + - @rocket.chat/omnichannel-services@0.3.1 + - @rocket.chat/pdf-worker@0.2.1 + - @rocket.chat/presence@0.2.4 + - @rocket.chat/apps@0.1.4 + - @rocket.chat/core-services@0.5.1 + - @rocket.chat/cron@0.1.4 + - @rocket.chat/fuselage-ui-kit@9.0.1 + - @rocket.chat/gazzodown@9.0.1 + - @rocket.chat/model-typings@0.6.1 + - @rocket.chat/ui-contexts@9.0.1 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.1 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.1 + - @rocket.chat/ui-client@9.0.1 + - @rocket.chat/ui-video-conf@9.0.1 + - @rocket.chat/web-ui-registration@9.0.1 + - @rocket.chat/instance-status@0.1.4 +
+ ## 6.11.0 ### Minor Changes diff --git a/apps/meteor/app/2fa/server/code/EmailCheck.spec.ts b/apps/meteor/app/2fa/server/code/EmailCheck.spec.ts new file mode 100644 index 000000000000..5c3574f0b395 --- /dev/null +++ b/apps/meteor/app/2fa/server/code/EmailCheck.spec.ts @@ -0,0 +1,70 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import proxyquire from 'proxyquire'; +import sinon from 'sinon'; + +const settingsMock = sinon.stub(); + +const { EmailCheck } = proxyquire.noCallThru().load('./EmailCheck', { + '@rocket.chat/models': { + Users: {}, + }, + 'meteor/accounts-base': { + Accounts: { + _bcryptRounds: () => '123', + }, + }, + '../../../../server/lib/i18n': { + i18n: { + t: (key: string) => key, + }, + }, + '../../../mailer/server/api': { + send: () => undefined, + }, + '../../../settings/server': { + settings: { + get: settingsMock, + }, + }, +}); + +const normalUserMock = { services: { email2fa: { enabled: true } }, emails: [{ email: 'abc@gmail.com', verified: true }] }; +const normalUserWithUnverifiedEmailMock = { + services: { email2fa: { enabled: true } }, + emails: [{ email: 'abc@gmail.com', verified: false }], +}; +const OAuthUserMock = { services: { google: {} }, emails: [{ email: 'abc@gmail.com', verified: true }] }; + +describe('EmailCheck', () => { + let emailCheck: typeof EmailCheck; + beforeEach(() => { + settingsMock.reset(); + + emailCheck = new EmailCheck(); + }); + + it('should return EmailCheck is enabled for a normal user', () => { + settingsMock.returns(true); + + const isEmail2FAEnabled = emailCheck.isEnabled(normalUserMock); + + expect(isEmail2FAEnabled).to.be.equal(true); + }); + + it('should return EmailCheck is not enabled for a normal user with unverified email', () => { + settingsMock.returns(true); + + const isEmail2FAEnabled = emailCheck.isEnabled(normalUserWithUnverifiedEmailMock); + + expect(isEmail2FAEnabled).to.be.equal(false); + }); + + it('should return EmailCheck is not enabled for a OAuth user with setting being false', () => { + settingsMock.returns(true); + + const isEmail2FAEnabled = emailCheck.isEnabled(OAuthUserMock); + + expect(isEmail2FAEnabled).to.be.equal(false); + }); +}); diff --git a/apps/meteor/app/2fa/server/code/EmailCheck.ts b/apps/meteor/app/2fa/server/code/EmailCheck.ts index 123df96ee264..5baf218a62bb 100644 --- a/apps/meteor/app/2fa/server/code/EmailCheck.ts +++ b/apps/meteor/app/2fa/server/code/EmailCheck.ts @@ -1,4 +1,4 @@ -import type { IUser } from '@rocket.chat/core-typings'; +import { isOAuthUser, type IUser } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import bcrypt from 'bcrypt'; @@ -24,6 +24,10 @@ export class EmailCheck implements ICodeCheck { return false; } + if (!settings.get('Accounts_twoFactorAuthentication_email_available_for_OAuth_users') && isOAuthUser(user)) { + return false; + } + if (!user.services?.email2fa?.enabled) { return false; } @@ -34,7 +38,7 @@ export class EmailCheck implements ICodeCheck { private async send2FAEmail(address: string, random: string, user: IUser): Promise { const language = user.language || settings.get('Language') || 'en'; - const t = (s: string): string => i18n.t(s, { lng: language }); + const t = i18n.getFixedT(language); await Mailer.send({ to: address, diff --git a/apps/meteor/app/2fa/server/code/index.ts b/apps/meteor/app/2fa/server/code/index.ts index 1fbe658e5682..b05157416e31 100644 --- a/apps/meteor/app/2fa/server/code/index.ts +++ b/apps/meteor/app/2fa/server/code/index.ts @@ -45,14 +45,10 @@ function getAvailableMethodNames(user: IUser): string[] { export async function getUserForCheck(userId: string): Promise { return Users.findOneById(userId, { projection: { - 'emails': 1, - 'language': 1, - 'createdAt': 1, - 'services.totp': 1, - 'services.email2fa': 1, - 'services.emailCode': 1, - 'services.password': 1, - 'services.resume.loginTokens': 1, + emails: 1, + language: 1, + createdAt: 1, + services: 1, }, }); } diff --git a/apps/meteor/app/2fa/server/functions/resetTOTP.ts b/apps/meteor/app/2fa/server/functions/resetTOTP.ts index 3be8ec7c8060..84426cd4f88d 100644 --- a/apps/meteor/app/2fa/server/functions/resetTOTP.ts +++ b/apps/meteor/app/2fa/server/functions/resetTOTP.ts @@ -22,7 +22,7 @@ const sendResetNotification = async function (uid: string): Promise { return; } - const t = (s: string): string => i18n.t(s, { lng: language }); + const t = i18n.getFixedT(language); const text = ` ${t('Your_TOTP_has_been_reset')} diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index dd4da47bff05..8348b8429e4e 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -1,6 +1,6 @@ import crypto from 'crypto'; -import type { IUser } from '@rocket.chat/core-typings'; +import { isOAuthUser, type IUser } from '@rocket.chat/core-typings'; import { Settings, Users } from '@rocket.chat/models'; import { isShieldSvgProps, @@ -26,7 +26,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { passwordPolicy } from '../../../lib/server'; import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; -import { getDefaultUserFields } from '../../../utils/server/functions/getDefaultUserFields'; +import { getBaseUserFields } from '../../../utils/server/functions/getBaseUserFields'; import { isSMTPConfigured } from '../../../utils/server/functions/isSMTPConfigured'; import { getURL } from '../../../utils/server/getURL'; import { API } from '../api'; @@ -176,15 +176,19 @@ API.v1.addRoute( { authRequired: true }, { async get() { - const fields = getDefaultUserFields(); - const { services, ...user } = (await Users.findOneById(this.userId, { projection: fields })) as IUser; + const userFields = { ...getBaseUserFields(), services: 1 }; + const { services, ...user } = (await Users.findOneById(this.userId, { projection: userFields })) as IUser; return API.v1.success( await getUserInfo({ ...user, + isOAuthUser: isOAuthUser({ ...user, services }), ...(services && { services: { - ...services, + ...(services.github && { github: services.github }), + ...(services.gitlab && { gitlab: services.gitlab }), + ...(services.email2fa?.enabled && { email2fa: { enabled: services.email2fa.enabled } }), + ...(services.totp?.enabled && { totp: { enabled: services.totp.enabled } }), password: { // The password hash shouldn't be leaked but the client may need to know if it exists. exists: Boolean(services?.password?.bcrypt), diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 17ef75b74574..5da59d977fb1 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1,4 +1,4 @@ -import { Media } from '@rocket.chat/core-services'; +import { Media, Team } from '@rocket.chat/core-services'; import type { IRoom, IUpload } from '@rocket.chat/core-typings'; import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models'; import type { Notifications } from '@rocket.chat/rest-typings'; @@ -8,6 +8,7 @@ import { isRoomsMuteUnmuteUserProps, isRoomsExportProps, isRoomsIsMemberProps, + isRoomsCleanHistoryProps, } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; @@ -361,7 +362,7 @@ API.v1.addRoute( API.v1.addRoute( 'rooms.cleanHistory', - { authRequired: true }, + { authRequired: true, validateParams: isRoomsCleanHistoryProps }, { async post() { const { _id } = await findRoomByIdOrName({ params: this.bodyParams }); @@ -416,7 +417,19 @@ API.v1.addRoute( return API.v1.failure('not-allowed', 'Not Allowed'); } - return API.v1.success({ room: (await Rooms.findOneByIdOrName(room._id, { projection: fields })) ?? undefined }); + const discussionParent = + room.prid && + (await Rooms.findOneById>(room.prid, { + projection: { name: 1, fname: 1, t: 1, prid: 1, u: 1 }, + })); + const { team, parentRoom } = await Team.getRoomInfo(room); + const parent = discussionParent || parentRoom; + + return API.v1.success({ + room: (await Rooms.findOneByIdOrName(room._id, { projection: fields })) ?? undefined, + ...(team && { team }), + ...(parent && { parent }), + }); }, }, ); diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index 9c56ecac01cb..530e3dc0770c 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -755,6 +755,12 @@ API.v1.addRoute( { authRequired: false }, { async post() { + const isPasswordResetEnabled = settings.get('Accounts_PasswordReset'); + + if (!isPasswordResetEnabled) { + return API.v1.failure('Password reset is not enabled'); + } + const { email } = this.bodyParams; if (!email) { return API.v1.failure("The 'email' param is required"); diff --git a/apps/meteor/app/apps/server/bridges/bridges.js b/apps/meteor/app/apps/server/bridges/bridges.js index 6d52a1d8e56d..aeab9f191039 100644 --- a/apps/meteor/app/apps/server/bridges/bridges.js +++ b/apps/meteor/app/apps/server/bridges/bridges.js @@ -5,6 +5,7 @@ import { AppApisBridge } from './api'; import { AppCloudBridge } from './cloud'; import { AppCommandsBridge } from './commands'; import { AppDetailChangesBridge } from './details'; +import { AppEmailBridge } from './email'; import { AppEnvironmentalVariableBridge } from './environmental'; import { AppHttpBridge } from './http'; import { AppInternalBridge } from './internal'; @@ -53,6 +54,7 @@ export class RealAppBridges extends AppBridges { this._moderationBridge = new AppModerationBridge(orch); this._threadBridge = new AppThreadBridge(orch); this._roleBridge = new AppRoleBridge(orch); + this._emailBridge = new AppEmailBridge(orch); } getCommandBridge() { @@ -150,4 +152,8 @@ export class RealAppBridges extends AppBridges { getRoleBridge() { return this._roleBridge; } + + getEmailBridge() { + return this._emailBridge; + } } diff --git a/apps/meteor/app/apps/server/bridges/email.ts b/apps/meteor/app/apps/server/bridges/email.ts new file mode 100644 index 000000000000..4c9cb9a93ed6 --- /dev/null +++ b/apps/meteor/app/apps/server/bridges/email.ts @@ -0,0 +1,16 @@ +import type { IAppServerOrchestrator } from '@rocket.chat/apps'; +import type { IEmail } from '@rocket.chat/apps-engine/definition/email'; +import { EmailBridge } from '@rocket.chat/apps-engine/server/bridges'; + +import * as Mailer from '../../../mailer/server/api'; + +export class AppEmailBridge extends EmailBridge { + constructor(private readonly orch: IAppServerOrchestrator) { + super(); + } + + protected async sendEmail(email: IEmail, appId: string): Promise { + this.orch.debugLog(`The app ${appId} is sending an email.`); + await Mailer.send(email); + } +} diff --git a/apps/meteor/app/authorization/server/constant/permissions.ts b/apps/meteor/app/authorization/server/constant/permissions.ts index d9ae4133e49e..46d40713bad1 100644 --- a/apps/meteor/app/authorization/server/constant/permissions.ts +++ b/apps/meteor/app/authorization/server/constant/permissions.ts @@ -97,6 +97,14 @@ export const permissions = [ _id: 'create-livechat-contact', roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'], }, + { + _id: 'update-livechat-contact', + roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'], + }, + { + _id: 'view-livechat-contact', + roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'], + }, { _id: 'view-livechat-manager', roles: ['livechat-manager', 'livechat-monitor', 'admin'] }, { _id: 'view-omnichannel-contact-center', diff --git a/apps/meteor/app/authorization/server/functions/canDeleteMessage.ts b/apps/meteor/app/authorization/server/functions/canDeleteMessage.ts index 7cd953a52bb2..fea37fd1c2a5 100644 --- a/apps/meteor/app/authorization/server/functions/canDeleteMessage.ts +++ b/apps/meteor/app/authorization/server/functions/canDeleteMessage.ts @@ -1,7 +1,8 @@ -import type { IUser } from '@rocket.chat/core-typings'; +import type { IUser, IRoom } from '@rocket.chat/core-typings'; import { Rooms } from '@rocket.chat/models'; import { getValue } from '../../../settings/server/raw'; +import { canAccessRoomAsync } from './canAccessRoom'; import { hasPermissionAsync } from './hasPermission'; const elapsedTime = (ts: Date): number => { @@ -13,6 +14,25 @@ export const canDeleteMessageAsync = async ( uid: string, { u, rid, ts }: { u: Pick; rid: string; ts: Date }, ): Promise => { + const room = await Rooms.findOneById>(rid, { + projection: { + _id: 1, + ro: 1, + unmuted: 1, + t: 1, + teamId: 1, + prid: 1, + }, + }); + + if (!room) { + return false; + } + + if (!(await canAccessRoomAsync(room, { _id: uid }))) { + return false; + } + const forceDelete = await hasPermissionAsync(uid, 'force-delete-message', rid); if (forceDelete) { @@ -45,12 +65,6 @@ export const canDeleteMessageAsync = async ( } } - const room = await Rooms.findOneById(rid, { projection: { ro: 1, unmuted: 1 } }); - - if (!room) { - return false; - } - if (room.ro === true && !(await hasPermissionAsync(uid, 'post-readonly', rid))) { // Unless the user was manually unmuted if (u.username && !(room.unmuted || []).includes(u.username)) { diff --git a/apps/meteor/app/autotranslate/server/autotranslate.ts b/apps/meteor/app/autotranslate/server/autotranslate.ts index f3c6d9e55fdb..23e1b189a792 100644 --- a/apps/meteor/app/autotranslate/server/autotranslate.ts +++ b/apps/meteor/app/autotranslate/server/autotranslate.ts @@ -113,7 +113,12 @@ export class TranslationProviderRegistry { return; } - callbacks.add('afterSaveMessage', provider.translateMessage.bind(provider), callbacks.priority.MEDIUM, 'autotranslate'); + callbacks.add( + 'afterSaveMessage', + (message, { room }) => provider.translateMessage(message, { room }), + callbacks.priority.MEDIUM, + 'autotranslate', + ); } } diff --git a/apps/meteor/app/federation/server/hooks/afterUnsetReaction.js b/apps/meteor/app/federation/server/hooks/afterUnsetReaction.js index 51181d88ab9e..995146b290bf 100644 --- a/apps/meteor/app/federation/server/hooks/afterUnsetReaction.js +++ b/apps/meteor/app/federation/server/hooks/afterUnsetReaction.js @@ -6,7 +6,7 @@ import { getFederationDomain } from '../lib/getFederationDomain'; import { clientLogger } from '../lib/logger'; async function afterUnsetReaction(message, { user, reaction }) { - const room = Rooms.findOneById(message.rid, { fields: { federation: 1 } }); + const room = await Rooms.findOneById(message.rid, { projection: { federation: 1 } }); // If there are not federated users on this room, ignore it if (!hasExternalDomain(room)) { diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index 8714c71f20d6..ac97923be41e 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -175,7 +175,12 @@ export const FileUpload = { throw new Meteor.Error('error-invalid-file-type', reason); } - // E2EE files are of type - application/octet-stream, application/octet-stream is whitelisted for E2EE files. + // E2EE files should be of type application/octet-stream. no information about them should be disclosed on upload if they are encrypted + if (isE2EEUpload(file)) { + file.type = 'application/octet-stream'; + } + + // E2EE files are of type application/octet-stream, which is whitelisted for E2EE files if (!fileUploadIsValidContentType(file?.type, isE2EEUpload(file) ? 'application/octet-stream' : undefined)) { const reason = i18n.t('File_type_is_not_accepted', { lng: language }); throw new Meteor.Error('error-invalid-file-type', reason); diff --git a/apps/meteor/app/integrations/server/triggers.ts b/apps/meteor/app/integrations/server/triggers.ts index cdf8acda6a21..64b95827645f 100644 --- a/apps/meteor/app/integrations/server/triggers.ts +++ b/apps/meteor/app/integrations/server/triggers.ts @@ -8,7 +8,12 @@ const callbackHandler = function _callbackHandler(eventType: string) { }; }; -callbacks.add('afterSaveMessage', callbackHandler('sendMessage'), callbacks.priority.LOW, 'integrations-sendMessage'); +callbacks.add( + 'afterSaveMessage', + (message, { room }) => callbackHandler('sendMessage')(message, room), + callbacks.priority.LOW, + 'integrations-sendMessage', +); callbacks.add('afterCreateChannel', callbackHandler('roomCreated'), callbacks.priority.LOW, 'integrations-roomCreated'); callbacks.add('afterCreatePrivateGroup', callbackHandler('roomCreated'), callbacks.priority.LOW, 'integrations-roomCreated'); callbacks.add('afterCreateUser', callbackHandler('userCreated'), callbacks.priority.LOW, 'integrations-userCreated'); diff --git a/apps/meteor/app/irc/server/irc-bridge/index.js b/apps/meteor/app/irc/server/irc-bridge/index.js index 09b7a3568362..bc5b4f0bc33f 100644 --- a/apps/meteor/app/irc/server/irc-bridge/index.js +++ b/apps/meteor/app/irc/server/irc-bridge/index.js @@ -209,7 +209,7 @@ class Bridge { // Chatting callbacks.add( 'afterSaveMessage', - this.onMessageReceived.bind(this, 'local', 'onSaveMessage'), + (message, { room }) => this.onMessageReceived('local', 'onSaveMessage', message, room), callbacks.priority.LOW, 'irc-on-save-message', ); diff --git a/apps/meteor/app/lib/server/functions/getModifiedHttpHeaders.ts b/apps/meteor/app/lib/server/functions/getModifiedHttpHeaders.ts new file mode 100644 index 000000000000..e62727814de3 --- /dev/null +++ b/apps/meteor/app/lib/server/functions/getModifiedHttpHeaders.ts @@ -0,0 +1,20 @@ +export const getModifiedHttpHeaders = (httpHeaders: Record) => { + const modifiedHttpHeaders = { ...httpHeaders }; + + if ('x-auth-token' in modifiedHttpHeaders) { + modifiedHttpHeaders['x-auth-token'] = '[redacted]'; + } + + if (modifiedHttpHeaders.cookie) { + const cookies = modifiedHttpHeaders.cookie.split('; '); + const modifiedCookies = cookies.map((cookie: string) => { + if (cookie.startsWith('rc_token=')) { + return 'rc_token=[redacted]'; + } + return cookie; + }); + modifiedHttpHeaders.cookie = modifiedCookies.join('; '); + } + + return modifiedHttpHeaders; +}; diff --git a/apps/meteor/app/lib/server/lib/debug.js b/apps/meteor/app/lib/server/lib/debug.js index aaa492e80337..cbf38528579f 100644 --- a/apps/meteor/app/lib/server/lib/debug.js +++ b/apps/meteor/app/lib/server/lib/debug.js @@ -7,6 +7,7 @@ import _ from 'underscore'; import { getMethodArgs } from '../../../../server/lib/logger/logPayloads'; import { metrics } from '../../../metrics/server'; import { settings } from '../../../settings/server'; +import { getModifiedHttpHeaders } from '../functions/getModifiedHttpHeaders'; const logger = new Logger('Meteor'); @@ -41,7 +42,7 @@ const traceConnection = (enable, filter, prefix, name, connection, userId) => { console.log(name, { id: connection.id, clientAddress: connection.clientAddress, - httpHeaders: connection.httpHeaders, + httpHeaders: getModifiedHttpHeaders(connection.httpHeaders), userId, }); } else { diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 7551cabb6e63..fdb99c83207c 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -111,7 +111,7 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise roomId: room._id, uidsExclude: [message.u._id], uidsInclude: userIds, - onlyRead: !toAll && !toHere, + onlyRead: !toAll && !toHere && unreadCount !== 'all_messages', }).forEach((sub) => { const hasUserMention = userIds.includes(sub.u._id); const shouldIncUnread = hasUserMention || toAll || toHere || unreadCount === 'all_messages'; @@ -168,8 +168,6 @@ export async function updateThreadUsersSubscriptions(message: IMessage, replies: } export async function notifyUsersOnMessage(message: IMessage, room: IRoom, roomUpdater: Updater): Promise { - console.log('notifyUsersOnMessage function'); - // Skips this callback if the message was edited and increments it if the edit was way in the past (aka imported) if (isEditedMessage(message)) { if (Math.abs(moment(message.editedAt).diff(Date.now())) > 60000) { diff --git a/apps/meteor/app/lib/server/methods/addUsersToRoom.ts b/apps/meteor/app/lib/server/methods/addUsersToRoom.ts index 73fbf6e51a04..119071e8ece0 100644 --- a/apps/meteor/app/lib/server/methods/addUsersToRoom.ts +++ b/apps/meteor/app/lib/server/methods/addUsersToRoom.ts @@ -98,14 +98,11 @@ export const addUsersToRoomMethod = async (userId: string, data: { rid: string; return; } void api.broadcast('notify.ephemeralMessage', userId, data.rid, { - msg: i18n.t( - 'Username_is_already_in_here', - { - postProcess: 'sprintf', - sprintf: [newUser.username], - }, - user?.language, - ), + msg: i18n.t('Username_is_already_in_here', { + postProcess: 'sprintf', + sprintf: [newUser.username], + lng: user?.language, + }), }); } }), diff --git a/apps/meteor/app/lib/server/methods/sendMessage.ts b/apps/meteor/app/lib/server/methods/sendMessage.ts index 56009f15fede..e004f2199fa0 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.ts +++ b/apps/meteor/app/lib/server/methods/sendMessage.ts @@ -2,6 +2,7 @@ import { api } from '@rocket.chat/core-services'; import type { AtLeast, IMessage, IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Users } from '@rocket.chat/models'; +import type { TOptions } from 'i18next'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import moment from 'moment'; @@ -98,9 +99,9 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast void; @@ -9,7 +9,7 @@ type PlaceChatOnHoldModalProps = { }; const PlaceChatOnHoldModal = ({ onCancel, onOnHoldChat, confirm = onOnHoldChat, ...props }: PlaceChatOnHoldModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); return ( diff --git a/apps/meteor/app/livechat/imports/server/rest/appearance.ts b/apps/meteor/app/livechat/imports/server/rest/appearance.ts index 48863fc9e5d3..7496b6243abe 100644 --- a/apps/meteor/app/livechat/imports/server/rest/appearance.ts +++ b/apps/meteor/app/livechat/imports/server/rest/appearance.ts @@ -51,6 +51,7 @@ API.v1.addRoute( 'Livechat_background', 'Livechat_widget_position', 'Livechat_hide_system_messages', + 'Omnichannel_allow_visitors_to_close_conversation', ]; const valid = settings.every((setting) => validSettingList.includes(setting._id)); diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index 6f8ce64bc635..6b2411cf8e3d 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -97,12 +97,18 @@ const normalizeLocationSharing = (payload: ServiceData) => { // @ts-expect-error - this is an special endpoint that requires the return to not be wrapped as regular returns API.v1.addRoute('livechat/sms-incoming/:service', { async post() { - if (!(await OmnichannelIntegration.isConfiguredSmsService(this.urlParams.service))) { + const { service } = this.urlParams; + if (!(await OmnichannelIntegration.isConfiguredSmsService(service))) { return API.v1.failure('Invalid service'); } const smsDepartment = settings.get('SMS_Default_Omnichannel_Department'); - const SMSService = await OmnichannelIntegration.getSmsService(this.urlParams.service); + const SMSService = await OmnichannelIntegration.getSmsService(service); + + if (!SMSService.validateRequest(this.request)) { + return API.v1.failure('Invalid request'); + } + const sms = SMSService.parse(this.bodyParams); const { department } = this.queryParams; let targetDepartment = await defineDepartment(department || smsDepartment); @@ -121,7 +127,7 @@ API.v1.addRoute('livechat/sms-incoming/:service', { }, source: { type: OmnichannelSourceType.SMS, - alias: this.urlParams.service, + alias: service, }, }; diff --git a/apps/meteor/app/livechat/server/api/lib/appearance.ts b/apps/meteor/app/livechat/server/api/lib/appearance.ts index 785413ead9d1..0fc7d3547b2c 100644 --- a/apps/meteor/app/livechat/server/api/lib/appearance.ts +++ b/apps/meteor/app/livechat/server/api/lib/appearance.ts @@ -28,6 +28,7 @@ export async function findAppearance(): Promise<{ appearance: ISetting[] }> { 'Livechat_background', 'Livechat_widget_position', 'Livechat_hide_system_messages', + 'Omnichannel_allow_visitors_to_close_conversation', ], }, }; diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index a922edd40899..8041566d796e 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -142,6 +142,7 @@ export async function settings({ businessUnit = '' }: { businessUnit?: string } hiddenSystemMessages: initSettings.Livechat_hide_system_messages, livechatLogo: initSettings.Assets_livechat_widget_logo, hideWatermark: initSettings.Livechat_hide_watermark || false, + visitorsCanCloseChat: initSettings.Omnichannel_allow_visitors_to_close_conversation, }, theme: { title: initSettings.Livechat_title, diff --git a/apps/meteor/app/livechat/server/api/v1/contact.ts b/apps/meteor/app/livechat/server/api/v1/contact.ts index 91b18a6b21af..f3fec80b23fe 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.ts +++ b/apps/meteor/app/livechat/server/api/v1/contact.ts @@ -1,11 +1,15 @@ -import { LivechatCustomField, LivechatVisitors } from '@rocket.chat/models'; -import { isPOSTOmnichannelContactsProps } from '@rocket.chat/rest-typings'; +import { LivechatContacts, LivechatCustomField, LivechatVisitors } from '@rocket.chat/models'; +import { + isPOSTOmnichannelContactsProps, + isPOSTUpdateOmnichannelContactsProps, + isGETOmnichannelContactsProps, +} from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { API } from '../../../../api/server'; -import { Contacts, createContact } from '../../lib/Contacts'; +import { Contacts, createContact, updateContact } from '../../lib/Contacts'; API.v1.addRoute( 'omnichannel/contact', @@ -92,7 +96,7 @@ API.v1.addRoute( { authRequired: true, permissionsRequired: ['create-livechat-contact'], validateParams: isPOSTOmnichannelContactsProps }, { async post() { - if (!process.env.TEST_MODE) { + if (process.env.TEST_MODE?.toUpperCase() !== 'TRUE') { throw new Meteor.Error('error-not-allowed', 'This endpoint is only allowed in test mode'); } const contactId = await createContact({ ...this.bodyParams, unknown: false }); @@ -101,3 +105,34 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'omnichannel/contacts.update', + { authRequired: true, permissionsRequired: ['update-livechat-contact'], validateParams: isPOSTUpdateOmnichannelContactsProps }, + { + async post() { + if (process.env.TEST_MODE?.toUpperCase() !== 'TRUE') { + throw new Meteor.Error('error-not-allowed', 'This endpoint is only allowed in test mode'); + } + + const contact = await updateContact({ ...this.bodyParams }); + + return API.v1.success({ contact }); + }, + }, +); + +API.v1.addRoute( + 'omnichannel/contacts.get', + { authRequired: true, permissionsRequired: ['view-livechat-contact'], validateParams: isGETOmnichannelContactsProps }, + { + async get() { + if (process.env.TEST_MODE?.toUpperCase() !== 'TRUE') { + throw new Meteor.Error('error-not-allowed', 'This endpoint is only allowed in test mode'); + } + const contact = await LivechatContacts.findOneById(this.queryParams.contactId); + + return API.v1.success({ contact }); + }, + }, +); diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index b0f45a63ff87..404f576ea513 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -31,63 +31,72 @@ import { findVisitorInfo } from '../lib/visitors'; const isAgentWithInfo = (agentObj: ILivechatAgent | { hiddenInfo: boolean }): agentObj is ILivechatAgent => !('hiddenInfo' in agentObj); -API.v1.addRoute('livechat/room', { - async get() { - // I'll temporary use check for validation, as validateParams doesnt support what's being done here - const extraCheckParams = await onCheckRoomParams({ - token: String, - rid: Match.Maybe(String), - agentId: Match.Maybe(String), - }); - - check(this.queryParams, extraCheckParams as any); - - const { token, rid, agentId, ...extraParams } = this.queryParams; - - const guest = token && (await findGuest(token)); - if (!guest) { - throw new Error('invalid-token'); - } - - if (!rid) { - const room = await LivechatRooms.findOneOpenByVisitorToken(token, {}); - if (room) { - return API.v1.success({ room, newRoom: false }); - } - - let agent: SelectedAgent | undefined; - const agentObj = agentId && (await findAgent(agentId)); - if (agentObj) { - if (isAgentWithInfo(agentObj)) { - const { username = undefined } = agentObj; - agent = { agentId, username }; - } else { - agent = { agentId }; - } +API.v1.addRoute( + 'livechat/room', + { + rateLimiterOptions: { + numRequestsAllowed: 5, + intervalTimeInMS: 60000, + }, + }, + { + async get() { + // I'll temporary use check for validation, as validateParams doesnt support what's being done here + const extraCheckParams = await onCheckRoomParams({ + token: String, + rid: Match.Maybe(String), + agentId: Match.Maybe(String), + }); + + check(this.queryParams, extraCheckParams as any); + + const { token, rid, agentId, ...extraParams } = this.queryParams; + + const guest = token && (await findGuest(token)); + if (!guest) { + throw new Error('invalid-token'); } - const roomInfo = { - source: { - type: isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API, - }, - }; + if (!rid) { + const room = await LivechatRooms.findOneOpenByVisitorToken(token, {}); + if (room) { + return API.v1.success({ room, newRoom: false }); + } - const newRoom = await LivechatTyped.createRoom({ visitor: guest, roomInfo, agent, extraData: extraParams }); + let agent: SelectedAgent | undefined; + const agentObj = agentId && (await findAgent(agentId)); + if (agentObj) { + if (isAgentWithInfo(agentObj)) { + const { username = undefined } = agentObj; + agent = { agentId, username }; + } else { + agent = { agentId }; + } + } - return API.v1.success({ - room: newRoom, - newRoom: true, - }); - } + const roomInfo = { + source: { + type: isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API, + }, + }; - const froom = await LivechatRooms.findOneOpenByRoomIdAndVisitorToken(rid, token, {}); - if (!froom) { - throw new Error('invalid-room'); - } + const newRoom = await LivechatTyped.createRoom({ visitor: guest, roomInfo, agent, extraData: extraParams }); - return API.v1.success({ room: froom, newRoom: false }); + return API.v1.success({ + room: newRoom, + newRoom: true, + }); + } + + const froom = await LivechatRooms.findOneOpenByRoomIdAndVisitorToken(rid, token, {}); + if (!froom) { + throw new Error('invalid-room'); + } + + return API.v1.success({ room: froom, newRoom: false }); + }, }, -}); +); // Note: use this route if a visitor is closing a room // If a RC user(like eg agent) is closing a room, use the `livechat/room.closeByUser` route @@ -98,6 +107,10 @@ API.v1.addRoute( async post() { const { rid, token } = this.bodyParams; + if (!rcSettings.get('Omnichannel_allow_visitors_to_close_conversation')) { + throw new Error('error-not-allowed-to-close-conversation'); + } + const visitor = await findGuest(token); if (!visitor) { throw new Error('invalid-token'); @@ -146,7 +159,7 @@ API.v1.addRoute( const visitorEmail = visitor.visitorEmails?.[0]?.address; const language = servingAgent.language || rcSettings.get('Language') || 'en'; - const t = (s: string): string => i18n.t(s, { lng: language }); + const t = i18n.getFixedT(language); const subject = t('Transcript_of_your_livechat_conversation'); options.emailTranscript = { diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index a5b3f2de35b1..ed32f0e2d279 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -9,119 +9,128 @@ import { settings } from '../../../../settings/server'; import { Livechat as LivechatTyped } from '../../lib/LivechatTyped'; import { findGuest, normalizeHttpHeaderData } from '../lib/livechat'; -API.v1.addRoute('livechat/visitor', { - async post() { - check(this.bodyParams, { - visitor: Match.ObjectIncluding({ - token: String, - name: Match.Maybe(String), - email: Match.Maybe(String), - department: Match.Maybe(String), - phone: Match.Maybe(String), - username: Match.Maybe(String), - customFields: Match.Maybe([ - Match.ObjectIncluding({ - key: String, - value: String, - overwrite: Boolean, - }), - ]), - }), - }); - - const { customFields, id, token, name, email, department, phone, username, connectionData } = this.bodyParams.visitor; - - if (!token?.trim()) { - throw new Meteor.Error('error-invalid-token', 'Token cannot be empty', { method: 'livechat/visitor' }); - } - - const guest = { - token, - ...(id && { id }), - ...(name && { name }), - ...(email && { email }), - ...(department && { department }), - ...(username && { username }), - ...(connectionData && { connectionData }), - ...(phone && typeof phone === 'string' && { phone: { number: phone as string } }), - connectionData: normalizeHttpHeaderData(this.request.headers), - }; - - const visitor = await LivechatTyped.registerGuest(guest); - if (!visitor) { - throw new Meteor.Error('error-livechat-visitor-registration', 'Error registering visitor', { - method: 'livechat/visitor', +API.v1.addRoute( + 'livechat/visitor', + { + rateLimiterOptions: { + numRequestsAllowed: 5, + intervalTimeInMS: 60000, + }, + }, + { + async post() { + check(this.bodyParams, { + visitor: Match.ObjectIncluding({ + token: String, + name: Match.Maybe(String), + email: Match.Maybe(String), + department: Match.Maybe(String), + phone: Match.Maybe(String), + username: Match.Maybe(String), + customFields: Match.Maybe([ + Match.ObjectIncluding({ + key: String, + value: String, + overwrite: Boolean, + }), + ]), + }), }); - } - const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}); - // If it's updating an existing visitor, it must also update the roomInfo - const rooms = await LivechatRooms.findOpenByVisitorToken(visitor?.token, {}, extraQuery).toArray(); - await Promise.all( - rooms.map( - (room: IRoom) => - visitor && - LivechatTyped.saveRoomInfo(room, { - _id: visitor._id, - name: visitor.name, - phone: visitor.phone?.[0]?.phoneNumber, - livechatData: visitor.livechatData as { [k: string]: string }, - }), - ), - ); - - if (customFields && Array.isArray(customFields) && customFields.length > 0) { - const keys = customFields.map((field) => field.key); - const errors: string[] = []; - - const processedKeys = await Promise.all( - await LivechatCustomField.findByIdsAndScope>(keys, 'visitor', { - projection: { _id: 1 }, - }) - .map(async (field) => { - const customField = customFields.find((f) => f.key === field._id); - if (!customField) { - return; - } - - const { key, value, overwrite } = customField; - // TODO: Change this to Bulk update - if (!(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { - errors.push(key); - } - - return key; - }) - .toArray(), - ); + const { customFields, id, token, name, email, department, phone, username, connectionData } = this.bodyParams.visitor; - if (processedKeys.length !== keys.length) { - LivechatTyped.logger.warn({ - msg: 'Some custom fields were not processed', - visitorId: visitor._id, - missingKeys: keys.filter((key) => !processedKeys.includes(key)), - }); + if (!token?.trim()) { + throw new Meteor.Error('error-invalid-token', 'Token cannot be empty', { method: 'livechat/visitor' }); } - if (errors.length > 0) { - LivechatTyped.logger.error({ - msg: 'Error updating custom fields', - visitorId: visitor._id, - errors, + const guest = { + token, + ...(id && { id }), + ...(name && { name }), + ...(email && { email }), + ...(department && { department }), + ...(username && { username }), + ...(connectionData && { connectionData }), + ...(phone && typeof phone === 'string' && { phone: { number: phone as string } }), + connectionData: normalizeHttpHeaderData(this.request.headers), + }; + + const visitor = await LivechatTyped.registerGuest(guest); + if (!visitor) { + throw new Meteor.Error('error-livechat-visitor-registration', 'Error registering visitor', { + method: 'livechat/visitor', }); - throw new Error('error-updating-custom-fields'); } - return API.v1.success({ visitor: await VisitorsRaw.findOneEnabledById(visitor._id) }); - } + const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}); + // If it's updating an existing visitor, it must also update the roomInfo + const rooms = await LivechatRooms.findOpenByVisitorToken(visitor?.token, {}, extraQuery).toArray(); + await Promise.all( + rooms.map( + (room: IRoom) => + visitor && + LivechatTyped.saveRoomInfo(room, { + _id: visitor._id, + name: visitor.name, + phone: visitor.phone?.[0]?.phoneNumber, + livechatData: visitor.livechatData as { [k: string]: string }, + }), + ), + ); - if (!visitor) { - throw new Meteor.Error('error-saving-visitor', 'An error ocurred while saving visitor'); - } + if (customFields && Array.isArray(customFields) && customFields.length > 0) { + const keys = customFields.map((field) => field.key); + const errors: string[] = []; - return API.v1.success({ visitor }); + const processedKeys = await Promise.all( + await LivechatCustomField.findByIdsAndScope>(keys, 'visitor', { + projection: { _id: 1 }, + }) + .map(async (field) => { + const customField = customFields.find((f) => f.key === field._id); + if (!customField) { + return; + } + + const { key, value, overwrite } = customField; + // TODO: Change this to Bulk update + if (!(await VisitorsRaw.updateLivechatDataByToken(token, key, value, overwrite))) { + errors.push(key); + } + + return key; + }) + .toArray(), + ); + + if (processedKeys.length !== keys.length) { + LivechatTyped.logger.warn({ + msg: 'Some custom fields were not processed', + visitorId: visitor._id, + missingKeys: keys.filter((key) => !processedKeys.includes(key)), + }); + } + + if (errors.length > 0) { + LivechatTyped.logger.error({ + msg: 'Error updating custom fields', + visitorId: visitor._id, + errors, + }); + throw new Error('error-updating-custom-fields'); + } + + return API.v1.success({ visitor: await VisitorsRaw.findOneEnabledById(visitor._id) }); + } + + if (!visitor) { + throw new Meteor.Error('error-saving-visitor', 'An error ocurred while saving visitor'); + } + + return API.v1.success({ visitor }); + }, }, -}); +); API.v1.addRoute('livechat/visitor/:token', { async get() { diff --git a/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts b/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts index a6031bd42efa..2c718c058b00 100644 --- a/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts +++ b/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts @@ -58,14 +58,17 @@ export const getSecondsSinceLastAgentResponse = async (room: IOmnichannelRoom, a for (let index = 0; index <= daysOfInactivity; index++) { const today = inactivityDay.clone().format('dddd'); const officeDay = officeDays[today]; + if (!officeDay) { inactivityDay.add(1, 'days'); continue; } + if (!officeDay.open) { inactivityDay.add(1, 'days'); continue; } + if (!officeDay?.start?.time || !officeDay?.finish?.time) { inactivityDay.add(1, 'days'); continue; diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts index 4f4a33ee61b2..f6f812ce8af8 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.ts +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -1,4 +1,11 @@ -import type { ILivechatContactChannel, ILivechatCustomField, ILivechatVisitor, IOmnichannelRoom, IUser } from '@rocket.chat/core-typings'; +import type { + ILivechatContact, + ILivechatContactChannel, + ILivechatCustomField, + ILivechatVisitor, + IOmnichannelRoom, + IUser, +} from '@rocket.chat/core-typings'; import { LivechatVisitors, Users, @@ -37,14 +44,24 @@ type RegisterContactProps = { type CreateContactParams = { name: string; - emails: string[]; - phones: string[]; + emails?: string[]; + phones?: string[]; unknown: boolean; customFields?: Record; contactManager?: string; channels?: ILivechatContactChannel[]; }; +type UpdateContactParams = { + contactId: string; + name?: string; + emails?: string[]; + phones?: string[]; + customFields?: Record; + contactManager?: string; + channels?: ILivechatContactChannel[]; +}; + export const Contacts = { async registerContact({ token, @@ -189,10 +206,7 @@ export async function createContact(params: CreateContactParams): Promise>(contactManager, { projection: { roles: 1 } }); - if (!contactManagerUser) { - throw new Error('error-contact-manager-not-found'); - } + await validateContactManager(contactManager); } const allowedCustomFields = await getAllowedCustomFields(); @@ -211,6 +225,29 @@ export async function createContact(params: CreateContactParams): Promise { + const { contactId, name, emails, phones, customFields, contactManager, channels } = params; + + const contact = await LivechatContacts.findOneById>(contactId, { projection: { _id: 1 } }); + + if (!contact) { + throw new Error('error-contact-not-found'); + } + + if (contactManager) { + await validateContactManager(contactManager); + } + + if (customFields) { + const allowedCustomFields = await getAllowedCustomFields(); + validateCustomFields(allowedCustomFields, customFields); + } + + const updatedContact = await LivechatContacts.updateContact(contactId, { name, emails, phones, contactManager, channels, customFields }); + + return updatedContact; +} + async function getAllowedCustomFields(): Promise { return LivechatCustomField.findByScope( 'visitor', @@ -245,4 +282,18 @@ export function validateCustomFields(allowedCustomFields: ILivechatCustomField[] } } } + + const allowedCustomFieldIds = new Set(allowedCustomFields.map((cf) => cf._id)); + for (const key in customFields) { + if (!allowedCustomFieldIds.has(key)) { + throw new Error(i18n.t('error-custom-field-not-allowed', { key })); + } + } +} + +export async function validateContactManager(contactManagerUserId: string) { + const contactManagerUser = await Users.findOneAgentById>(contactManagerUserId, { projection: { _id: 1 } }); + if (!contactManagerUser) { + throw new Error('error-contact-manager-not-found'); + } } diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 17f21d8d7b04..017eb516a487 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -439,8 +439,8 @@ export const dispatchInquiryQueued = async (inquiry: ILivechatInquiryRecord, age hasMentionToHere: false, message: { _id: '', u: v, msg: '' }, // we should use server's language for this type of messages instead of user's - notificationMessage: i18n.t('User_started_a_new_conversation', { username: notificationUserName }, language), - room: Object.assign(room, { name: i18n.t('New_chat_in_queue', {}, language) }), + notificationMessage: i18n.t('User_started_a_new_conversation', { username: notificationUserName, lng: language }), + room: Object.assign(room, { name: i18n.t('New_chat_in_queue', { lng: language }) }), mentionIds: [], }); } diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index be79d565f6de..89d125033977 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -71,6 +71,7 @@ import * as Mailer from '../../../mailer/server/api'; import { metrics } from '../../../metrics/server'; import { settings } from '../../../settings/server'; import { businessHourManager } from '../business-hour'; +import { createContact } from './Contacts'; import { parseAgentCustomFields, updateDepartmentAgents, validateEmail, normalizeTransferredByData } from './Helper'; import { QueueManager } from './QueueManager'; import { RoutingManager } from './RoutingManager'; @@ -664,6 +665,16 @@ class LivechatClass { } } + if (process.env.TEST_MODE?.toUpperCase() === 'TRUE') { + const contactId = await createContact({ + name: name ?? (visitorDataToUpdate.username as string), + emails: email ? [email] : [], + phones: phone ? [phone.number] : [], + unknown: true, + }); + visitorDataToUpdate.contactId = contactId; + } + const upsertedLivechatVisitor = await LivechatVisitors.updateOneByIdOrToken(visitorDataToUpdate, { upsert: true, returnDocument: 'after', @@ -1068,6 +1079,7 @@ class LivechatClass { 'Livechat_background', 'Assets_livechat_widget_logo', 'Livechat_hide_watermark', + 'Omnichannel_allow_visitors_to_close_conversation', ] as const; type SettingTypes = (typeof validSettings)[number] | 'Livechat_Show_Connecting'; diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index e1ea79d84163..c6728d470870 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -371,8 +371,8 @@ export class QueueManager { hasMentionToHere: false, message: { _id: '', u: v, msg: '' }, // we should use server's language for this type of messages instead of user's - notificationMessage: i18n.t('User_started_a_new_conversation', { username: notificationUserName }, language), - room: { ...room, name: i18n.t('New_chat_in_queue', {}, language) }, + notificationMessage: i18n.t('User_started_a_new_conversation', { username: notificationUserName, lng: language }), + room: { ...room, name: i18n.t('New_chat_in_queue', { lng: language }) }, mentionIds: [], }); } diff --git a/apps/meteor/app/livechat/server/lib/sendTranscript.ts b/apps/meteor/app/livechat/server/lib/sendTranscript.ts index 74032121ee50..bc7c06e0eaae 100644 --- a/apps/meteor/app/livechat/server/lib/sendTranscript.ts +++ b/apps/meteor/app/livechat/server/lib/sendTranscript.ts @@ -3,12 +3,13 @@ import { type IUser, type MessageTypesValues, type IOmnichannelSystemMessage, + type ILivechatVisitor, isFileAttachment, isFileImageAttachment, } from '@rocket.chat/core-typings'; import colors from '@rocket.chat/fuselage-tokens/colors'; import { Logger } from '@rocket.chat/logger'; -import { LivechatRooms, LivechatVisitors, Messages, Uploads, Users } from '@rocket.chat/models'; +import { LivechatRooms, Messages, Uploads, Users } from '@rocket.chat/models'; import { check } from 'meteor/check'; import moment from 'moment-timezone'; @@ -41,16 +42,12 @@ export async function sendTranscript({ const room = await LivechatRooms.findOneById(rid); - const visitor = await LivechatVisitors.getVisitorByToken(token, { - projection: { _id: 1, token: 1, language: 1, username: 1, name: 1 }, - }); - - if (!visitor) { - throw new Error('error-invalid-token'); + const visitor = room?.v as ILivechatVisitor; + if (token !== visitor?.token) { + throw new Error('error-invalid-visitor'); } - // @ts-expect-error - Visitor typings should include language? - const userLanguage = visitor?.language || settings.get('Language') || 'en'; + const userLanguage = settings.get('Language') || 'en'; const timezone = getTimezone(user); logger.debug(`Transcript will be sent using ${timezone} as timezone`); @@ -59,7 +56,7 @@ export async function sendTranscript({ } // allow to only user to send transcripts from their own chats - if (room.t !== 'l' || !room.v || room.v.token !== token) { + if (room.t !== 'l') { throw new Error('error-invalid-room'); } diff --git a/apps/meteor/app/models/client/index.ts b/apps/meteor/app/models/client/index.ts index 354baa2b71fd..397a1e45bb18 100644 --- a/apps/meteor/app/models/client/index.ts +++ b/apps/meteor/app/models/client/index.ts @@ -11,7 +11,6 @@ import CustomSounds from './models/CustomSounds'; import EmojiCustom from './models/EmojiCustom'; import { Roles } from './models/Roles'; import { RoomRoles } from './models/RoomRoles'; -import { UserAndRoom } from './models/UserAndRoom'; import { UserRoles } from './models/UserRoles'; import { Users } from './models/Users'; @@ -23,22 +22,21 @@ export { CachedChatSubscription, CachedUserList, RoomRoles, - UserAndRoom, UserRoles, AuthzCachedCollection, ChatPermissions, CustomSounds, EmojiCustom, - /** @deprecated */ + /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ Users, - /** @deprecated */ + /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ ChatRoom, - /** @deprecated */ + /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ ChatSubscription, - /** @deprecated */ + /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ ChatSubscription as Subscriptions, - /** @deprecated */ + /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ ChatMessage, - /** @deprecated */ + /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ ChatMessage as Messages, }; diff --git a/apps/meteor/app/models/client/models/CachedChatRoom.ts b/apps/meteor/app/models/client/models/CachedChatRoom.ts index 852bed5a6067..248b68554583 100644 --- a/apps/meteor/app/models/client/models/CachedChatRoom.ts +++ b/apps/meteor/app/models/client/models/CachedChatRoom.ts @@ -110,6 +110,6 @@ class CachedChatRoom extends CachedCollection { const instance = new CachedChatRoom(); export { - /** @deprecated */ + /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ instance as CachedChatRoom, }; diff --git a/apps/meteor/app/models/client/models/CachedChatSubscription.ts b/apps/meteor/app/models/client/models/CachedChatSubscription.ts index 7c0e84800c77..d17493e57f2e 100644 --- a/apps/meteor/app/models/client/models/CachedChatSubscription.ts +++ b/apps/meteor/app/models/client/models/CachedChatSubscription.ts @@ -139,6 +139,6 @@ class CachedChatSubscription extends CachedCollection['queries']; } -/** @deprecated */ +/** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ export const ChatMessage = new ChatMessageCollection(); diff --git a/apps/meteor/app/models/client/models/ChatRoom.ts b/apps/meteor/app/models/client/models/ChatRoom.ts index 00528233daed..f23f20e8048e 100644 --- a/apps/meteor/app/models/client/models/ChatRoom.ts +++ b/apps/meteor/app/models/client/models/ChatRoom.ts @@ -2,7 +2,7 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { CachedChatRoom } from './CachedChatRoom'; -/** @deprecated */ +/** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ export const ChatRoom = Object.assign(CachedChatRoom.collection, { setReactionsInLastMessage(this: typeof CachedChatRoom.collection, roomId: IRoom['_id'], lastMessage: IMessage) { return this.update({ _id: roomId }, { $set: { lastMessage } }); diff --git a/apps/meteor/app/models/client/models/ChatSubscription.ts b/apps/meteor/app/models/client/models/ChatSubscription.ts index 023f9d8f4649..aa7892fe5031 100644 --- a/apps/meteor/app/models/client/models/ChatSubscription.ts +++ b/apps/meteor/app/models/client/models/ChatSubscription.ts @@ -6,7 +6,7 @@ import type { Filter } from 'mongodb'; import { isTruthy } from '../../../../lib/isTruthy'; import { CachedChatSubscription } from './CachedChatSubscription'; -/** @deprecated */ +/** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ export const ChatSubscription = Object.assign(CachedChatSubscription.collection, { isUserInRole: mem( function (this: typeof CachedChatSubscription.collection, _uid: IUser['_id'], roleId: IRole['_id'], rid?: IRoom['_id']) { diff --git a/apps/meteor/app/models/client/models/Roles.ts b/apps/meteor/app/models/client/models/Roles.ts index 475c0465c7cc..a048905cd2b4 100644 --- a/apps/meteor/app/models/client/models/Roles.ts +++ b/apps/meteor/app/models/client/models/Roles.ts @@ -53,5 +53,5 @@ class RolesCollection extends Mongo.Collection implements MinimongoCollec public declare queries: MinimongoCollection['queries']; } -/** @deprecated */ +/** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ export const Roles = new RolesCollection(); diff --git a/apps/meteor/app/models/client/models/RoomRoles.ts b/apps/meteor/app/models/client/models/RoomRoles.ts index bb9fabbedbf2..ab347a59eada 100644 --- a/apps/meteor/app/models/client/models/RoomRoles.ts +++ b/apps/meteor/app/models/client/models/RoomRoles.ts @@ -1,5 +1,5 @@ import type { ISubscription } from '@rocket.chat/core-typings'; import { Mongo } from 'meteor/mongo'; -/** @deprecated */ +/** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ export const RoomRoles = new Mongo.Collection>(null); diff --git a/apps/meteor/app/models/client/models/UserAndRoom.ts b/apps/meteor/app/models/client/models/UserAndRoom.ts deleted file mode 100644 index f43b2c0fdbe0..000000000000 --- a/apps/meteor/app/models/client/models/UserAndRoom.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -export const UserAndRoom = new Mongo.Collection(null); diff --git a/apps/meteor/app/models/client/models/UserRoles.ts b/apps/meteor/app/models/client/models/UserRoles.ts index 9e68239bdb44..04a1710e8b9c 100644 --- a/apps/meteor/app/models/client/models/UserRoles.ts +++ b/apps/meteor/app/models/client/models/UserRoles.ts @@ -1,7 +1,7 @@ import type { IRocketChatRecord, IRole } from '@rocket.chat/core-typings'; import { Mongo } from 'meteor/mongo'; -/** @deprecated */ +/** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ export const UserRoles = new Mongo.Collection< IRocketChatRecord & { roles?: IRole['_id'][]; diff --git a/apps/meteor/app/models/client/models/Users.ts b/apps/meteor/app/models/client/models/Users.ts index 26d333cc8bb1..e2d8c7856752 100644 --- a/apps/meteor/app/models/client/models/Users.ts +++ b/apps/meteor/app/models/client/models/Users.ts @@ -38,5 +38,5 @@ Object.assign(Meteor.users, { remove: UsersCollection.prototype.remove, }); -/** @deprecated */ +/** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ export const Users = Meteor.users as UsersCollection; diff --git a/apps/meteor/app/otr/server/methods/updateOTRAck.ts b/apps/meteor/app/otr/server/methods/updateOTRAck.ts index 4fbd182e9d27..64e5e97fa4e5 100644 --- a/apps/meteor/app/otr/server/methods/updateOTRAck.ts +++ b/apps/meteor/app/otr/server/methods/updateOTRAck.ts @@ -1,8 +1,12 @@ import { api } from '@rocket.chat/core-services'; import type { IOTRMessage } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { Rooms } from '@rocket.chat/models'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { canAccessRoomAsync } from '../../../authorization/server/functions/canAccessRoom'; + declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -11,10 +15,40 @@ declare module '@rocket.chat/ddp-client' { } Meteor.methods({ - updateOTRAck({ message, ack }) { - if (!Meteor.userId()) { + async updateOTRAck({ message, ack }) { + const uid = Meteor.userId(); + if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'updateOTRAck' }); } + + check(ack, String); + check(message, { + _id: String, + rid: String, + msg: String, + t: String, + ts: Date, + u: { + _id: String, + username: String, + name: String, + }, + }); + + if (message?.t !== 'otr') { + throw new Meteor.Error('error-invalid-message', 'Invalid message type', { method: 'updateOTRAck' }); + } + + const room = await Rooms.findOneByIdAndType(message.rid, 'd', { projection: { t: 1, _id: 1, uids: 1 } }); + + if (!room) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'updateOTRAck' }); + } + + if (!(await canAccessRoomAsync(room, { _id: uid })) || (room.uids && (!message.u._id || !room.uids.includes(message.u._id)))) { + throw new Meteor.Error('error-invalid-user', 'Invalid user, not in room', { method: 'updateOTRAck' }); + } + const acknowledgeMessage: IOTRMessage = { ...message, otrAck: ack }; void api.broadcast('otrAckUpdate', { roomId: message.rid, acknowledgeMessage }); }, diff --git a/apps/meteor/app/ui-message/client/messageBox/AddLinkComposerActionModal.tsx b/apps/meteor/app/ui-message/client/messageBox/AddLinkComposerActionModal.tsx index 420bf93df66d..21e502c90001 100644 --- a/apps/meteor/app/ui-message/client/messageBox/AddLinkComposerActionModal.tsx +++ b/apps/meteor/app/ui-message/client/messageBox/AddLinkComposerActionModal.tsx @@ -1,8 +1,8 @@ import { Field, FieldGroup, TextInput, FieldLabel, FieldRow, Box } from '@rocket.chat/fuselage'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useEffect } from 'react'; import { useForm, Controller } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import GenericModal from '../../../../client/components/GenericModal'; @@ -13,7 +13,7 @@ type AddLinkComposerActionModalProps = { }; const AddLinkComposerActionModal = ({ selectedText, onClose, onConfirm }: AddLinkComposerActionModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); const textField = useUniqueId(); const urlField = useUniqueId(); diff --git a/apps/meteor/app/utils/lib/i18n.ts b/apps/meteor/app/utils/lib/i18n.ts index b69fe6b30513..737b98666d0a 100644 --- a/apps/meteor/app/utils/lib/i18n.ts +++ b/apps/meteor/app/utils/lib/i18n.ts @@ -1,4 +1,5 @@ import type { RocketchatI18nKeys } from '@rocket.chat/i18n'; +import type { TOptions } from 'i18next'; import i18next from 'i18next'; import sprintf from 'i18next-sprintf-postprocessor'; @@ -13,7 +14,7 @@ export const addSprinfToI18n = function (t: (typeof i18n)['t']) { } if (isObject(replaces[0]) && !Array.isArray(replaces[0])) { - return t(key, replaces[0]); + return t(key, replaces[0] as TOptions); } return t(key, { diff --git a/apps/meteor/app/utils/rocketchat.info b/apps/meteor/app/utils/rocketchat.info index faf0e8f47de6..cb3ad01b882a 100644 --- a/apps/meteor/app/utils/rocketchat.info +++ b/apps/meteor/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "6.12.0-develop" + "version": "6.13.0-develop" } diff --git a/apps/meteor/app/utils/server/functions/getBaseUserFields.ts b/apps/meteor/app/utils/server/functions/getBaseUserFields.ts new file mode 100644 index 000000000000..5e2a3bf2b4d7 --- /dev/null +++ b/apps/meteor/app/utils/server/functions/getBaseUserFields.ts @@ -0,0 +1,34 @@ +type UserFields = { + [k: string]: number; +}; + +export const getBaseUserFields = (): UserFields => ({ + 'name': 1, + 'username': 1, + 'nickname': 1, + 'emails': 1, + 'status': 1, + 'statusDefault': 1, + 'statusText': 1, + 'statusConnection': 1, + 'bio': 1, + 'avatarOrigin': 1, + 'utcOffset': 1, + 'language': 1, + 'settings': 1, + 'enableAutoAway': 1, + 'idleTimeLimit': 1, + 'roles': 1, + 'active': 1, + 'defaultRoom': 1, + 'customFields': 1, + 'requirePasswordChange': 1, + 'requirePasswordChangeReason': 1, + 'statusLivechat': 1, + 'banners': 1, + 'oauth.authorizedClients': 1, + '_updatedAt': 1, + 'avatarETag': 1, + 'extension': 1, + 'openBusinessHours': 1, +}); diff --git a/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts b/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts index 03d0cae77ab9..293eb8607342 100644 --- a/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts +++ b/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts @@ -1,39 +1,14 @@ -type DefaultUserFields = { +import { getBaseUserFields } from './getBaseUserFields'; + +type UserFields = { [k: string]: number; }; -export const getDefaultUserFields = (): DefaultUserFields => ({ - 'name': 1, - 'username': 1, - 'nickname': 1, - 'emails': 1, - 'status': 1, - 'statusDefault': 1, - 'statusText': 1, - 'statusConnection': 1, - 'bio': 1, - 'avatarOrigin': 1, - 'utcOffset': 1, - 'language': 1, - 'settings': 1, - 'enableAutoAway': 1, - 'idleTimeLimit': 1, - 'roles': 1, - 'active': 1, - 'defaultRoom': 1, - 'customFields': 1, - 'requirePasswordChange': 1, - 'requirePasswordChangeReason': 1, +export const getDefaultUserFields = (): UserFields => ({ + ...getBaseUserFields(), 'services.github': 1, 'services.gitlab': 1, 'services.password.bcrypt': 1, 'services.totp.enabled': 1, 'services.email2fa.enabled': 1, - 'statusLivechat': 1, - 'banners': 1, - 'oauth.authorizedClients': 1, - '_updatedAt': 1, - 'avatarETag': 1, - 'extension': 1, - 'openBusinessHours': 1, }); diff --git a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmniChannelCallDialPad.tsx b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmniChannelCallDialPad.tsx index af9b907df12e..2693060578ed 100644 --- a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmniChannelCallDialPad.tsx +++ b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmniChannelCallDialPad.tsx @@ -1,7 +1,7 @@ import { NavBarItem } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentPropsWithoutRef } from 'react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { useVoipOutboundStates } from '../../contexts/CallContext'; import { useDialModal } from '../../hooks/useDialModal'; @@ -9,7 +9,7 @@ import { useDialModal } from '../../hooks/useDialModal'; type NavBarItemOmniChannelCallDialPadProps = ComponentPropsWithoutRef; const NavBarItemOmniChannelCallDialPad = (props: NavBarItemOmniChannelCallDialPadProps) => { - const t = useTranslation(); + const { t } = useTranslation(); const { openDialModal } = useDialModal(); diff --git a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleError.tsx b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleError.tsx index cf4e7ec240b4..7f2b6adc8691 100644 --- a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleError.tsx +++ b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleError.tsx @@ -1,12 +1,12 @@ import { NavBarItem } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentPropsWithoutRef } from 'react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; type NavBarItemOmnichannelCallToggleErrorProps = ComponentPropsWithoutRef; const NavBarItemOmnichannelCallToggleError = (props: NavBarItemOmnichannelCallToggleErrorProps) => { - const t = useTranslation(); + const { t } = useTranslation(); return ; }; diff --git a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleLoading.tsx b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleLoading.tsx index c4b53acefabb..149500050402 100644 --- a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleLoading.tsx +++ b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleLoading.tsx @@ -1,12 +1,12 @@ import { NavBarItem } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentPropsWithoutRef } from 'react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; type NavBarItemOmnichannelCallToggleLoadingProps = ComponentPropsWithoutRef; const NavBarItemOmnichannelCallToggleLoading = (props: NavBarItemOmnichannelCallToggleLoadingProps) => { - const t = useTranslation(); + const { t } = useTranslation(); return ; }; diff --git a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleReady.tsx b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleReady.tsx index 8b51fc6c5b57..82f1c28350cd 100644 --- a/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleReady.tsx +++ b/apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/NavBarItemOmnichannelCallToggleReady.tsx @@ -1,14 +1,14 @@ import { NavBarItem } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentPropsWithoutRef } from 'react'; import React, { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { useCallerInfo, useCallRegisterClient, useCallUnregisterClient, useVoipNetworkStatus } from '../../contexts/CallContext'; type NavBarItemOmnichannelCallToggleReadyProps = ComponentPropsWithoutRef; const NavBarItemOmnichannelCallToggleReady = (props: NavBarItemOmnichannelCallToggleReadyProps) => { - const t = useTranslation(); + const { t } = useTranslation(); const caller = useCallerInfo(); const unregister = useCallUnregisterClient(); diff --git a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemAuditMenu.tsx b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemAuditMenu.tsx index 07936f6f4276..7c8a50338e7d 100644 --- a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemAuditMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemAuditMenu.tsx @@ -1,9 +1,9 @@ import { NavBarItem } from '@rocket.chat/fuselage'; +import { GenericMenu } from '@rocket.chat/ui-client'; import { useCurrentRoutePath, useTranslation } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes } from 'react'; import React from 'react'; -import GenericMenu from '../../components/GenericMenu/GenericMenu'; import { useAuditMenu } from './hooks/useAuditMenu'; type NavBarItemAuditMenuProps = Omit, 'is'>; diff --git a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemMarketPlaceMenu.tsx b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemMarketPlaceMenu.tsx index 4a2bbc916b57..85687bb12a2e 100644 --- a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemMarketPlaceMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/NavBarItemMarketPlaceMenu.tsx @@ -1,9 +1,9 @@ import { NavBarItem } from '@rocket.chat/fuselage'; +import { GenericMenu } from '@rocket.chat/ui-client'; import { useCurrentRoutePath, useTranslation } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes } from 'react'; import React from 'react'; -import GenericMenu from '../../components/GenericMenu/GenericMenu'; import { useMarketPlaceMenu } from './hooks/useMarketPlaceMenu'; type NavBarItemMarketPlaceMenuProps = Omit, 'is'>; diff --git a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.tsx b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.tsx index 88a2a5de31aa..97c8d7299497 100644 --- a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.tsx @@ -1,6 +1,6 @@ +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { usePermission, useRouter, useTranslation } from '@rocket.chat/ui-contexts'; -import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; import { useHasLicenseModule } from '../../../hooks/useHasLicenseModule'; export const useAuditMenu = () => { diff --git a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx index fd704ffafe1f..034ab0367e81 100644 --- a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx @@ -1,8 +1,8 @@ import { Badge, Skeleton } from '@rocket.chat/fuselage'; +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useTranslation, usePermission, useRouter } from '@rocket.chat/ui-contexts'; import React from 'react'; -import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; import { useUserDropdownAppsActionButtons } from '../../../hooks/useAppActionButtons'; import { useAppRequestStats } from '../../../views/marketplace/hooks/useAppRequestStats'; diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/NavBarItemAdministrationMenu.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/NavBarItemAdministrationMenu.tsx index 045b36425512..8236eec030e8 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/NavBarItemAdministrationMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/NavBarItemAdministrationMenu.tsx @@ -1,9 +1,9 @@ import { NavBarItem } from '@rocket.chat/fuselage'; +import { GenericMenu } from '@rocket.chat/ui-client'; import { useCurrentRoutePath, useTranslation } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes } from 'react'; import React from 'react'; -import GenericMenu from '../../components/GenericMenu/GenericMenu'; import { useAdministrationMenu } from './hooks/useAdministrationMenu'; type NavBarItemAdministrationMenuProps = Omit, 'is'>; diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/UserMenu.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/UserMenu.tsx index 531ff8a74b66..149ad0ea585e 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/UserMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/UserMenu.tsx @@ -1,18 +1,17 @@ import type { IUser } from '@rocket.chat/core-typings'; -import { useTranslation } from '@rocket.chat/ui-contexts'; +import { GenericMenu, useHandleMenuAction } from '@rocket.chat/ui-client'; +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import type { ComponentProps } from 'react'; import React, { memo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; -import GenericMenu from '../../../components/GenericMenu/GenericMenu'; -import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; -import { useHandleMenuAction } from '../../../components/GenericMenu/hooks/useHandleMenuAction'; import UserMenuButton from './UserMenuButton'; import { useUserMenu } from './hooks/useUserMenu'; type UserMenuProps = { user: IUser } & Omit, 'sections' | 'items' | 'title'>; const UserMenu = function UserMenu({ user, ...props }: UserMenuProps) { - const t = useTranslation(); + const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); const sections = useUserMenu(user); diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx index bf1b7e55f244..82c39c5c1b10 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx @@ -1,11 +1,10 @@ import { Badge } from '@rocket.chat/fuselage'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { defaultFeaturesPreview, useFeaturePreviewList } from '@rocket.chat/ui-client'; +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; - export const useAccountItems = (): GenericMenuItemProps[] => { const t = useTranslation(); const router = useRouter(); diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useStatusItems.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useStatusItems.tsx index 2957d22c5e32..1c9cf09e4610 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useStatusItems.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useStatusItems.tsx @@ -1,11 +1,11 @@ import { Box } from '@rocket.chat/fuselage'; +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useEndpoint, useSetting } from '@rocket.chat/ui-contexts'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { callbacks } from '../../../../../lib/callbacks'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import MarkdownText from '../../../../components/MarkdownText'; import { UserStatus } from '../../../../components/UserStatus'; import { userStatuses } from '../../../../lib/userStatuses'; diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx index a969c853d797..85a481f3e257 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx @@ -1,9 +1,9 @@ import type { IUser } from '@rocket.chat/core-typings'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useLogout, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import UserMenuHeader from '../UserMenuHeader'; import { useAccountItems } from './useAccountItems'; import { useStatusItems } from './useStatusItems'; diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.tsx index 54d4818128ea..e3c4a358c7c1 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.tsx @@ -1,7 +1,6 @@ +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useAtLeastOnePermission, usePermission, useRouter, useTranslation } from '@rocket.chat/ui-contexts'; -import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; - const ADMIN_PERMISSIONS = [ 'view-statistics', 'run-import', diff --git a/apps/meteor/client/apps/gameCenter/GameCenterContainer.tsx b/apps/meteor/client/apps/gameCenter/GameCenterContainer.tsx index f589dd21ed50..dbaea02ace4a 100644 --- a/apps/meteor/client/apps/gameCenter/GameCenterContainer.tsx +++ b/apps/meteor/client/apps/gameCenter/GameCenterContainer.tsx @@ -1,7 +1,7 @@ import { Avatar } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { ContextualbarTitle, @@ -19,7 +19,7 @@ interface IGameCenterContainerProps { } const GameCenterContainer = ({ handleClose, handleBack, game }: IGameCenterContainerProps): ReactElement => { - const t = useTranslation(); + const { t } = useTranslation(); return ( <> diff --git a/apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx b/apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx index d0dcc6fad4fe..871e82f3ff56 100644 --- a/apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx +++ b/apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx @@ -1,8 +1,8 @@ import type { IUser } from '@rocket.chat/core-typings'; import { Box } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import GenericModal from '../../components/GenericModal'; import UserAutoCompleteMultipleFederated from '../../components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated'; @@ -19,7 +19,7 @@ interface IGameCenterInvitePlayersModalProps { } const GameCenterInvitePlayersModal = ({ game, onClose }: IGameCenterInvitePlayersModalProps): ReactElement => { - const t = useTranslation(); + const { t } = useTranslation(); const [users, setUsers] = useState>([]); const { name } = game; diff --git a/apps/meteor/client/components/ActionManagerBusyState.tsx b/apps/meteor/client/components/ActionManagerBusyState.tsx index 1399c045271f..932eb08ea502 100644 --- a/apps/meteor/client/components/ActionManagerBusyState.tsx +++ b/apps/meteor/client/components/ActionManagerBusyState.tsx @@ -1,12 +1,12 @@ import { css } from '@rocket.chat/css-in-js'; import { Box } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager'; const ActionManagerBusyState = () => { - const t = useTranslation(); + const { t } = useTranslation(); const actionManager = useUiKitActionManager(); const [busy, setBusy] = useState(false); diff --git a/apps/meteor/client/components/AutoCompleteDepartment.tsx b/apps/meteor/client/components/AutoCompleteDepartment.tsx index 6217f1d99610..0c50f2254aac 100644 --- a/apps/meteor/client/components/AutoCompleteDepartment.tsx +++ b/apps/meteor/client/components/AutoCompleteDepartment.tsx @@ -1,8 +1,8 @@ import { PaginatedSelectFiltered } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps, ReactElement } from 'react'; import React, { memo, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useRecordList } from '../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../hooks/useAsyncState'; @@ -28,7 +28,7 @@ const AutoCompleteDepartment = ({ showArchived = false, ...props }: AutoCompleteDepartmentProps): ReactElement | null => { - const t = useTranslation(); + const { t } = useTranslation(); const [departmentsFilter, setDepartmentsFilter] = useState(''); const debouncedDepartmentsFilter = useDebouncedValue(departmentsFilter, 500); diff --git a/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx b/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx index 99af9a1f6a2c..c15d480f3900 100644 --- a/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx +++ b/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx @@ -1,9 +1,9 @@ import { CheckOption, PaginatedMultiSelectFiltered } from '@rocket.chat/fuselage'; import type { PaginatedMultiSelectOption } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; import React, { memo, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useRecordList } from '../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../hooks/useAsyncState'; @@ -24,7 +24,7 @@ const AutoCompleteDepartmentMultiple = ({ enabled = false, onChange = () => undefined, }: AutoCompleteDepartmentMultipleProps) => { - const t = useTranslation(); + const { t } = useTranslation(); const [departmentsFilter, setDepartmentsFilter] = useState(''); const debouncedDepartmentsFilter = useDebouncedValue(departmentsFilter, 500); diff --git a/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx b/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx index 349341baf003..77135fad6230 100644 --- a/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx +++ b/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentPropsWithoutRef } from 'react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import GenericModal from './GenericModal'; import RawText from './RawText'; @@ -20,7 +20,7 @@ const ConfirmOwnerChangeModal = ({ onConfirm, onCancel, }: ConfirmOwnerChangeModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); let changeOwnerRooms = ''; if (shouldChangeOwner.length > 0) { diff --git a/apps/meteor/client/components/Contextualbar/ContextualbarBack.tsx b/apps/meteor/client/components/Contextualbar/ContextualbarBack.tsx index c8e17ab88d80..dcac448b1e92 100644 --- a/apps/meteor/client/components/Contextualbar/ContextualbarBack.tsx +++ b/apps/meteor/client/components/Contextualbar/ContextualbarBack.tsx @@ -1,13 +1,13 @@ -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement, ComponentProps } from 'react'; import React, { memo } from 'react'; +import { useTranslation } from 'react-i18next'; import ContextualbarAction from './ContextualbarAction'; type ContextualbarBackProps = Partial>; const ContextualbarBack = (props: ContextualbarBackProps): ReactElement => { - const t = useTranslation(); + const { t } = useTranslation(); return ; }; diff --git a/apps/meteor/client/components/Contextualbar/ContextualbarClose.tsx b/apps/meteor/client/components/Contextualbar/ContextualbarClose.tsx index 1670c9be5895..38db516476e3 100644 --- a/apps/meteor/client/components/Contextualbar/ContextualbarClose.tsx +++ b/apps/meteor/client/components/Contextualbar/ContextualbarClose.tsx @@ -1,13 +1,13 @@ -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps, ReactElement } from 'react'; import React, { memo } from 'react'; +import { useTranslation } from 'react-i18next'; import ContextualbarAction from './ContextualbarAction'; type ContextualbarCloseProps = Partial>; const ContextualbarClose = (props: ContextualbarCloseProps): ReactElement => { - const t = useTranslation(); + const { t } = useTranslation(); return ; }; diff --git a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx index cd39a187cd89..b1dcb9d8dfbc 100644 --- a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx +++ b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx @@ -120,7 +120,7 @@ const CreateDiscussion = ({ onClose, defaultParentRoom, parentMessageId, nameSug ( ( void; @@ -13,7 +13,7 @@ const FilterByText = forwardRef(function Fi { placeholder, onChange: setFilter, shouldAutoFocus = false, children, ...props }, ref, ) { - const t = useTranslation(); + const { t } = useTranslation(); const [text, setText] = useState(''); const autoFocusRef = useAutoFocus(shouldAutoFocus); const mergedRefs = useMergedRefs(ref, autoFocusRef); diff --git a/apps/meteor/client/components/FingerprintChangeModal.tsx b/apps/meteor/client/components/FingerprintChangeModal.tsx index db4c33654a92..a45a17db8ccc 100644 --- a/apps/meteor/client/components/FingerprintChangeModal.tsx +++ b/apps/meteor/client/components/FingerprintChangeModal.tsx @@ -1,7 +1,7 @@ import { Box } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import GenericModal from './GenericModal'; @@ -12,7 +12,7 @@ type FingerprintChangeModalProps = { }; const FingerprintChangeModal = ({ onConfirm, onCancel, onClose }: FingerprintChangeModalProps): ReactElement => { - const t = useTranslation(); + const { t } = useTranslation(); return ( { - const t = useTranslation(); + const { t } = useTranslation(); return ( { - const t = useTranslation(); + const { t } = useTranslation(); return ( diff --git a/apps/meteor/client/components/GenericModal/GenericModal.tsx b/apps/meteor/client/components/GenericModal/GenericModal.tsx index d371e1ff4ef2..d91e3c066007 100644 --- a/apps/meteor/client/components/GenericModal/GenericModal.tsx +++ b/apps/meteor/client/components/GenericModal/GenericModal.tsx @@ -1,9 +1,9 @@ import { Button, Modal } from '@rocket.chat/fuselage'; import { useEffectEvent, useUniqueId } from '@rocket.chat/fuselage-hooks'; import type { Keys as IconName } from '@rocket.chat/icons'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps, ReactElement, ReactNode, ComponentPropsWithoutRef } from 'react'; import React, { useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import type { RequiredModalProps } from './withDoNotAskAgain'; import { withDoNotAskAgain } from './withDoNotAskAgain'; @@ -75,7 +75,7 @@ const GenericModal = ({ annotation, ...props }: GenericModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); const genericModalId = useUniqueId(); const dismissedRef = useRef(true); @@ -111,7 +111,7 @@ const GenericModal = ({ {tagline && {tagline}} {title ?? t('Are_you_sure')} - + {onClose && } {children} diff --git a/apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx b/apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx index d56cbdd26a67..2dcdf3b3578c 100644 --- a/apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx +++ b/apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx @@ -1,25 +1,13 @@ import { Skeleton } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; import React from 'react'; import GenericModal from './GenericModal'; -const GenericModalSkeleton = ({ onClose, ...props }: ComponentProps) => { - const t = useTranslation(); - - return ( - } - confirmText={t('Cancel')} - onConfirm={onClose} - > - - - ); -}; +const GenericModalSkeleton = (props: ComponentProps) => ( + }> + + +); export default GenericModalSkeleton; diff --git a/apps/meteor/client/components/GenericNoResults/GenericNoResults.tsx b/apps/meteor/client/components/GenericNoResults/GenericNoResults.tsx index 3fcfe2b0e0ac..d21023024fb3 100644 --- a/apps/meteor/client/components/GenericNoResults/GenericNoResults.tsx +++ b/apps/meteor/client/components/GenericNoResults/GenericNoResults.tsx @@ -1,7 +1,7 @@ import { Box, States, StatesIcon, StatesLink, StatesTitle, StatesSubtitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; import type { Keys as IconName } from '@rocket.chat/icons'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; +import { useTranslation } from 'react-i18next'; type LinkProps = { linkText: string; linkHref: string } | { linkText?: never; linkHref?: never }; type ButtonProps = { buttonTitle: string; buttonAction: () => void } | { buttonTitle?: never; buttonAction?: never }; @@ -23,7 +23,7 @@ const GenericNoResults = ({ linkHref, linkText, }: GenericNoResultsProps) => { - const t = useTranslation(); + const { t } = useTranslation(); return ( diff --git a/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts b/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts index 0a8a8deb8262..4c390041497e 100644 --- a/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts +++ b/apps/meteor/client/components/GenericTable/hooks/useItemsPerPageLabel.ts @@ -1,7 +1,7 @@ -import { useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; export const useItemsPerPageLabel = (): (() => string) => { - const t = useTranslation(); + const { t } = useTranslation(); return useCallback(() => t('Items_per_page:'), [t]); }; diff --git a/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts b/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts index c610340f28bd..8ff7d2ac18cf 100644 --- a/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts +++ b/apps/meteor/client/components/GenericTable/hooks/useShowingResultsLabel.ts @@ -1,15 +1,19 @@ import type { Pagination } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; type Props['showingResultsLabel'] = ComponentProps['showingResultsLabel']> = T extends (...args: any[]) => any ? Parameters : never; export const useShowingResultsLabel = (): ((...params: Props) => string) => { - const t = useTranslation(); + const { t } = useTranslation(); return useCallback( - ({ count, current, itemsPerPage }) => t('Showing_results_of', current + 1, Math.min(current + itemsPerPage, count), count), + ({ count, current, itemsPerPage }) => + t('Showing_results_of', { + postProcess: 'sprintf', + sprintf: [current + 1, Math.min(current + itemsPerPage, count), count], + }), [t], ); }; diff --git a/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx b/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx index 3d68e3f4b6d3..e7ce515ac496 100644 --- a/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx +++ b/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx @@ -1,8 +1,8 @@ import { Box, Button, Modal } from '@rocket.chat/fuselage'; import type { Keys as IconName } from '@rocket.chat/icons'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactNode, ReactElement, ComponentProps } from 'react'; import React from 'react'; +import { useTranslation } from 'react-i18next'; type GenericUpsellModalProps = { children?: ReactNode; @@ -35,7 +35,7 @@ const GenericUpsellModal = ({ annotation, ...props }: GenericUpsellModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); return ( diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 2cabfed460bd..6db61b2ec910 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -1,10 +1,10 @@ import type { IUpload } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { Box, ButtonGroup, IconButton, Palette, Throbber } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useRef, useState } from 'react'; import { FocusScope } from 'react-aria'; import { createPortal } from 'react-dom'; +import { useTranslation } from 'react-i18next'; import { Keyboard, Navigation, Zoom, A11y } from 'swiper'; import type { SwiperRef } from 'swiper/react'; import { type SwiperClass, Swiper, SwiperSlide } from 'swiper/react'; @@ -108,7 +108,7 @@ const swiperStyle = css` `; export const ImageGallery = ({ images, onClose, loadMore }: { images: IUpload[]; onClose: () => void; loadMore?: () => void }) => { - const t = useTranslation(); + const { t } = useTranslation(); const swiperRef = useRef(null); const [, setSwiperInst] = useState(); const [zoomScale, setZoomScale] = useState(1); diff --git a/apps/meteor/client/components/ImageGallery/ImageGalleryError.tsx b/apps/meteor/client/components/ImageGallery/ImageGalleryError.tsx index 97d91de95f62..8dcc55a93a48 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGalleryError.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGalleryError.tsx @@ -1,8 +1,8 @@ import { css } from '@rocket.chat/css-in-js'; import { IconButton, ModalBackdrop } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import { createPortal } from 'react-dom'; +import { useTranslation } from 'react-i18next'; import GenericError from '../GenericError/GenericError'; @@ -14,7 +14,7 @@ const closeButtonStyle = css` `; export const ImageGalleryError = ({ onClose }: { onClose: () => void }) => { - const t = useTranslation(); + const { t } = useTranslation(); return createPortal( diff --git a/apps/meteor/client/components/ImageGallery/ImageGalleryLoading.tsx b/apps/meteor/client/components/ImageGallery/ImageGalleryLoading.tsx index 1c057584bd1f..588605786664 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGalleryLoading.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGalleryLoading.tsx @@ -1,8 +1,8 @@ import { css } from '@rocket.chat/css-in-js'; import { IconButton, ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import { createPortal } from 'react-dom'; +import { useTranslation } from 'react-i18next'; const closeButtonStyle = css` position: absolute; @@ -12,7 +12,7 @@ const closeButtonStyle = css` `; export const ImageGalleryLoading = ({ onClose }: { onClose: () => void }) => { - const t = useTranslation(); + const { t } = useTranslation(); return createPortal( diff --git a/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.tsx b/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.tsx index cbefeb2c72c1..6f94be4ebc90 100644 --- a/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.tsx +++ b/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.tsx @@ -1,14 +1,14 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { Callout } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { usePruneWarningMessage } from '../../hooks/usePruneWarningMessage'; import { withErrorBoundary } from '../withErrorBoundary'; const RetentionPolicyCallout = ({ room }: { room: IRoom }) => { const message = usePruneWarningMessage(room); - const t = useTranslation(); + const { t } = useTranslation(); return ( diff --git a/apps/meteor/client/components/LocalTime.tsx b/apps/meteor/client/components/LocalTime.tsx index 100ba2ec6a62..498b2da9711e 100644 --- a/apps/meteor/client/components/LocalTime.tsx +++ b/apps/meteor/client/components/LocalTime.tsx @@ -1,6 +1,6 @@ -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; +import { useTranslation } from 'react-i18next'; import { useUTCClock } from '../hooks/useUTCClock'; @@ -10,7 +10,7 @@ type LocalTimeProps = { const LocalTime = ({ utcOffset }: LocalTimeProps): ReactElement => { const time = useUTCClock(utcOffset); - const t = useTranslation(); + const { t } = useTranslation(); return <>{t('Local_Time_time', { time })}; }; diff --git a/apps/meteor/client/components/MarkdownText.tsx b/apps/meteor/client/components/MarkdownText.tsx index c9af942f6e1c..6426b24810ee 100644 --- a/apps/meteor/client/components/MarkdownText.tsx +++ b/apps/meteor/client/components/MarkdownText.tsx @@ -1,10 +1,10 @@ import { Box } from '@rocket.chat/fuselage'; import { isExternal, getBaseURI } from '@rocket.chat/ui-client'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import dompurify from 'dompurify'; import { marked } from 'marked'; import type { ComponentProps } from 'react'; import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { renderMessageEmoji } from '../lib/utils/renderMessageEmoji'; @@ -89,7 +89,7 @@ const MarkdownText = ({ ...props }: MarkdownTextProps) => { const sanitizer = dompurify.sanitize; - const t = useTranslation(); + const { t } = useTranslation(); let markedOptions: marked.MarkedOptions; const schemes = 'http,https,notes,ftp,ftps,tel,mailto,sms,cid'; diff --git a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx index 7c028fb5c876..489eb4a04602 100644 --- a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx @@ -84,11 +84,11 @@ const CloseChatModal = ({ const requestData = transcriptEmail && visitorEmail ? { email: visitorEmail, subject } : undefined; if (!comment?.trim() && commentRequired) { - setError('comment', { type: 'custom', message: t('The_field_is_required', t('Comment')) }); + setError('comment', { type: 'custom', message: t('Required_field', { field: t('Comment') }) }); } if (transcriptEmail && !subject) { - setError('subject', { type: 'custom', message: t('The_field_is_required', t('Subject')) }); + setError('subject', { type: 'custom', message: t('Required_field', { field: t('Subject') }) }); } if (!tags?.length && tagRequired) { @@ -154,12 +154,7 @@ const CloseChatModal = ({ @@ -206,12 +201,7 @@ const CloseChatModal = ({ diff --git a/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx b/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx index 04fcb29eed01..b19ccfee1769 100644 --- a/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx @@ -1,6 +1,6 @@ import { Button, Modal } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; +import { useTranslation } from 'react-i18next'; type ReturnChatQueueModalProps = { onMoveChat: () => void; @@ -8,7 +8,7 @@ type ReturnChatQueueModalProps = { }; const ReturnChatQueueModal = ({ onCancel, onMoveChat, ...props }: ReturnChatQueueModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); return ( diff --git a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx index c06b6a190465..de13ee3aa9a4 100644 --- a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx @@ -1,8 +1,8 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Field, Button, TextInput, Modal, Box, FieldGroup, FieldLabel, FieldRow, FieldError } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useEffect } from 'react'; import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; type TranscriptModalProps = { email: string; @@ -14,7 +14,7 @@ type TranscriptModalProps = { }; const TranscriptModal = ({ email: emailDefault = '', room, onRequest, onSend, onCancel, onDiscard, ...props }: TranscriptModalProps) => { - const t = useTranslation(); + const { t } = useTranslation(); const { register, @@ -74,7 +74,7 @@ const TranscriptModal = ({ email: emailDefault = '', room, onRequest, onSend, on disabled={!!emailDefault || !!transcriptRequest} error={errors.email?.message} flexGrow={1} - {...register('email', { required: t('The_field_is_required', t('Email')) })} + {...register('email', { required: t('Required_field', { field: t('Email') }) })} /> {errors.email?.message} @@ -86,7 +86,7 @@ const TranscriptModal = ({ email: emailDefault = '', room, onRequest, onSend, on disabled={!!transcriptRequest} error={errors.subject?.message} flexGrow={1} - {...register('subject', { required: t('The_field_is_required', t('Subject')) })} + {...register('subject', { required: t('Required_field', { field: t('Subject') }) })} /> {errors.subject?.message} diff --git a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/OmnichannelAppSourceRoomIcon.tsx b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/OmnichannelAppSourceRoomIcon.tsx index 60f3af65eb9d..6a841c30491b 100644 --- a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/OmnichannelAppSourceRoomIcon.tsx +++ b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/OmnichannelAppSourceRoomIcon.tsx @@ -1,53 +1,29 @@ -import { UserStatus, type IOmnichannelRoomFromAppSource } from '@rocket.chat/core-typings'; +import { type IOmnichannelSourceFromApp } from '@rocket.chat/core-typings'; import { Icon, Box } from '@rocket.chat/fuselage'; -import type { ComponentProps, ReactElement } from 'react'; +import type { ComponentProps } from 'react'; import React from 'react'; import { AsyncStatePhase } from '../../../lib/asyncState/AsyncStatePhase'; import { useOmnichannelRoomIcon } from './context/OmnichannelRoomIconContext'; -const colors = { - busy: 'status-font-on-danger', - away: 'status-font-on-warning', - online: 'status-font-on-success', - offline: 'annotation', - disabled: 'annotation', +type OmnichannelAppSourceRoomIconProps = { + source: IOmnichannelSourceFromApp; + color: ComponentProps['color']; + size: ComponentProps['size']; + placement: 'sidebar' | 'default'; }; -const convertBoxSizeToNumber = (boxSize: ComponentProps['size']): number => { - switch (boxSize) { - case 'x20': { - return 20; - } - case 'x24': { - return 24; - } - case 'x16': - default: { - return 16; - } - } -}; +export const OmnichannelAppSourceRoomIcon = ({ source, color, size, placement }: OmnichannelAppSourceRoomIconProps) => { + const icon = (placement === 'sidebar' && source.sidebarIcon) || source.defaultIcon; + const { phase, value } = useOmnichannelRoomIcon(source.id, icon || ''); -export const OmnichannelAppSourceRoomIcon = ({ - room, - size = 16, - placement = 'default', -}: { - room: IOmnichannelRoomFromAppSource; - size: ComponentProps['size']; - placement: 'sidebar' | 'default'; -}): ReactElement => { - const color = colors[room.v.status || UserStatus.OFFLINE]; - const icon = (placement === 'sidebar' && room.source.sidebarIcon) || room.source.defaultIcon; - const { phase, value } = useOmnichannelRoomIcon(room.source.id, icon || ''); - const fontSize = convertBoxSizeToNumber(size); if ([AsyncStatePhase.REJECTED, AsyncStatePhase.LOADING].includes(phase)) { return ; } + return ( - -