diff --git a/.changeset/brave-brooms-invent.md b/.changeset/brave-brooms-invent.md new file mode 100644 index 000000000000..35d32b485944 --- /dev/null +++ b/.changeset/brave-brooms-invent.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixes a problem that caused visitor creation to fail when GDPR setting was enabled and visitor was created via Apps Engine or the deprecated `livechat:registerGuest` method. 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/bump-patch-1727212585363.md b/.changeset/bump-patch-1727212585363.md new file mode 100644 index 000000000000..e1eaa7980afb --- /dev/null +++ b/.changeset/bump-patch-1727212585363.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/.changeset/bump-patch-1727392133102.md b/.changeset/bump-patch-1727392133102.md new file mode 100644 index 000000000000..e1eaa7980afb --- /dev/null +++ b/.changeset/bump-patch-1727392133102.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/.changeset/bump-patch-1727482167703.md b/.changeset/bump-patch-1727482167703.md new file mode 100644 index 000000000000..e1eaa7980afb --- /dev/null +++ b/.changeset/bump-patch-1727482167703.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/.changeset/bump-patch-1727817480079.md b/.changeset/bump-patch-1727817480079.md new file mode 100644 index 000000000000..e1eaa7980afb --- /dev/null +++ b/.changeset/bump-patch-1727817480079.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/.changeset/bump-patch-1728026023781.md b/.changeset/bump-patch-1728026023781.md new file mode 100644 index 000000000000..e1eaa7980afb --- /dev/null +++ b/.changeset/bump-patch-1728026023781.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/.changeset/bump-patch-1728416283220.md b/.changeset/bump-patch-1728416283220.md new file mode 100644 index 000000000000..e1eaa7980afb --- /dev/null +++ b/.changeset/bump-patch-1728416283220.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/.changeset/cyan-ladybugs-thank.md b/.changeset/cyan-ladybugs-thank.md new file mode 100644 index 000000000000..377a014fcb72 --- /dev/null +++ b/.changeset/cyan-ladybugs-thank.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed error during sendmessage client stub diff --git a/.changeset/dirty-stingrays-beg.md b/.changeset/dirty-stingrays-beg.md new file mode 100644 index 000000000000..cf5e3a4ca839 --- /dev/null +++ b/.changeset/dirty-stingrays-beg.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/model-typings": minor +"@rocket.chat/rest-typings": minor +--- + +Added support for specifying a unit on departments' creation and update diff --git a/.changeset/dry-taxis-cry.md b/.changeset/dry-taxis-cry.md new file mode 100644 index 000000000000..ae8244087d9e --- /dev/null +++ b/.changeset/dry-taxis-cry.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes a race condition that causes livechat conversations to get stuck in the agent's sidebar panel after being forwarded. diff --git a/.changeset/five-coats-rhyme.md b/.changeset/five-coats-rhyme.md new file mode 100644 index 000000000000..c5359e3c978a --- /dev/null +++ b/.changeset/five-coats-rhyme.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/fuselage-ui-kit': patch +--- + +Fixed an error that incorrectly showed conference calls as not answered after they ended 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/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/hot-balloons-travel.md b/.changeset/hot-balloons-travel.md new file mode 100644 index 000000000000..d6154babc49d --- /dev/null +++ b/.changeset/hot-balloons-travel.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed issue where when you marked a room as unread and you were part of it, sometimes it would mark it as read right after 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/kind-llamas-grin.md b/.changeset/kind-llamas-grin.md new file mode 100644 index 000000000000..fd349e82d7f9 --- /dev/null +++ b/.changeset/kind-llamas-grin.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Changed the contextualbar behavior based on chat size instead the viewport diff --git a/.changeset/late-planes-sniff.md b/.changeset/late-planes-sniff.md new file mode 100644 index 000000000000..d702a938da78 --- /dev/null +++ b/.changeset/late-planes-sniff.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-typings": patch +"@rocket.chat/i18n": patch +--- + +Added a new setting to enable mentions in end to end encrypted 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/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/poor-falcons-doubt.md b/.changeset/poor-falcons-doubt.md new file mode 100644 index 000000000000..cb297885c5a9 --- /dev/null +++ b/.changeset/poor-falcons-doubt.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/livechat': patch +--- + +Fixes an issue where the unread message counter in the livechat widget does not update when a visitor receives their first response from an agent while the widget is minimized. diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000000..c1eafc495c79 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,109 @@ +{ + "mode": "pre", + "tag": "rc", + "initialVersions": { + "@rocket.chat/meteor": "6.13.0-develop", + "rocketchat-services": "1.3.3", + "@rocket.chat/uikit-playground": "0.4.0", + "@rocket.chat/account-service": "0.4.6", + "@rocket.chat/authorization-service": "0.4.6", + "@rocket.chat/ddp-streamer": "0.3.6", + "@rocket.chat/omnichannel-transcript": "0.4.6", + "@rocket.chat/presence-service": "0.4.6", + "@rocket.chat/queue-worker": "0.4.6", + "@rocket.chat/stream-hub-service": "0.4.6", + "@rocket.chat/license": "0.2.6", + "@rocket.chat/omnichannel-services": "0.3.3", + "@rocket.chat/pdf-worker": "0.2.3", + "@rocket.chat/presence": "0.2.6", + "@rocket.chat/ui-theming": "0.2.1", + "@rocket.chat/account-utils": "0.0.2", + "@rocket.chat/agenda": "0.1.0", + "@rocket.chat/api-client": "0.2.6", + "@rocket.chat/apps": "0.1.6", + "@rocket.chat/base64": "1.0.13", + "@rocket.chat/cas-validate": "0.0.2", + "@rocket.chat/core-services": "0.6.0", + "@rocket.chat/core-typings": "6.13.0-develop", + "@rocket.chat/cron": "0.1.6", + "@rocket.chat/ddp-client": "0.3.6", + "@rocket.chat/eslint-config": "0.7.0", + "@rocket.chat/favicon": "0.0.2", + "@rocket.chat/fuselage-ui-kit": "10.0.0", + "@rocket.chat/gazzodown": "10.0.0", + "@rocket.chat/i18n": "0.7.0", + "@rocket.chat/instance-status": "0.1.6", + "@rocket.chat/jest-presets": "0.0.1", + "@rocket.chat/jwt": "0.1.1", + "@rocket.chat/livechat": "1.19.3", + "@rocket.chat/log-format": "0.0.2", + "@rocket.chat/logger": "0.0.2", + "@rocket.chat/message-parser": "0.31.29", + "@rocket.chat/mock-providers": "0.1.2", + "@rocket.chat/model-typings": "0.7.0", + "@rocket.chat/models": "0.2.3", + "@rocket.chat/poplib": "0.0.2", + "@rocket.chat/password-policies": "0.0.2", + "@rocket.chat/patch-injection": "0.0.1", + "@rocket.chat/peggy-loader": "0.31.25", + "@rocket.chat/random": "1.2.2", + "@rocket.chat/release-action": "2.2.3", + "@rocket.chat/release-changelog": "0.1.0", + "@rocket.chat/rest-typings": "6.13.0-develop", + "@rocket.chat/server-cloud-communication": "0.0.2", + "@rocket.chat/server-fetch": "0.0.3", + "@rocket.chat/sha256": "1.0.10", + "@rocket.chat/tools": "0.2.2", + "@rocket.chat/ui-avatar": "6.0.0", + "@rocket.chat/ui-client": "10.0.0", + "@rocket.chat/ui-composer": "0.2.1", + "@rocket.chat/ui-contexts": "10.0.0", + "@rocket.chat/ui-kit": "0.36.1", + "@rocket.chat/ui-video-conf": "10.0.0", + "@rocket.chat/web-ui-registration": "10.0.0" + }, + "changesets": [ + "brave-brooms-invent", + "brown-singers-appear", + "bump-patch-1727212585363", + "bump-patch-1727392133102", + "bump-patch-1727482167703", + "bump-patch-1727817480079", + "bump-patch-1728026023781", + "bump-patch-1728416283220", + "cyan-ladybugs-thank", + "dirty-stingrays-beg", + "dry-taxis-cry", + "five-coats-rhyme", + "four-cherries-kneel", + "great-humans-live", + "healthy-rivers-nail", + "heavy-snails-help", + "hot-balloons-travel", + "khaki-cameras-glow", + "kind-llamas-grin", + "late-planes-sniff", + "many-balloons-scream", + "many-rules-shout", + "mighty-drinks-hide", + "nasty-tools-enjoy", + "pink-swans-teach", + "quick-rings-wave", + "quiet-cherries-punch", + "rich-toes-bow", + "rotten-rabbits-brush", + "short-drinks-itch", + "sixty-spoons-own", + "small-crabs-travel", + "soft-mirrors-remember", + "spicy-rocks-burn", + "strong-grapes-brake", + "stupid-pigs-share", + "sweet-nails-grin", + "tame-mayflies-press", + "tiny-geckos-kiss", + "wet-hats-walk", + "wise-avocados-taste", + "witty-lemons-type" + ] +} diff --git a/.changeset/quick-rings-wave.md b/.changeset/quick-rings-wave.md new file mode 100644 index 000000000000..0ea22897ff45 --- /dev/null +++ b/.changeset/quick-rings-wave.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/i18n": minor +"@rocket.chat/ui-client": minor +--- + +Added new Admin Feature Preview management view, this will allow the workspace admins to both enable feature previewing in the workspace as well as define which feature previews are enabled by default for the users in the workspace. 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-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/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/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/sixty-spoons-own.md b/.changeset/sixty-spoons-own.md new file mode 100644 index 000000000000..0b717c3965ef --- /dev/null +++ b/.changeset/sixty-spoons-own.md @@ -0,0 +1,9 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-typings": minor +"@rocket.chat/model-typings": minor +"@rocket.chat/models": minor +"@rocket.chat/rest-typings": minor +--- + +Introduced "create contacts" endpoint to omnichannel diff --git a/.changeset/small-crabs-travel.md b/.changeset/small-crabs-travel.md new file mode 100644 index 000000000000..201494a5b70f --- /dev/null +++ b/.changeset/small-crabs-travel.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed avatar blob image setting in setUserAvatar method by correcting service handling logic. diff --git a/.changeset/soft-mirrors-remember.md b/.changeset/soft-mirrors-remember.md new file mode 100644 index 000000000000..78b005ee6b6e --- /dev/null +++ b/.changeset/soft-mirrors-remember.md @@ -0,0 +1,8 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-services": minor +"@rocket.chat/model-typings": minor +"@rocket.chat/rest-typings": minor +--- + +New `teams.listChildren` endpoint that allows users listing rooms & discussions from teams. Only the discussions from the team's main room are returned. diff --git a/.changeset/spicy-rocks-burn.md b/.changeset/spicy-rocks-burn.md new file mode 100644 index 000000000000..6468dbbec241 --- /dev/null +++ b/.changeset/spicy-rocks-burn.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed `LivechatSessionTaken` webhook event being called without the `agent` param, which represents the agent serving the room. diff --git a/.changeset/strong-grapes-brake.md b/.changeset/strong-grapes-brake.md new file mode 100644 index 000000000000..c867600a8cd2 --- /dev/null +++ b/.changeset/strong-grapes-brake.md @@ -0,0 +1,9 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed remaining direct references to external user avatar URLs + +Fixed local avatars having priority over external provider + +It mainly corrects the behavior of E2E encryption messages and desktop notifications. diff --git a/.changeset/stupid-pigs-share.md b/.changeset/stupid-pigs-share.md new file mode 100644 index 000000000000..55d68c66d587 --- /dev/null +++ b/.changeset/stupid-pigs-share.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Wraps some room settings in an accordion advanced settings section in room edit contextual bar to improve organization diff --git a/.changeset/sweet-nails-grin.md b/.changeset/sweet-nails-grin.md new file mode 100644 index 000000000000..de240bfc0e3f --- /dev/null +++ b/.changeset/sweet-nails-grin.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed inconsistency between the markdown parser from the composer and the rest of the application when using bold and italics in a text. 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/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/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/.changeset/witty-lemons-type.md b/.changeset/witty-lemons-type.md new file mode 100644 index 000000000000..a007cbe6260e --- /dev/null +++ b/.changeset/witty-lemons-type.md @@ -0,0 +1,10 @@ +--- +'@rocket.chat/core-services': minor +'@rocket.chat/model-typings': minor +'@rocket.chat/core-typings': minor +'@rocket.chat/rest-typings': minor +'@rocket.chat/ui-client': minor +'@rocket.chat/meteor': minor +--- + +Implemented new feature preview for Sidepanel 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/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml new file mode 100644 index 000000000000..4a1e67fca33a --- /dev/null +++ b/.github/workflows/release-candidate.yml @@ -0,0 +1,35 @@ +name: Release candidate cut +on: + schedule: + - cron: '28 0 20 * *' # run at minute 28 to avoid the chance of delay due to high load on GH +jobs: + new-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + token: ${{ secrets.CI_PAT }} + + - name: Setup NodeJS + uses: ./.github/actions/setup-node + with: + node-version: 14.21.3 + cache-modules: true + install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - uses: rharkor/caching-for-turbo@v1.5 + + - name: Build packages + run: yarn build + + - name: 'Start release candidate' + uses: ./packages/release-action + with: + action: next + base-ref: ${{ github.ref_name }} + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.CI_PAT }} 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/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 c7029b774cc0..340ae30e35a7 100644 --- a/apps/meteor/CHANGELOG.md +++ b/apps/meteor/CHANGELOG.md @@ -1,5 +1,306 @@ # @rocket.chat/meteor +## 6.13.0-rc.6 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.13.0-rc.6 + - @rocket.chat/rest-typings@6.13.0-rc.6 + - @rocket.chat/license@0.2.8-rc.6 + - @rocket.chat/omnichannel-services@0.3.5-rc.6 + - @rocket.chat/pdf-worker@0.2.5-rc.6 + - @rocket.chat/presence@0.2.8-rc.6 + - @rocket.chat/api-client@0.2.8-rc.6 + - @rocket.chat/apps@0.1.8-rc.6 + - @rocket.chat/core-services@0.7.0-rc.6 + - @rocket.chat/cron@0.1.8-rc.6 + - @rocket.chat/fuselage-ui-kit@11.0.0-rc.6 + - @rocket.chat/gazzodown@11.0.0-rc.6 + - @rocket.chat/model-typings@0.8.0-rc.6 + - @rocket.chat/ui-contexts@11.0.0-rc.6 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.3.0-rc.6 + - @rocket.chat/ui-theming@0.3.0-rc.0 + - @rocket.chat/ui-avatar@7.0.0-rc.6 + - @rocket.chat/ui-video-conf@11.0.0-rc.6 + - @rocket.chat/web-ui-registration@11.0.0-rc.6 + - @rocket.chat/instance-status@0.1.8-rc.6 +
+ +## 6.13.0-rc.5 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.13.0-rc.5 + - @rocket.chat/rest-typings@6.13.0-rc.5 + - @rocket.chat/license@0.2.8-rc.5 + - @rocket.chat/omnichannel-services@0.3.5-rc.5 + - @rocket.chat/pdf-worker@0.2.5-rc.5 + - @rocket.chat/presence@0.2.8-rc.5 + - @rocket.chat/api-client@0.2.8-rc.5 + - @rocket.chat/apps@0.1.8-rc.5 + - @rocket.chat/core-services@0.7.0-rc.5 + - @rocket.chat/cron@0.1.8-rc.5 + - @rocket.chat/fuselage-ui-kit@11.0.0-rc.5 + - @rocket.chat/gazzodown@11.0.0-rc.5 + - @rocket.chat/model-typings@0.8.0-rc.5 + - @rocket.chat/ui-contexts@11.0.0-rc.5 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.3.0-rc.5 + - @rocket.chat/ui-theming@0.3.0-rc.0 + - @rocket.chat/ui-avatar@7.0.0-rc.5 + - @rocket.chat/ui-client@11.0.0-rc.5 + - @rocket.chat/ui-video-conf@11.0.0-rc.5 + - @rocket.chat/web-ui-registration@11.0.0-rc.5 + - @rocket.chat/instance-status@0.1.8-rc.5 +
+ +## 6.13.0-rc.4 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.13.0-rc.4 + - @rocket.chat/rest-typings@6.13.0-rc.4 + - @rocket.chat/license@0.2.8-rc.4 + - @rocket.chat/omnichannel-services@0.3.5-rc.4 + - @rocket.chat/pdf-worker@0.2.5-rc.4 + - @rocket.chat/presence@0.2.8-rc.4 + - @rocket.chat/api-client@0.2.8-rc.4 + - @rocket.chat/apps@0.1.8-rc.4 + - @rocket.chat/core-services@0.7.0-rc.4 + - @rocket.chat/cron@0.1.8-rc.4 + - @rocket.chat/fuselage-ui-kit@11.0.0-rc.4 + - @rocket.chat/gazzodown@11.0.0-rc.4 + - @rocket.chat/model-typings@0.8.0-rc.4 + - @rocket.chat/ui-contexts@11.0.0-rc.4 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.3.0-rc.4 + - @rocket.chat/ui-theming@0.3.0-rc.0 + - @rocket.chat/ui-avatar@7.0.0-rc.4 + - @rocket.chat/ui-client@11.0.0-rc.4 + - @rocket.chat/ui-video-conf@11.0.0-rc.4 + - @rocket.chat/web-ui-registration@11.0.0-rc.4 + - @rocket.chat/instance-status@0.1.8-rc.4 +
+ +## 6.13.0-rc.3 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- ([#33381](https://github.com/RocketChat/Rocket.Chat/pull/33381)) Fixes a race condition that causes livechat conversations to get stuck in the agent's sidebar panel after being forwarded. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.13.0-rc.3 + - @rocket.chat/rest-typings@6.13.0-rc.3 + - @rocket.chat/license@0.2.8-rc.3 + - @rocket.chat/omnichannel-services@0.3.5-rc.3 + - @rocket.chat/pdf-worker@0.2.5-rc.3 + - @rocket.chat/presence@0.2.8-rc.3 + - @rocket.chat/api-client@0.2.8-rc.3 + - @rocket.chat/apps@0.1.8-rc.3 + - @rocket.chat/core-services@0.7.0-rc.3 + - @rocket.chat/cron@0.1.8-rc.3 + - @rocket.chat/fuselage-ui-kit@11.0.0-rc.3 + - @rocket.chat/gazzodown@11.0.0-rc.3 + - @rocket.chat/model-typings@0.8.0-rc.3 + - @rocket.chat/ui-contexts@11.0.0-rc.3 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.3.0-rc.3 + - @rocket.chat/ui-theming@0.3.0-rc.0 + - @rocket.chat/ui-avatar@7.0.0-rc.3 + - @rocket.chat/ui-client@11.0.0-rc.3 + - @rocket.chat/ui-video-conf@11.0.0-rc.3 + - @rocket.chat/web-ui-registration@11.0.0-rc.3 + - @rocket.chat/instance-status@0.1.8-rc.3 +
+ +## 6.13.0-rc.2 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.13.0-rc.2 + - @rocket.chat/rest-typings@6.13.0-rc.2 + - @rocket.chat/license@0.2.8-rc.2 + - @rocket.chat/omnichannel-services@0.3.5-rc.2 + - @rocket.chat/pdf-worker@0.2.5-rc.2 + - @rocket.chat/presence@0.2.8-rc.2 + - @rocket.chat/api-client@0.2.8-rc.2 + - @rocket.chat/apps@0.1.8-rc.2 + - @rocket.chat/core-services@0.7.0-rc.2 + - @rocket.chat/cron@0.1.8-rc.2 + - @rocket.chat/fuselage-ui-kit@11.0.0-rc.2 + - @rocket.chat/gazzodown@11.0.0-rc.2 + - @rocket.chat/model-typings@0.8.0-rc.2 + - @rocket.chat/ui-contexts@11.0.0-rc.2 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.3.0-rc.2 + - @rocket.chat/ui-theming@0.3.0-rc.0 + - @rocket.chat/ui-avatar@7.0.0-rc.2 + - @rocket.chat/ui-client@11.0.0-rc.2 + - @rocket.chat/ui-video-conf@11.0.0-rc.2 + - @rocket.chat/web-ui-registration@11.0.0-rc.2 + - @rocket.chat/instance-status@0.1.8-rc.2 +
+ +## 6.13.0-rc.1 + +### Minor Changes + +- ([#33212](https://github.com/RocketChat/Rocket.Chat/pull/33212)) Added new Admin Feature Preview management view, this will allow the workspace admins to both enable feature previewing in the workspace as well as define which feature previews are enabled by default for the users in the workspace. + +### Patch Changes + +- ([#33339](https://github.com/RocketChat/Rocket.Chat/pull/33339)) Fixes a problem that caused visitor creation to fail when GDPR setting was enabled and visitor was created via Apps Engine or the deprecated `livechat:registerGuest` method. + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies [2f9eea03d2]: + + - @rocket.chat/i18n@0.8.0-rc.1 + - @rocket.chat/ui-client@11.0.0-rc.1 + - @rocket.chat/ui-contexts@11.0.0-rc.1 + - @rocket.chat/web-ui-registration@11.0.0-rc.1 + - @rocket.chat/gazzodown@11.0.0-rc.1 + - @rocket.chat/fuselage-ui-kit@11.0.0-rc.1 + - @rocket.chat/ui-theming@0.3.0-rc.0 + - @rocket.chat/ui-avatar@7.0.0-rc.1 + - @rocket.chat/ui-video-conf@11.0.0-rc.1 + - @rocket.chat/core-typings@6.13.0-rc.1 + - @rocket.chat/rest-typings@6.13.0-rc.1 + - @rocket.chat/license@0.2.8-rc.1 + - @rocket.chat/omnichannel-services@0.3.5-rc.1 + - @rocket.chat/pdf-worker@0.2.5-rc.1 + - @rocket.chat/presence@0.2.8-rc.1 + - @rocket.chat/api-client@0.2.8-rc.1 + - @rocket.chat/apps@0.1.8-rc.1 + - @rocket.chat/core-services@0.7.0-rc.1 + - @rocket.chat/cron@0.1.8-rc.1 + - @rocket.chat/model-typings@0.8.0-rc.1 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.3.0-rc.1 + - @rocket.chat/instance-status@0.1.8-rc.1 +
+ +## 6.13.0-rc.0 + +### Minor Changes + +- ([#33156](https://github.com/RocketChat/Rocket.Chat/pull/33156)) added `sidepanelNavigation` to feature preview list + +- ([#32682](https://github.com/RocketChat/Rocket.Chat/pull/32682)) Added support for specifying a unit on departments' creation and update + +- ([#33139](https://github.com/RocketChat/Rocket.Chat/pull/33139)) 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. +- ([#32729](https://github.com/RocketChat/Rocket.Chat/pull/32729)) Implemented "omnichannel/contacts.update" endpoint to update contacts + +- ([#32510](https://github.com/RocketChat/Rocket.Chat/pull/32510)) Added a new setting to enable mentions in end to end encrypted channels + +- ([#32821](https://github.com/RocketChat/Rocket.Chat/pull/32821)) Replaced new `SidebarV2` components under feature preview + +- ([#33011](https://github.com/RocketChat/Rocket.Chat/pull/33011)) Return `parent` and `team` information when calling `rooms.info` endpoint + +- ([#32693](https://github.com/RocketChat/Rocket.Chat/pull/32693)) Introduced "create contacts" endpoint to omnichannel + +- ([#33177](https://github.com/RocketChat/Rocket.Chat/pull/33177)) New `teams.listChildren` endpoint that allows users listing rooms & discussions from teams. Only the discussions from the team's main room are returned. + +- ([#33114](https://github.com/RocketChat/Rocket.Chat/pull/33114)) Wraps some room settings in an accordion advanced settings section in room edit contextual bar to improve organization + +- ([#33160](https://github.com/RocketChat/Rocket.Chat/pull/33160)) Implemented sending email via apps + +- ([#32945](https://github.com/RocketChat/Rocket.Chat/pull/32945)) 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. + +- ([#33225](https://github.com/RocketChat/Rocket.Chat/pull/33225)) Implemented new feature preview for Sidepanel + +### Patch Changes + +- ([#33317](https://github.com/RocketChat/Rocket.Chat/pull/33317)) Fixed error during sendmessage client stub + +- ([#33211](https://github.com/RocketChat/Rocket.Chat/pull/33211)) 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. + +- ([#33298](https://github.com/RocketChat/Rocket.Chat/pull/33298)) Fixed a Federation callback not awaiting db call + +- ([#32939](https://github.com/RocketChat/Rocket.Chat/pull/32939)) Fixed issue where when you marked a room as unread and you were part of it, sometimes it would mark it as read right after + +- ([#33197](https://github.com/RocketChat/Rocket.Chat/pull/33197)) Fixes an issue where the retention policy warning keep displaying even if the retention is disabled inside the room + +- ([#33321](https://github.com/RocketChat/Rocket.Chat/pull/33321)) Changed the contextualbar behavior based on chat size instead the viewport + +- ([#33246](https://github.com/RocketChat/Rocket.Chat/pull/33246)) Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates) + +- ([#32999](https://github.com/RocketChat/Rocket.Chat/pull/32999)) Fixes multiple selection for MultiStaticSelectElement in UiKit + +- ([#33155](https://github.com/RocketChat/Rocket.Chat/pull/33155)) Fixed a code issue on NPS service. It was passing `startAt` as the expiration date when creating a banner. + +- ([#33237](https://github.com/RocketChat/Rocket.Chat/pull/33237)) fixed retention policy max age settings not being respected after upgrade + +- ([#33216](https://github.com/RocketChat/Rocket.Chat/pull/33216)) Prevented uiInteraction to subscribe multiple times + +- ([#33295](https://github.com/RocketChat/Rocket.Chat/pull/33295)) 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. + +- ([#33193](https://github.com/RocketChat/Rocket.Chat/pull/33193)) Fixed avatar blob image setting in setUserAvatar method by correcting service handling logic. + +- ([#33209](https://github.com/RocketChat/Rocket.Chat/pull/33209)) Fixed `LivechatSessionTaken` webhook event being called without the `agent` param, which represents the agent serving the room. + +- ([#33296](https://github.com/RocketChat/Rocket.Chat/pull/33296)) Fixed remaining direct references to external user avatar URLs + + Fixed local avatars having priority over external provider + + It mainly corrects the behavior of E2E encryption messages and desktop notifications. + +- ([#33157](https://github.com/RocketChat/Rocket.Chat/pull/33157) by [@csuadev](https://github.com/csuadev)) Fixed inconsistency between the markdown parser from the composer and the rest of the application when using bold and italics in a text. + +- ([#33181](https://github.com/RocketChat/Rocket.Chat/pull/33181)) Fixed issue that caused an infinite loading state when uploading a private app to Rocket.Chat + +- ([#33158](https://github.com/RocketChat/Rocket.Chat/pull/33158)) Fixes an issue where multi-step modals were closing unexpectedly + +-
Updated dependencies [bb94c9c67a, 9a38c8e13f, 599762739a, 7c14fd1a80, 9eaefdc892, 274f4f5881, cd0d50016e, 78e6ba4820, 532f08819e, 79c16d315a, 927710d778, 3a161c4310, 0f21fa01a3, 12d6307998]: + + - @rocket.chat/ui-client@11.0.0-rc.0 + - @rocket.chat/i18n@0.8.0-rc.0 + - @rocket.chat/model-typings@0.8.0-rc.0 + - @rocket.chat/rest-typings@6.13.0-rc.0 + - @rocket.chat/fuselage-ui-kit@11.0.0-rc.0 + - @rocket.chat/core-typings@6.13.0-rc.0 + - @rocket.chat/ui-theming@0.3.0-rc.0 + - @rocket.chat/ui-video-conf@11.0.0-rc.0 + - @rocket.chat/ui-composer@0.3.0-rc.0 + - @rocket.chat/gazzodown@11.0.0-rc.0 + - @rocket.chat/ui-avatar@7.0.0-rc.0 + - @rocket.chat/core-services@0.7.0-rc.0 + - @rocket.chat/message-parser@0.31.30-rc.0 + - @rocket.chat/models@0.3.0-rc.0 + - @rocket.chat/web-ui-registration@11.0.0-rc.0 + - @rocket.chat/ui-contexts@11.0.0-rc.0 + - @rocket.chat/omnichannel-services@0.3.4-rc.0 + - @rocket.chat/apps@0.1.7-rc.0 + - @rocket.chat/presence@0.2.7-rc.0 + - @rocket.chat/api-client@0.2.7-rc.0 + - @rocket.chat/license@0.2.7-rc.0 + - @rocket.chat/pdf-worker@0.2.4-rc.0 + - @rocket.chat/cron@0.1.7-rc.0 + - @rocket.chat/instance-status@0.1.7-rc.0 + - @rocket.chat/server-cloud-communication@0.0.2 +
+ ## 6.12.1 ### Patch 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..d947c1b30c2e 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; } 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/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 3ccc9caeafa0..d04d1a2418b5 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -376,7 +376,7 @@ API.v1.addRoute( throw new Meteor.Error('error-emoji-param-not-provided', 'The required "emoji" param is missing.'); } - await executeSetReaction(this.userId, emoji, msg._id, this.bodyParams.shouldReact); + await executeSetReaction(this.userId, emoji, msg, this.bodyParams.shouldReact); return API.v1.success(); }, 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 a0ed75ceea30..3dc62e462ddf 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'; @@ -417,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, sidepanel: 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/teams.ts b/apps/meteor/app/api/server/v1/teams.ts index f64f8c820575..acb6cba2bac7 100644 --- a/apps/meteor/app/api/server/v1/teams.ts +++ b/apps/meteor/app/api/server/v1/teams.ts @@ -11,6 +11,7 @@ import { isTeamsDeleteProps, isTeamsLeaveProps, isTeamsUpdateProps, + isTeamsListChildrenProps, } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Match, check } from 'meteor/check'; @@ -375,6 +376,44 @@ API.v1.addRoute( }, ); +const getTeamByIdOrNameOrParentRoom = async ( + params: { teamId: string } | { teamName: string } | { roomId: string }, +): Promise | null> => { + if ('teamId' in params && params.teamId) { + return Team.getOneById(params.teamId, { projection: { type: 1, roomId: 1 } }); + } + if ('teamName' in params && params.teamName) { + return Team.getOneByName(params.teamName, { projection: { type: 1, roomId: 1 } }); + } + if ('roomId' in params && params.roomId) { + return Team.getOneByRoomId(params.roomId, { projection: { type: 1, roomId: 1 } }); + } + return null; +}; + +// This should accept a teamId, filter (search by name on rooms collection) and sort/pagination +// should return a list of rooms/discussions from the team. the discussions will only be returned from the main room +API.v1.addRoute( + 'teams.listChildren', + { authRequired: true, validateParams: isTeamsListChildrenProps }, + { + async get() { + const { offset, count } = await getPaginationItems(this.queryParams); + const { sort } = await this.parseJsonQuery(); + const { filter, type } = this.queryParams; + + const team = await getTeamByIdOrNameOrParentRoom(this.queryParams); + if (!team) { + return API.v1.notFound(); + } + + const data = await Team.listChildren(this.userId, team, filter, type, sort, offset, count); + + return API.v1.success({ ...data, offset, count }); + }, + }, +); + API.v1.addRoute( 'teams.members', { authRequired: true }, 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 6efe99e14d0e..46d40713bad1 100644 --- a/apps/meteor/app/authorization/server/constant/permissions.ts +++ b/apps/meteor/app/authorization/server/constant/permissions.ts @@ -93,6 +93,18 @@ export const permissions = [ _id: 'view-l-room', roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'], }, + { + _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/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/channel-settings/server/functions/saveStreamingOptions.ts b/apps/meteor/app/channel-settings/server/functions/saveStreamingOptions.ts deleted file mode 100644 index aee596402cc6..000000000000 --- a/apps/meteor/app/channel-settings/server/functions/saveStreamingOptions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Rooms } from '@rocket.chat/models'; -import { Match, check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -export const saveStreamingOptions = async function (rid: string, options: Record): Promise { - if (!Match.test(rid, String)) { - throw new Meteor.Error('invalid-room', 'Invalid room', { - function: 'RocketChat.saveStreamingOptions', - }); - } - - check(options, { - id: Match.Optional(String), - type: Match.Optional(String), - url: Match.Optional(String), - thumbnail: Match.Optional(String), - isAudioOnly: Match.Optional(Boolean), - message: Match.Optional(String), - }); - - await Rooms.setStreamingOptionsById(rid, options); -}; diff --git a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts index 44ad253d83ef..04e8fdbaf186 100644 --- a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts +++ b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts @@ -21,7 +21,6 @@ import { saveRoomReadOnly } from '../functions/saveRoomReadOnly'; import { saveRoomSystemMessages } from '../functions/saveRoomSystemMessages'; import { saveRoomTopic } from '../functions/saveRoomTopic'; import { saveRoomType } from '../functions/saveRoomType'; -import { saveStreamingOptions } from '../functions/saveStreamingOptions'; type RoomSettings = { roomAvatar: string; @@ -37,7 +36,6 @@ type RoomSettings = { systemMessages: MessageTypesValues[]; default: boolean; joinCode: string; - streamingOptions: NonNullable; retentionEnabled: boolean; retentionMaxAge: number; retentionExcludePinned: boolean; @@ -272,9 +270,6 @@ const settingSavers: RoomSettingsSavers = { void Team.update(user._id, room.teamId, { type, updateRoom: false }); } }, - async streamingOptions({ value, rid }) { - await saveStreamingOptions(rid, value); - }, async readOnly({ value, room, rid, user }) { if (value !== room.ro) { await saveRoomReadOnly(rid, value, user); @@ -354,7 +349,6 @@ const fields: (keyof RoomSettings)[] = [ 'systemMessages', 'default', 'joinCode', - 'streamingOptions', 'retentionEnabled', 'retentionMaxAge', 'retentionExcludePinned', 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/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/client/methods/sendMessage.ts b/apps/meteor/app/lib/client/methods/sendMessage.ts index bdaca587493a..19220f901458 100644 --- a/apps/meteor/app/lib/client/methods/sendMessage.ts +++ b/apps/meteor/app/lib/client/methods/sendMessage.ts @@ -43,7 +43,7 @@ Meteor.methods({ await onClientMessageReceived(message as IMessage).then((message) => { ChatMessage.insert(message); - return callbacks.run('afterSaveMessage', message, room); + return callbacks.run('afterSaveMessage', message, { room }); }); }, }); diff --git a/apps/meteor/app/lib/server/functions/isTheLastMessage.ts b/apps/meteor/app/lib/server/functions/isTheLastMessage.ts index f8e5be94002c..f1f1fb4c1497 100644 --- a/apps/meteor/app/lib/server/functions/isTheLastMessage.ts +++ b/apps/meteor/app/lib/server/functions/isTheLastMessage.ts @@ -1,7 +1,7 @@ -import type { IMessage, IRoom } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom, AtLeast } from '@rocket.chat/core-typings'; import { settings } from '../../../settings/server'; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export const isTheLastMessage = (room: IRoom, message: Pick) => +export const isTheLastMessage = (room: AtLeast, message: Pick) => settings.get('Store_Last_Message') && (!room.lastMessage || room.lastMessage._id === message._id); diff --git a/apps/meteor/app/lib/server/functions/setUsername.ts b/apps/meteor/app/lib/server/functions/setUsername.ts index 5b2b1923da75..c4d2c47c6d9d 100644 --- a/apps/meteor/app/lib/server/functions/setUsername.ts +++ b/apps/meteor/app/lib/server/functions/setUsername.ts @@ -102,23 +102,22 @@ export const _setUsername = async function (userId: string, u: string, fullUser: // Set new username* await Users.setUsername(user._id, username); user.username = username; + if (!previousUsername && settings.get('Accounts_SetDefaultAvatar') === true) { - // eslint-disable-next-line @typescript-eslint/ban-types - const avatarSuggestions = (await getAvatarSuggestionForUser(user)) as {}; - let gravatar; - for await (const service of Object.keys(avatarSuggestions)) { - const avatarData = avatarSuggestions[+service as keyof typeof avatarSuggestions]; + const avatarSuggestions = await getAvatarSuggestionForUser(user); + let avatarData; + let serviceName = 'gravatar'; + + for (const service of Object.keys(avatarSuggestions)) { + avatarData = avatarSuggestions[service]; if (service !== 'gravatar') { - // eslint-disable-next-line dot-notation - await setUserAvatar(user, avatarData['blob'], avatarData['contentType'], service); - gravatar = null; + serviceName = service; break; } - gravatar = avatarData; } - if (gravatar != null) { - // eslint-disable-next-line dot-notation - await setUserAvatar(user, gravatar['blob'], gravatar['contentType'], 'gravatar'); + + if (avatarData) { + await setUserAvatar(user, avatarData.blob, avatarData.contentType, serviceName); } } diff --git a/apps/meteor/app/lib/server/methods/updateMessage.ts b/apps/meteor/app/lib/server/methods/updateMessage.ts index 8cebe563cd23..c03208a438e9 100644 --- a/apps/meteor/app/lib/server/methods/updateMessage.ts +++ b/apps/meteor/app/lib/server/methods/updateMessage.ts @@ -10,7 +10,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { settings } from '../../../settings/server'; import { updateMessage } from '../functions/updateMessage'; -const allowedEditedFields = ['tshow', 'alias', 'attachments', 'avatar', 'emoji', 'msg', 'customFields', 'content']; +const allowedEditedFields = ['tshow', 'alias', 'attachments', 'avatar', 'emoji', 'msg', 'customFields', 'content', 'e2eMentions']; export async function executeUpdateMessage( uid: IUser['_id'], diff --git a/apps/meteor/app/livechat/client/lib/chartHandler.ts b/apps/meteor/app/livechat/client/lib/chartHandler.ts index 19c1a004ca22..da2d4be3735c 100644 --- a/apps/meteor/app/livechat/client/lib/chartHandler.ts +++ b/apps/meteor/app/livechat/client/lib/chartHandler.ts @@ -177,10 +177,9 @@ export const drawDoughnutChart = async ( chartContext: { destroy: () => void } | undefined, dataLabels: string[], dataPoints: number[], -): Promise | void> => { +): Promise => { if (!chart) { - console.error('No chart element'); - return; + throw new Error('No chart element'); } if (chartContext) { chartContext.destroy(); @@ -200,7 +199,7 @@ export const drawDoughnutChart = async ( ], }, options: doughnutChartConfiguration(title), - }); + }) as ChartType; }; /** @@ -209,12 +208,12 @@ export const drawDoughnutChart = async ( * @param {String} label [chart label] * @param {Array(Double)} data [updated data] */ -export const updateChart = async (c: ChartType, label: string, data: { [x: string]: number }): Promise => { +export const updateChart = async (c: ChartType, label: string, data: number[]): Promise => { const chart = await c; if (chart.data?.labels?.indexOf(label) === -1) { // insert data chart.data.labels.push(label); - chart.data.datasets.forEach((dataset: { data: any[] }, idx: string | number) => { + chart.data.datasets.forEach((dataset: { data: any[] }, idx: number) => { dataset.data.push(data[idx]); }); } else { @@ -224,7 +223,7 @@ export const updateChart = async (c: ChartType, label: string, data: { [x: strin return; } - chart.data.datasets.forEach((dataset: { data: { [x: string]: any } }, idx: string | number) => { + chart.data.datasets.forEach((dataset: { data: { [x: string]: any } }, idx: number) => { dataset.data[index] = data[idx]; }); } 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/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index 252a83855700..e56feeac2fa3 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -57,10 +57,18 @@ API.v1.addRoute( check(this.bodyParams, { department: Object, agents: Match.Maybe(Array), + departmentUnit: Match.Maybe({ _id: Match.Optional(String) }), }); const agents = this.bodyParams.agents ? { upsert: this.bodyParams.agents } : {}; - const department = await LivechatTs.saveDepartment(null, this.bodyParams.department as ILivechatDepartment, agents); + const { departmentUnit } = this.bodyParams; + const department = await LivechatTs.saveDepartment( + this.userId, + null, + this.bodyParams.department as ILivechatDepartment, + agents, + departmentUnit || {}, + ); if (department) { return API.v1.success({ @@ -112,17 +120,18 @@ API.v1.addRoute( check(this.bodyParams, { department: Object, agents: Match.Maybe(Array), + departmentUnit: Match.Maybe({ _id: Match.Optional(String) }), }); const { _id } = this.urlParams; - const { department, agents } = this.bodyParams; + const { department, agents, departmentUnit } = this.bodyParams; if (!permissionToSave) { throw new Error('error-not-allowed'); } const agentParam = permissionToAddAgents && agents ? { upsert: agents } : {}; - await LivechatTs.saveDepartment(_id, department, agentParam); + await LivechatTs.saveDepartment(this.userId, _id, department, agentParam, departmentUnit || {}); return API.v1.success({ department: await LivechatDepartment.findOneById(_id), 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 57c1d117f1b0..f3fec80b23fe 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.ts +++ b/apps/meteor/app/livechat/server/api/v1/contact.ts @@ -1,14 +1,22 @@ -import { LivechatCustomField, LivechatVisitors } from '@rocket.chat/models'; +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 } from '../../lib/Contacts'; +import { Contacts, createContact, updateContact } from '../../lib/Contacts'; API.v1.addRoute( 'omnichannel/contact', - { authRequired: true, permissionsRequired: ['view-l-room'] }, + { + authRequired: true, + permissionsRequired: ['view-l-room'], + }, { async post() { check(this.bodyParams, { @@ -82,3 +90,49 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'omnichannel/contacts', + { authRequired: true, permissionsRequired: ['create-livechat-contact'], validateParams: isPOSTOmnichannelContactsProps }, + { + 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 contactId = await createContact({ ...this.bodyParams, unknown: false }); + + return API.v1.success({ contactId }); + }, + }, +); + +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 565f8e0bb3f4..7aacfacb4476 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -107,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'); diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts index c20b5dbdb661..f6f812ce8af8 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.ts +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -1,5 +1,21 @@ -import type { ILivechatCustomField, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { LivechatVisitors, Users, LivechatRooms, LivechatCustomField, LivechatInquiry, Rooms, Subscriptions } from '@rocket.chat/models'; +import type { + ILivechatContact, + ILivechatContactChannel, + ILivechatCustomField, + ILivechatVisitor, + IOmnichannelRoom, + IUser, +} from '@rocket.chat/core-typings'; +import { + LivechatVisitors, + Users, + LivechatRooms, + LivechatCustomField, + LivechatInquiry, + Rooms, + Subscriptions, + LivechatContacts, +} from '@rocket.chat/models'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { MatchKeysAndValues, OnlyFieldsOfType } from 'mongodb'; @@ -26,6 +42,26 @@ type RegisterContactProps = { }; }; +type CreateContactParams = { + name: 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, @@ -165,3 +201,99 @@ export const Contacts = { return contactId; }, }; + +export async function createContact(params: CreateContactParams): Promise { + const { name, emails, phones, customFields = {}, contactManager, channels, unknown } = params; + + if (contactManager) { + await validateContactManager(contactManager); + } + + const allowedCustomFields = await getAllowedCustomFields(); + validateCustomFields(allowedCustomFields, customFields); + + const { insertedId } = await LivechatContacts.insertOne({ + name, + emails, + phones, + contactManager, + channels, + customFields, + unknown, + }); + + return insertedId; +} + +export async function updateContact(params: UpdateContactParams): 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', + { + projection: { _id: 1, label: 1, regexp: 1, required: 1 }, + }, + false, + ).toArray(); +} + +export function validateCustomFields(allowedCustomFields: ILivechatCustomField[], customFields: Record) { + for (const cf of allowedCustomFields) { + if (!customFields.hasOwnProperty(cf._id)) { + if (cf.required) { + throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); + } + continue; + } + const cfValue: string = trim(customFields[cf._id]); + + if (!cfValue || typeof cfValue !== 'string') { + if (cf.required) { + throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); + } + continue; + } + + if (cf.regexp) { + const regex = new RegExp(cf.regexp); + if (!regex.test(cfValue)) { + throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); + } + } + } + + 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/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index be79d565f6de..6c2d655f4c95 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'; @@ -588,6 +589,10 @@ class LivechatClass { } } + isValidObject(obj: unknown): obj is Record { + return typeof obj === 'object' && obj !== null; + } + async registerGuest({ id, token, @@ -653,10 +658,10 @@ class LivechatClass { visitorDataToUpdate.status = status; visitorDataToUpdate.ts = new Date(); - if (settings.get('Livechat_Allow_collect_and_store_HTTP_header_informations')) { + if (settings.get('Livechat_Allow_collect_and_store_HTTP_header_informations') && Livechat.isValidObject(connectionData)) { Livechat.logger.debug(`Saving connection data for visitor ${token}`); const { httpHeaders, clientAddress } = connectionData; - if (httpHeaders) { + if (Livechat.isValidObject(httpHeaders)) { visitorDataToUpdate.userAgent = httpHeaders['user-agent']; visitorDataToUpdate.ip = httpHeaders['x-real-ip'] || httpHeaders['x-forwarded-for'] || clientAddress; visitorDataToUpdate.host = httpHeaders?.host; @@ -664,6 +669,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 +1083,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'; @@ -1777,18 +1793,37 @@ class LivechatClass { * @param {string|null} _id - The department id * @param {Partial} departmentData * @param {{upsert?: { agentId: string; count?: number; order?: number; }[], remove?: { agentId: string; count?: number; order?: number; }}} [departmentAgents] - The department agents + * @param {{_id?: string}} [departmentUnit] - The department's unit id */ async saveDepartment( + userId: string, _id: string | null, departmentData: LivechatDepartmentDTO, departmentAgents?: { upsert?: { agentId: string; count?: number; order?: number }[]; remove?: { agentId: string; count?: number; order?: number }; }, + departmentUnit?: { _id?: string }, ) { check(_id, Match.Maybe(String)); + if (departmentUnit?._id !== undefined && typeof departmentUnit._id !== 'string') { + throw new Meteor.Error('error-invalid-department-unit', 'Invalid department unit id provided', { + method: 'livechat:saveDepartment', + }); + } - const department = _id ? await LivechatDepartment.findOneById(_id, { projection: { _id: 1, archived: 1, enabled: 1 } }) : null; + const department = _id + ? await LivechatDepartment.findOneById(_id, { projection: { _id: 1, archived: 1, enabled: 1, parentId: 1 } }) + : null; + + if (departmentUnit && !departmentUnit._id && department && department.parentId) { + const isLastDepartmentInUnit = (await LivechatDepartment.countDepartmentsInUnit(department.parentId)) === 1; + if (isLastDepartmentInUnit) { + throw new Meteor.Error('error-unit-cant-be-empty', "The last department in a unit can't be removed", { + method: 'livechat:saveDepartment', + }); + } + } if (!department && !(await isDepartmentCreationAvailable())) { throw new Meteor.Error('error-max-departments-number-reached', 'Maximum number of departments reached', { @@ -1875,6 +1910,10 @@ class LivechatClass { await callbacks.run('livechat.afterDepartmentDisabled', departmentDB); } + if (departmentUnit) { + await callbacks.run('livechat.manageDepartmentUnit', { userId, departmentId: departmentDB._id, unitId: departmentUnit._id }); + } + return departmentDB; } } diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index f4a2288305e5..28e5c72efc16 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -265,11 +265,20 @@ export const RoutingManager: Routing = { logger.info(`Inquiry ${inquiry._id} taken by agent ${agent.agentId}`); + // assignAgent changes the room data to add the agent serving the conversation. afterTakeInquiry expects room object to be updated + const inq = await this.assignAgent(inquiry as InquiryWithAgentInfo, room, agent); + const roomAfterUpdate = await LivechatRooms.findOneById(rid); + + if (!roomAfterUpdate) { + // This should never happen + throw new Error('error-room-not-found'); + } + callbacks.runAsync( 'livechat.afterTakeInquiry', { - inquiry: await this.assignAgent(inquiry as InquiryWithAgentInfo, room, agent), - room, + inquiry: inq, + room: roomAfterUpdate, }, agent, ); @@ -282,7 +291,7 @@ export const RoutingManager: Routing = { queuedAt: undefined, }); - return LivechatRooms.findOneById(rid); + return roomAfterUpdate; }, async transferRoom(room, guest, transferData) { diff --git a/apps/meteor/app/livechat/server/methods/saveDepartment.ts b/apps/meteor/app/livechat/server/methods/saveDepartment.ts index b4833523ab3f..659f85f49945 100644 --- a/apps/meteor/app/livechat/server/methods/saveDepartment.ts +++ b/apps/meteor/app/livechat/server/methods/saveDepartment.ts @@ -30,12 +30,13 @@ declare module '@rocket.chat/ddp-client' { order?: number | undefined; }[] | undefined, + departmentUnit?: { _id?: string }, ) => ILivechatDepartment; } } Meteor.methods({ - async 'livechat:saveDepartment'(_id, departmentData, departmentAgents) { + async 'livechat:saveDepartment'(_id, departmentData, departmentAgents, departmentUnit) { const uid = Meteor.userId(); if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-departments'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { @@ -43,6 +44,6 @@ Meteor.methods({ }); } - return Livechat.saveDepartment(_id, departmentData, { upsert: departmentAgents }); + return Livechat.saveDepartment(uid, _id, departmentData, { upsert: departmentAgents }, departmentUnit); }, }); diff --git a/apps/meteor/app/mentions/server/Mentions.ts b/apps/meteor/app/mentions/server/Mentions.ts index 9eda56fea21c..779af2087932 100644 --- a/apps/meteor/app/mentions/server/Mentions.ts +++ b/apps/meteor/app/mentions/server/Mentions.ts @@ -2,7 +2,7 @@ * Mentions is a named function that will process Mentions * @param {Object} message - The message object */ -import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import { isE2EEMessage, type IMessage, type IRoom, type IUser } from '@rocket.chat/core-typings'; import { type MentionsParserArgs, MentionsParser } from '../lib/MentionsParser'; @@ -43,8 +43,13 @@ export class MentionsServer extends MentionsParser { }); } - async getUsersByMentions({ msg, rid, u: sender }: Pick): Promise { - const mentions = this.getUserMentions(msg); + async getUsersByMentions(message: IMessage): Promise { + const { msg, rid, u: sender, e2eMentions }: Pick = message; + + const mentions = + isE2EEMessage(message) && e2eMentions?.e2eUserMentions && e2eMentions?.e2eUserMentions.length > 0 + ? e2eMentions?.e2eUserMentions + : this.getUserMentions(msg); const mentionsAll: { _id: string; username: string }[] = []; const userMentions = []; @@ -67,8 +72,13 @@ export class MentionsServer extends MentionsParser { return [...mentionsAll, ...(userMentions.length ? await this.getUsers(userMentions) : [])]; } - async getChannelbyMentions({ msg }: Pick) { - const channels = this.getChannelMentions(msg); + async getChannelbyMentions(message: IMessage) { + const { msg, e2eMentions }: Pick = message; + + const channels = + isE2EEMessage(message) && e2eMentions?.e2eChannelMentions && e2eMentions?.e2eChannelMentions.length > 0 + ? e2eMentions?.e2eChannelMentions + : this.getChannelMentions(msg); return this.getChannels(channels.map((c) => c.trim().substr(1))); } diff --git a/apps/meteor/app/message-mark-as-unread/client/actionButton.ts b/apps/meteor/app/message-mark-as-unread/client/actionButton.ts index fc4a9d80c43c..e1e35d216029 100644 --- a/apps/meteor/app/message-mark-as-unread/client/actionButton.ts +++ b/apps/meteor/app/message-mark-as-unread/client/actionButton.ts @@ -19,7 +19,6 @@ Meteor.startup(() => { const { message = messageArgs(this).msg } = props; try { - await sdk.call('unreadMessages', message); const subscription = ChatSubscription.findOne({ rid: message.rid, }); @@ -27,8 +26,9 @@ Meteor.startup(() => { if (subscription == null) { return; } + router.navigate('/home'); await LegacyRoomManager.close(subscription.t + subscription.name); - return router.navigate('/home'); + await sdk.call('unreadMessages', message); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } 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 f66e5b447432..248b68554583 100644 --- a/apps/meteor/app/models/client/models/CachedChatRoom.ts +++ b/apps/meteor/app/models/client/models/CachedChatRoom.ts @@ -46,7 +46,6 @@ class CachedChatRoom extends CachedCollection { usernames: room.usernames, usersCount: room.usersCount, lastMessage: room.lastMessage, - streamingOptions: room.streamingOptions, teamId: room.teamId, teamMain: room.teamMain, v: (room as IOmnichannelRoom | undefined)?.v, @@ -111,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 0e325453539a..d17493e57f2e 100644 --- a/apps/meteor/app/models/client/models/CachedChatSubscription.ts +++ b/apps/meteor/app/models/client/models/CachedChatSubscription.ts @@ -35,7 +35,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/reactions/client/methods/setReaction.ts b/apps/meteor/app/reactions/client/methods/setReaction.ts index ed15cda9ab8e..1744d49c0ceb 100644 --- a/apps/meteor/app/reactions/client/methods/setReaction.ts +++ b/apps/meteor/app/reactions/client/methods/setReaction.ts @@ -3,7 +3,6 @@ import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; -import { callbacks } from '../../../../lib/callbacks'; import { emoji } from '../../../emoji/client'; import { Messages, ChatRoom, Subscriptions } from '../../../models/client'; @@ -55,10 +54,8 @@ Meteor.methods({ if (!message.reactions || typeof message.reactions !== 'object' || Object.keys(message.reactions).length === 0) { delete message.reactions; Messages.update({ _id: messageId }, { $unset: { reactions: 1 } }); - await callbacks.run('unsetReaction', messageId, reaction); } else { Messages.update({ _id: messageId }, { $set: { reactions: message.reactions } }); - await callbacks.run('setReaction', messageId, reaction); } } else { if (!message.reactions) { @@ -72,7 +69,6 @@ Meteor.methods({ message.reactions[reaction].usernames.push(user.username); Messages.update({ _id: messageId }, { $set: { reactions: message.reactions } }); - await callbacks.run('setReaction', messageId, reaction); } }, }); diff --git a/apps/meteor/app/reactions/server/setReaction.ts b/apps/meteor/app/reactions/server/setReaction.ts index d513c8dda6a5..be6e5aed4a54 100644 --- a/apps/meteor/app/reactions/server/setReaction.ts +++ b/apps/meteor/app/reactions/server/setReaction.ts @@ -4,7 +4,6 @@ import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, EmojiCustom, Rooms, Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; import { callbacks } from '../../../lib/callbacks'; import { i18n } from '../../../server/lib/i18n'; @@ -12,26 +11,39 @@ import { canAccessRoomAsync } from '../../authorization/server'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { emoji } from '../../emoji/server'; import { isTheLastMessage } from '../../lib/server/functions/isTheLastMessage'; -import { notifyOnRoomChangedById, notifyOnMessageChange } from '../../lib/server/lib/notifyListener'; +import { notifyOnMessageChange } from '../../lib/server/lib/notifyListener'; -const removeUserReaction = (message: IMessage, reaction: string, username: string) => { +export const removeUserReaction = (message: IMessage, reaction: string, username: string) => { if (!message.reactions) { return message; } - message.reactions[reaction].usernames.splice(message.reactions[reaction].usernames.indexOf(username), 1); - if (message.reactions[reaction].usernames.length === 0) { + const idx = message.reactions[reaction].usernames.indexOf(username); + + // user not found in reaction array + if (idx === -1) { + return message; + } + + message.reactions[reaction].usernames.splice(idx, 1); + if (!message.reactions[reaction].usernames.length) { delete message.reactions[reaction]; } return message; }; -async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction: string, shouldReact?: boolean) { - reaction = `:${reaction.replace(/:/g, '')}:`; +export async function setReaction( + room: Pick, + user: IUser, + message: IMessage, + reaction: string, + userAlreadyReacted?: boolean, +) { + await Message.beforeReacted(message, room); - if (!emoji.list[reaction] && (await EmojiCustom.findByNameOrAlias(reaction, {}).count()) === 0) { - throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', { - method: 'setReaction', + if (Array.isArray(room.muted) && room.muted.includes(user.username as string)) { + throw new Meteor.Error('error-not-allowed', i18n.t('You_have_been_muted', { lng: user.language }), { + rid: room._id, }); } @@ -42,51 +54,23 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction } } - if (Array.isArray(room.muted) && room.muted.indexOf(user.username as string) !== -1) { - throw new Meteor.Error('error-not-allowed', i18n.t('You_have_been_muted', { lng: user.language }), { - rid: room._id, - }); - } - - // if (!('reactions' in message)) { - // return; - // } - - await Message.beforeReacted(message, room); - - const userAlreadyReacted = - message.reactions && - Boolean(message.reactions[reaction]) && - message.reactions[reaction].usernames.indexOf(user.username as string) !== -1; - // When shouldReact was not informed, toggle the reaction. - if (shouldReact === undefined) { - shouldReact = !userAlreadyReacted; - } - - if (userAlreadyReacted === shouldReact) { - return; - } - let isReacted; - if (userAlreadyReacted) { const oldMessage = JSON.parse(JSON.stringify(message)); removeUserReaction(message, reaction, user.username as string); - if (_.isEmpty(message.reactions)) { + if (Object.keys(message.reactions || {}).length === 0) { delete message.reactions; + await Messages.unsetReactions(message._id); if (isTheLastMessage(room, message)) { await Rooms.unsetReactionsInLastMessage(room._id); - void notifyOnRoomChangedById(room._id); } - await Messages.unsetReactions(message._id); } else { await Messages.setReactions(message._id, message.reactions); if (isTheLastMessage(room, message)) { await Rooms.setReactionsInLastMessage(room._id, message.reactions); } } - await callbacks.run('unsetReaction', message._id, reaction); - await callbacks.run('afterUnsetReaction', message, { user, reaction, shouldReact, oldMessage }); + void callbacks.run('afterUnsetReaction', message, { user, reaction, shouldReact: false, oldMessage }); isReacted = false; } else { @@ -102,34 +86,61 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction await Messages.setReactions(message._id, message.reactions); if (isTheLastMessage(room, message)) { await Rooms.setReactionsInLastMessage(room._id, message.reactions); - void notifyOnRoomChangedById(room._id); } - await callbacks.run('setReaction', message._id, reaction); - await callbacks.run('afterSetReaction', message, { user, reaction, shouldReact }); + + void callbacks.run('afterSetReaction', message, { user, reaction, shouldReact: true }); isReacted = true; } - await Apps.self?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted); + void Apps.self?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted); void notifyOnMessageChange({ id: message._id, }); } -export async function executeSetReaction(userId: string, reaction: string, messageId: IMessage['_id'], shouldReact?: boolean) { - const user = await Users.findOneById(userId); +export async function executeSetReaction( + userId: string, + reaction: string, + messageParam: IMessage['_id'] | IMessage, + shouldReact?: boolean, +) { + // Check if the emoji is valid before proceeding + const reactionWithoutColons = reaction.replace(/:/g, ''); + reaction = `:${reactionWithoutColons}:`; + + if (!emoji.list[reaction] && (await EmojiCustom.countByNameOrAlias(reactionWithoutColons)) === 0) { + throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', { + method: 'setReaction', + }); + } + const user = await Users.findOneById(userId); if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setReaction' }); } - const message = await Messages.findOneById(messageId); + const message = typeof messageParam === 'string' ? await Messages.findOneById(messageParam) : messageParam; if (!message) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' }); } - const room = await Rooms.findOneById(message.rid); + const userAlreadyReacted = + message.reactions && Boolean(message.reactions[reaction]) && message.reactions[reaction].usernames.includes(user.username as string); + + // When shouldReact was not informed, toggle the reaction. + if (shouldReact === undefined) { + shouldReact = !userAlreadyReacted; + } + + if (userAlreadyReacted === shouldReact) { + return; + } + + const room = await Rooms.findOneById< + Pick + >(message.rid, { projection: { _id: 1, ro: 1, muted: 1, reactWhenReadOnly: 1, lastMessage: 1, t: 1, prid: 1, federated: 1 } }); if (!room) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' }); } @@ -138,7 +149,7 @@ export async function executeSetReaction(userId: string, reaction: string, messa throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'setReaction' }); } - return setReaction(room, user, message, reaction, shouldReact); + return setReaction(room, user, message, reaction, userAlreadyReacted); } declare module '@rocket.chat/ddp-client' { diff --git a/apps/meteor/app/slackbridge/server/RocketAdapter.js b/apps/meteor/app/slackbridge/server/RocketAdapter.js index f76c33fa1f81..8ba2a76dcbc2 100644 --- a/apps/meteor/app/slackbridge/server/RocketAdapter.js +++ b/apps/meteor/app/slackbridge/server/RocketAdapter.js @@ -45,16 +45,16 @@ export default class RocketAdapter { rocketLogger.debug('Register for events'); callbacks.add('afterSaveMessage', this.onMessage.bind(this), callbacks.priority.LOW, 'SlackBridge_Out'); callbacks.add('afterDeleteMessage', this.onMessageDelete.bind(this), callbacks.priority.LOW, 'SlackBridge_Delete'); - callbacks.add('setReaction', this.onSetReaction.bind(this), callbacks.priority.LOW, 'SlackBridge_SetReaction'); - callbacks.add('unsetReaction', this.onUnSetReaction.bind(this), callbacks.priority.LOW, 'SlackBridge_UnSetReaction'); + callbacks.add('afterSetReaction', this.onSetReaction.bind(this), callbacks.priority.LOW, 'SlackBridge_SetReaction'); + callbacks.add('afterUnsetReaction', this.onUnSetReaction.bind(this), callbacks.priority.LOW, 'SlackBridge_UnSetReaction'); } unregisterForEvents() { rocketLogger.debug('Unregister for events'); callbacks.remove('afterSaveMessage', 'SlackBridge_Out'); callbacks.remove('afterDeleteMessage', 'SlackBridge_Delete'); - callbacks.remove('setReaction', 'SlackBridge_SetReaction'); - callbacks.remove('unsetReaction', 'SlackBridge_UnSetReaction'); + callbacks.remove('afterSetReaction', 'SlackBridge_SetReaction'); + callbacks.remove('afterUnsetReaction', 'SlackBridge_UnSetReaction'); } async onMessageDelete(rocketMessageDeleted) { @@ -72,7 +72,7 @@ export default class RocketAdapter { } } - async onSetReaction(rocketMsgID, reaction) { + async onSetReaction(rocketMsg, { reaction }) { try { if (!this.slackBridge.isReactionsEnabled) { return; @@ -80,12 +80,11 @@ export default class RocketAdapter { rocketLogger.debug('onRocketSetReaction'); - if (rocketMsgID && reaction) { - if (this.slackBridge.reactionsMap.delete(`set${rocketMsgID}${reaction}`)) { + if (rocketMsg._id && reaction) { + if (this.slackBridge.reactionsMap.delete(`set${rocketMsg._id}${reaction}`)) { // This was a Slack reaction, we don't need to tell Slack about it return; } - const rocketMsg = await Messages.findOneById(rocketMsgID); if (rocketMsg) { for await (const slack of this.slackAdapters) { const slackChannel = slack.getSlackChannel(rocketMsg.rid); @@ -101,7 +100,7 @@ export default class RocketAdapter { } } - async onUnSetReaction(rocketMsgID, reaction) { + async onUnSetReaction(rocketMsg, { reaction }) { try { if (!this.slackBridge.isReactionsEnabled) { return; @@ -109,13 +108,12 @@ export default class RocketAdapter { rocketLogger.debug('onRocketUnSetReaction'); - if (rocketMsgID && reaction) { - if (this.slackBridge.reactionsMap.delete(`unset${rocketMsgID}${reaction}`)) { + if (rocketMsg._id && reaction) { + if (this.slackBridge.reactionsMap.delete(`unset${rocketMsg._id}${reaction}`)) { // This was a Slack unset reaction, we don't need to tell Slack about it return; } - const rocketMsg = await Messages.findOneById(rocketMsgID); if (rocketMsg) { for await (const slack of this.slackAdapters) { const slackChannel = slack.getSlackChannel(rocketMsg.rid); diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index cff2aaefcc5a..e5001b2bff87 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -542,7 +542,6 @@ export const statistics = { statistics.totalOTRRooms = await Rooms.findByCreatedOTR().count(); statistics.totalOTR = settings.get('OTR_Count'); statistics.totalBroadcastRooms = await Rooms.findByBroadcast().count(); - statistics.totalRoomsWithActiveLivestream = await Rooms.findByActiveLivestream().count(); statistics.totalTriggeredEmails = settings.get('Triggered_Emails_Count'); statistics.totalRoomsWithStarred = await Messages.countRoomsWithStarredMessages({ readPreference }); statistics.totalRoomsWithPinned = await Messages.countRoomsWithPinnedMessages({ readPreference }); diff --git a/apps/meteor/app/utils/client/getUserAvatarURL.ts b/apps/meteor/app/utils/client/getUserAvatarURL.ts index 1a825a44fc27..d5fdd2b2d427 100644 --- a/apps/meteor/app/utils/client/getUserAvatarURL.ts +++ b/apps/meteor/app/utils/client/getUserAvatarURL.ts @@ -1,11 +1,6 @@ -import { settings } from '../../settings/client'; import { getAvatarURL } from './getAvatarURL'; export const getUserAvatarURL = function (username: string, cache = ''): string | undefined { - const externalSource = (settings.get('Accounts_AvatarExternalProviderUrl') || '').trim().replace(/\/$/, ''); - if (externalSource !== '') { - return externalSource.replace('{username}', username); - } if (username == null) { return; } diff --git a/apps/meteor/app/utils/rocketchat.info b/apps/meteor/app/utils/rocketchat.info index 7285c1c94e10..19164d07ee1b 100644 --- a/apps/meteor/app/utils/rocketchat.info +++ b/apps/meteor/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "6.12.1" + "version": "6.13.0-rc.6" } 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/app/utils/server/getUserAvatarURL.ts b/apps/meteor/app/utils/server/getUserAvatarURL.ts index b83efea1d842..d5fdd2b2d427 100644 --- a/apps/meteor/app/utils/server/getUserAvatarURL.ts +++ b/apps/meteor/app/utils/server/getUserAvatarURL.ts @@ -1,11 +1,6 @@ -import { settings } from '../../settings/server'; import { getAvatarURL } from './getAvatarURL'; export const getUserAvatarURL = function (username: string, cache = ''): string | undefined { - const externalSource = (settings.get('Accounts_AvatarExternalProviderUrl') || '').trim().replace(/\/$/, ''); - if (externalSource !== '') { - return externalSource.replace('{username}', username); - } if (username == null) { return; } 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..22895d55388f 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/UserMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/UserMenu.tsx @@ -1,11 +1,10 @@ import type { IUser } from '@rocket.chat/core-typings'; +import { GenericMenu, useHandleMenuAction } from '@rocket.chat/ui-client'; +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; import React, { memo, useState } from 'react'; -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'; diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx index bf1b7e55f244..e54e2b72d675 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx @@ -1,16 +1,15 @@ 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 { defaultFeaturesPreview, usePreferenceFeaturePreviewList } 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(); - const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList(); + const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList(); const handleMyAccount = useEffectEvent(() => { router.navigate('/account'); 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/components/Contextualbar/ContextualbarDialog.tsx b/apps/meteor/client/components/Contextualbar/ContextualbarDialog.tsx index 23def16a94a1..4e4640270087 100644 --- a/apps/meteor/client/components/Contextualbar/ContextualbarDialog.tsx +++ b/apps/meteor/client/components/Contextualbar/ContextualbarDialog.tsx @@ -18,7 +18,7 @@ type ContextualbarDialogProps = AriaDialogProps & ComponentProps { const ref = useRef(null); const { dialogProps } = useDialog({ 'aria-labelledby': 'contextualbarTitle', ...props }, ref); - const sizes = useLayoutSizes(); + const { contextualBar } = useLayoutSizes(); const position = useLayoutContextualBarPosition(); const { closeTab } = useRoomToolbox(); @@ -42,12 +42,12 @@ const ContextualbarDialog = (props: ContextualbarDialogProps) => { - + - + 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 ( ( { + const disabled = !useSidePanelNavigationScreenSize(); + return ; +}; diff --git a/apps/meteor/client/components/MarkdownText.spec.tsx b/apps/meteor/client/components/MarkdownText.spec.tsx new file mode 100644 index 000000000000..69e7a8c25cd2 --- /dev/null +++ b/apps/meteor/client/components/MarkdownText.spec.tsx @@ -0,0 +1,104 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import MarkdownText from './MarkdownText'; + +import '@testing-library/jest-dom'; + +const normalizeHtml = (html: any) => { + return html.replace(/\s+/g, ' ').trim(); +}; + +const markdownText = ` + # Heading 1 + **Paragraph text**: *Bold with one asterisk* **Bold with two asterisks** Lorem ipsum dolor sit amet, consectetur adipiscing elit. + ## Heading 2 + _Italic Text_: _Italic with one underscore_ __Italic with two underscores__ Lorem ipsum dolor sit amet, consectetur adipiscing elit. + ### Heading 3 + Lists, Links and elements + **Unordered List** + - List Item 1 + - List Item 2 + - List Item 3 + - List Item 4 + **Ordered List** + 1. List Item 1 + 2. List Item 2 + 3. List Item 3 + 4. List Item 4 + **Links:** + [Rocket.Chat](rocket.chat) + gabriel.engel@rocket.chat + +55991999999 + \`Inline code\` + \`\`\`typescript + const test = 'this is code' + \`\`\` + **Bold text within __Italics__** + *Bold text with single asterik and underscore within _Italics_* + __Italics within **Bold** text__ + _Italics within *Bold* text with single underscore and asterik_ +`; + +it('should render html elements as expected using default parser', async () => { + const { container } = render(, { + wrapper: mockAppRoot().build(), + legacyRoot: true, + }); + + const normalizedHtml = normalizeHtml(container.innerHTML); + + expect(normalizedHtml).toContain('

Heading 1

'); + expect(normalizedHtml).toContain( + 'Paragraph text: Bold with one asterisk Bold with two asterisks Lorem ipsum dolor sit amet', + ); + expect(normalizedHtml).toContain('

Heading 2

'); + expect(normalizedHtml).toContain( + 'Italic Text: Italic with one underscore Italic with two underscores Lorem ipsum dolor sit amet', + ); + expect(normalizedHtml).toContain('

Heading 3

'); + expect(normalizedHtml).toContain('
  • List Item 1
  • List Item 2
  • List Item 3
  • List Item 4'); + expect(normalizedHtml).toContain('
    1. List Item 1
    2. List Item 2
    3. List Item 3
    4. List Item 4'); + expect(normalizedHtml).toContain('Rocket.Chat'); + expect(normalizedHtml).toContain('gabriel.engel@rocket.chat'); + expect(normalizedHtml).toContain('+55991999999'); + expect(normalizedHtml).toContain('Inline code'); + expect(normalizedHtml).toContain('
      const test = \'this is code\' 
      '); + expect(normalizedHtml).toContain('Bold text within Italics'); + expect(normalizedHtml).toContain('Bold text with single asterik and underscore within Italics'); + expect(normalizedHtml).toContain('Italics within Bold text'); + expect(normalizedHtml).toContain('Italics within Bold text with single underscore and asterik'); +}); + +it('should render html elements as expected using inline parser', async () => { + const { container } = render(, { + wrapper: mockAppRoot().build(), + legacyRoot: true, + }); + + const normalizedHtml = normalizeHtml(container.innerHTML); + + expect(normalizedHtml).toContain('# Heading 1'); + expect(normalizedHtml).toContain( + 'Bold with one asterisk Bold with two asterisks Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + ); + expect(normalizedHtml).toContain('## Heading 2'); + expect(normalizedHtml).toContain( + 'Italic Text: Italic with one underscore Italic with two underscores Lorem ipsum dolor sit amet', + ); + expect(normalizedHtml).toContain('### Heading 3'); + expect(normalizedHtml).toContain('Unordered List - List Item 1 - List Item 2 - List Item 3 - List Item 4'); + expect(normalizedHtml).toContain('Ordered List 1. List Item 1 2. List Item 2 3. List Item 3 4. List Item 4'); + expect(normalizedHtml).toContain(`Rocket.Chat`); + expect(normalizedHtml).toContain( + `gabriel.engel@rocket.chat`, + ); + expect(normalizedHtml).toContain('+55991999999'); + expect(normalizedHtml).toContain('Inline code'); + expect(normalizedHtml).toContain(`typescript const test = 'this is code'`); + expect(normalizedHtml).toContain('Bold text within Italics'); + expect(normalizedHtml).toContain('Bold text with single asterik and underscore within Italics'); + expect(normalizedHtml).toContain('Italics within Bold text'); + expect(normalizedHtml).toContain('Italics within Bold text with single underscore and asterik'); +}); diff --git a/apps/meteor/client/components/MarkdownText.tsx b/apps/meteor/client/components/MarkdownText.tsx index c9af942f6e1c..16bd1b71430a 100644 --- a/apps/meteor/client/components/MarkdownText.tsx +++ b/apps/meteor/client/components/MarkdownText.tsx @@ -20,12 +20,18 @@ const documentRenderer = new marked.Renderer(); const inlineRenderer = new marked.Renderer(); const inlineWithoutBreaks = new marked.Renderer(); -marked.Lexer.rules.gfm = { - ...marked.Lexer.rules.gfm, - strong: /^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, - em: /^__(?=\S)([\s\S]*?\S)__(?!_)|^_(?=\S)([\s\S]*?\S)_(?!_)/, +const walkTokens = (token: marked.Token) => { + const boldPattern = /^\*[^*]+\*$|^\*\*[^*]+\*\*$/; + const italicPattern = /^__(?=\S)([\s\S]*?\S)__(?!_)|^_(?=\S)([\s\S]*?\S)_(?!_)/; + if (boldPattern.test(token.raw) && token.type === 'em') { + token.type = 'strong' as 'em'; + } else if (italicPattern.test(token.raw) && token.type === 'strong') { + token.type = 'em' as 'strong'; + } }; +marked.use({ walkTokens }); + const linkMarked = (href: string | null, _title: string | null, text: string): string => `${text} `; const paragraphMarked = (text: string): string => text; 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/TranscriptModal.tsx b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx index c06b6a190465..67deb558c1ed 100644 --- a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx @@ -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 ( - -