Merge pull request #19982 from RocketChat/release-3.10.0

Release 3.10.0
pull/19983/head 3.10.0
Diego Sampaio 5 years ago committed by GitHub
commit 3a13cead22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .docker-mongo/Dockerfile
  2. 6
      .docker/Dockerfile
  3. 14
      .docker/Dockerfile.rhel
  4. 2
      .eslintignore
  5. 64
      .github/PULL_REQUEST_TEMPLATE.md
  6. 953
      .github/history.json
  7. 5
      .mocharc.js
  8. 19
      .scripts/check-i18n.js
  9. 2
      .snapcraft/resources/prepareRocketChat
  10. 2
      .snapcraft/resources/preparecaddy
  11. 2
      .snapcraft/snap/snapcraft.yaml
  12. 1
      .stylelintignore
  13. 406
      HISTORY.md
  14. 6
      README.md
  15. 6
      app/api/server/api.d.ts
  16. 1
      app/api/server/index.js
  17. 5
      app/api/server/lib/users.js
  18. 28
      app/api/server/v1/instances.ts
  19. 8
      app/api/server/v1/misc.js
  20. 432
      app/apps/assets/stylesheets/apps.css
  21. 1
      app/apps/client/gameCenter/gameCenter.js
  22. 31
      app/apps/client/gameCenter/tabBar.js
  23. 28
      app/apps/client/gameCenter/tabBar.ts
  24. 19
      app/authentication/server/startup/index.js
  25. 3
      app/authorization/client/startup.js
  26. 113
      app/autolinker/client/client.js
  27. 2
      app/autolinker/client/index.js
  28. 6
      app/autotranslate/client/index.js
  29. 6
      app/autotranslate/client/lib/actionButton.js
  30. 164
      app/autotranslate/client/lib/autotranslate.js
  31. 23
      app/autotranslate/client/lib/tabBar.js
  32. 19
      app/autotranslate/client/lib/tabBar.ts
  33. 4
      app/autotranslate/server/autotranslate.js
  34. 4
      app/channel-settings/client/index.js
  35. 8
      app/channel-settings/client/startup/messageTypes.js
  36. 16
      app/channel-settings/client/startup/tabBar.js
  37. 14
      app/channel-settings/client/startup/tabBar.ts
  38. 48
      app/channel-settings/client/startup/trackSettingsChange.js
  39. 133
      app/channel-settings/client/stylesheets/channel-settings.css
  40. 7
      app/channel-settings/client/views/channelSettings.html
  41. 32
      app/channel-settings/client/views/channelSettings.js
  42. 2
      app/chatpal-search/client/route.js
  43. 29
      app/colors/client/client.js
  44. 2
      app/colors/client/index.js
  45. 2
      app/colors/client/style.css
  46. 1
      app/discussion/client/index.js
  47. 17
      app/discussion/client/tabBar.js
  48. 18
      app/discussion/client/tabBar.ts
  49. 11
      app/discussion/client/views/DiscussionTabbar.js
  50. 37
      app/e2e/client/tabbar.js
  51. 28
      app/e2e/client/tabbar.ts
  52. 2
      app/emoji-custom/client/admin/startup.js
  53. 2
      app/emoji-custom/client/lib/emojiCustom.js
  54. 111
      app/emoji/client/emojiParser.js
  55. 2
      app/emoji/client/emojiPicker.js
  56. 14
      app/emoji/client/index.js
  57. 150
      app/google-vision/client/googlevision.js
  58. 5
      app/google-vision/client/index.js
  59. 37
      app/highlight-words/client/client.js
  60. 6
      app/highlight-words/client/helper.js
  61. 2
      app/highlight-words/client/index.js
  62. 2
      app/integrations/client/startup.js
  63. 27
      app/issuelinks/client/client.js
  64. 2
      app/issuelinks/client/index.js
  65. 5
      app/katex/.eslintrc
  66. 61
      app/katex/client/index.js
  67. 1
      app/katex/katex.min.css
  68. 10
      app/katex/lib/katex.js
  69. 2
      app/katex/server/index.js
  70. 89
      app/lib/client/defaultTabBars.js
  71. 1
      app/lib/client/index.js
  72. 4
      app/lib/server/functions/checkEmailAvailability.js
  73. 5
      app/lib/server/functions/checkUsernameAvailability.js
  74. 3
      app/lib/server/functions/getFullUserData.js
  75. 2
      app/lib/server/functions/notifications/audio.js
  76. 15
      app/lib/server/functions/notifications/email.js
  77. 6
      app/lib/server/functions/notifications/index.js
  78. 2
      app/lib/server/functions/notifications/mobile.js
  79. 7
      app/lib/server/functions/saveUser.js
  80. 2
      app/lib/server/functions/saveUserIdentity.js
  81. 3
      app/lib/server/functions/setEmail.js
  82. 35
      app/lib/server/functions/updateGroupDMsName.js
  83. 4
      app/lib/server/lib/notifyUsersOnMessage.js
  84. 23
      app/livechat/client/externalFrame/tabBar.js
  85. 18
      app/livechat/client/externalFrame/tabBar.ts
  86. 29
      app/livechat/client/hooks/onCreateRoomTabBar.js
  87. 2
      app/livechat/client/index.js
  88. 2
      app/livechat/client/route.js
  89. 19
      app/livechat/client/tabBar.ts
  90. 26
      app/livechat/client/ui.js
  91. 5
      app/livechat/client/views/app/tabbar/contactChatHistory.js
  92. 2
      app/livechat/client/views/app/tabbar/visitorInfo.html
  93. 5
      app/livechat/client/views/app/tabbar/visitorInfo.js
  94. 9
      app/livechat/imports/server/rest/rooms.js
  95. 28
      app/livechat/lib/LivechatRoomType.js
  96. 5
      app/livechat/server/api/lib/customFields.js
  97. 5
      app/livechat/server/api/lib/departments.js
  98. 5
      app/livechat/server/api/lib/users.js
  99. 1
      app/livechat/server/api/lib/visitors.js
  100. 1
      app/livechat/server/api/rest.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -25,8 +25,9 @@ RUN set -x \
&& mkdir -p /app/uploads \
&& chown rocketchat:rocketchat /app/uploads
ADD . /app
ADD entrypoint.sh /app/bundle/
# --chown requires Docker 17.12 and works only on Linux
ADD --chown=rocketchat:rocketchat . /app
ADD --chown=rocketchat:rocketchat entrypoint.sh /app/bundle/
RUN aptMark="$(apt-mark showmanual)" \
&& apt-get install -y --no-install-recommends g++ make python ca-certificates \
@ -42,8 +43,7 @@ RUN aptMark="$(apt-mark showmanual)" \
| sort -u \
| xargs -r apt-mark manual \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& npm cache clear --force \
&& chown -R rocketchat:rocketchat /app
&& npm cache clear --force
VOLUME /app/uploads

@ -10,7 +10,8 @@ RUN groupadd -g 65533 -r rocketchat \
&& apt-get update \
&& apt-get install -y --no-install-recommends fontconfig
ADD . /app
# --chown requires Docker 17.12 and works only on Linux
ADD --chown=rocketchat:rocketchat . /app
RUN aptMark="$(apt-mark showmanual)" \
&& apt-get install -y --no-install-recommends g++ make python ca-certificates \
@ -26,8 +27,7 @@ RUN aptMark="$(apt-mark showmanual)" \
| sort -u \
| xargs -r apt-mark manual \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& npm cache clear --force \
&& chown -R rocketchat:rocketchat /app
&& npm cache clear --force
USER rocketchat

@ -1,6 +1,6 @@
FROM registry.access.redhat.com/rhscl/nodejs-8-rhel7
FROM registry.access.redhat.com/ubi8/nodejs-12
ENV RC_VERSION 3.9.3
ENV RC_VERSION 3.10.0
MAINTAINER buildmaster@rocket.chat
@ -13,9 +13,9 @@ LABEL name="Rocket.Chat" \
description="The Ultimate Open Source Web Chat Platform" \
run="docker run -d --name ${NAME} ${IMAGE}"
# This is ugly... But for some reason npm and node aren't available at this stage.
ENV PATH /opt/rh/rh-nodejs8/root/usr/bin:/opt/app-root/src/node_modules/.bin/:/opt/app-root/src/.npm-global/bin/:/opt/app-root/src/bin:/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
USER root
RUN dnf install -y python38 && rm -rf /var/cache /var/log/dnf* /var/log/yum.*
USER default
RUN set -x \
&& gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 0E163286C20D07B9787EBE9FD7F9D0414FD08104 \
@ -32,10 +32,6 @@ VOLUME /opt/app-root/src/uploads
WORKDIR /opt/app-root/src/bundle
# Hack needed to force use of bundled library instead of system level outdated library
# https://github.com/lovell/sharp/issues/892
ENV LD_PRELOAD=/opt/app-root/src/bundle/programs/server/npm/node_modules/sharp/vendor/lib/libz.so
ENV DEPLOY_METHOD=docker-redhat \
NODE_ENV=production \
MONGO_URL=mongodb://mongo:27017/rocketchat \

@ -4,7 +4,6 @@ packages/meteor-streams/
packages/meteor-timesync/
app/emoji-emojione/generateEmojiIndex.js
app/favico/favico.js
app/katex/client/katex/katex.min.js
packages/rocketchat-livechat/assets/rocketchat-livechat.min.js
packages/rocketchat-livechat/assets/rocket-livechat.js
app/theme/client/vendor/
@ -18,3 +17,4 @@ public/workers/**/*
imports/client/
!/.storybook/
ee/server/services/dist/**
!/.mocharc.js

@ -1,46 +1,38 @@
<!-- This is a pull request template, you do not need to uncomment or remove the comments, they won't show up in the PR text. -->
<!-- Your Pull Request name should start with one of the following tags -->
<!-- [NEW] For new features -->
<!-- [FIX] For bug fixes -->
<!-- [BREAK] For pull requests including breaking changes -->
## Proposed changes
<!-- Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue below. -->
<!-- Your Pull Request name should start with one of the following tags
[NEW] For new features
[IMPROVE] For a improvement (performance or little improvements) in existent features
[FIX] For bug fixes that affects the end user
[BREAK] For pull requests including breaking changes
Chore: For small tasks
Doc: For documentation
-->
<!-- Checklist!!! If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.
- I have read the Contributing Guide - https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat doc
- I have signed the CLA - https://cla-assistant.io/RocketChat/Rocket.Chat
- Lint and unit tests pass locally with my changes
- I have added tests that prove my fix is effective or that my feature works (if applicable)
- I have added necessary documentation (if applicable)
- Any dependent changes have been merged and published in downstream modules
-->
## Proposed changes (including videos or screenshots)
<!-- CHANGELOG -->
<!--
Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
If it fixes a bug or resolves a feature request, be sure to link to that issue below.
This description will appear in the release notes if we accept the contribution.
-->
<!-- END CHANGELOG -->
## Issue(s)
<!-- Link the issues being closed by or related to this PR. For example, you can use #594 if this PR closes issue number 594 -->
## How to test or reproduce
## Steps to test or reproduce
<!-- Mention how you would reproduce the bug if not mentioned on the issue page already. Also mention which screens are going to have the changes if applicable -->
## Screenshots
## Types of changes
<!-- What types of changes does your code introduce to Rocket.Chat? -->
<!-- Put an `x` in the boxes that apply -->
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] Improvement (non-breaking change which improves a current function)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Hotfix (a major bugfix that has to be merged asap)
- [ ] Documentation Update (if none of the other choices apply)
## Checklist
<!-- Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. -->
- [ ] I have read the [CONTRIBUTING](https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat) doc
- [ ] I have signed the [CLA](https://cla-assistant.io/RocketChat/Rocket.Chat)
- [ ] Lint and unit tests pass locally with my changes
- [ ] I have added tests that prove my fix is effective or that my feature works (if applicable)
- [ ] I have added necessary documentation (if applicable)
- [ ] Any dependent changes have been merged and published in downstream modules
## Changelog
<!-- CHANGELOG -->
<!-- Enter HERE a brief text that would go up on the changelog on our releases page -->
<!-- END CHANGELOG -->
## Further comments
<!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... -->

@ -52732,6 +52732,959 @@
]
}
]
},
"1.3.5": {
"node_version": "8.11.4",
"npm_version": "6.4.1",
"apps_engine_version": "1.5.1",
"mongo_versions": [],
"pull_requests": [
{
"pr": "19817",
"title": "[FIX] Issue with special message rendering",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19854",
"title": "[FIX] Problem with attachment render",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
}
]
},
"2.4.14": {
"node_version": "8.17.0",
"npm_version": "6.13.4",
"apps_engine_version": "1.11.2",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": [
{
"pr": "19854",
"title": "[FIX] Problem with attachment render",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19817",
"title": "[FIX] Issue with special message rendering",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
}
]
},
"3.7.4": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.18.0",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": [
{
"pr": "19854",
"title": "[FIX] Problem with attachment render",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19817",
"title": "[FIX] Issue with special message rendering",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
}
]
},
"3.8.4": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.19.0",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": [
{
"pr": "19854",
"title": "[FIX] Problem with attachment render",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19817",
"title": "[FIX] Issue with special message rendering",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
}
]
},
"3.10.0-rc.0": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.21.0-alpha.4235",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": [
{
"pr": "19931",
"title": "[NEW] Omnichannel Contact Center (Directory)",
"userLogin": "rafaelblink",
"milestone": "3.10.0",
"contributors": [
"rafaelblink",
"web-flow",
"renatobecker"
]
},
{
"pr": "19902",
"title": "[FIX] Hightlights validation on Account Preferences page",
"userLogin": "aKn1ghtOut",
"description": "This PR fixes two issues in the account settings \"preferences\" panel.\r\nOnce set, the \"Highlighted Words\" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable.\r\nSecondly, it tracks the changes to correctly identify if changes after the last \"save changes\" action have been made, using an \"updates\" state variable, instead of just comparing against the initialValue that does not change on clicking \"save changes\".",
"milestone": "3.10.0",
"contributors": [
"aKn1ghtOut",
"dougfabris",
"web-flow"
]
},
{
"pr": "19924",
"title": "[NEW] User preference for audio notifications",
"userLogin": "gabriellsh",
"description": "![image](https://user-images.githubusercontent.com/40830821/102808922-dfe32b00-439f-11eb-9268-6d0cf69dc64c.png)",
"contributors": [
"gabriellsh",
"sampaiodiego"
]
},
{
"pr": "19036",
"title": "[FIX] Update base image in Dockerfile.rhel",
"userLogin": "andykrohg",
"contributors": [
"andykrohg"
]
},
{
"pr": "19925",
"title": "[NEW] REST endpoints to add and retrieve Enterprise licenses",
"userLogin": "g-thome",
"contributors": [
"g-thome",
"sampaiodiego"
]
},
{
"pr": "19898",
"title": "[FIX] Admin Users screen sorting showing deactivated users in wrong order",
"userLogin": "alansikora",
"contributors": [
"alansikora"
]
},
{
"pr": "19926",
"title": "[NEW] REST Endpoint `instances.get`",
"userLogin": "g-thome",
"description": "Returns an array of instances on the cluster.",
"contributors": [
"g-thome",
"sampaiodiego",
"web-flow"
]
},
{
"pr": "19929",
"title": "Bump systeminformation from 4.30.1 to 4.33.0 in /ee/server/services",
"userLogin": "dependabot[bot]",
"contributors": [
"dependabot[bot]",
"web-flow"
]
},
{
"pr": "19834",
"title": "[FIX] Group DMs title when user changes his/her name",
"userLogin": "g-thome",
"contributors": [
"g-thome",
"sampaiodiego",
"web-flow"
]
},
{
"pr": "19928",
"title": "[IMPROVE] Show all screen when printing screen",
"userLogin": "gabriellsh",
"contributors": [
"gabriellsh"
]
},
{
"pr": "19808",
"title": "Rewrite: Room Header",
"userLogin": "ggazzo",
"contributors": [
"ggazzo",
"gabriellsh",
"web-flow"
]
},
{
"pr": "19850",
"title": "Chore: Add watch.settings to events whitelist",
"userLogin": "alansikora",
"contributors": [
"alansikora"
]
},
{
"pr": "19843",
"title": "Improve: Report Weekly Active Users to statistics",
"userLogin": "rodrigok",
"description": "Add the fields `uniqueUsersOfLastWeek`, `uniqueDevicesOfLastWeek` and `uniqueOSOfLastWeek` to the statistics report among the daily and monthly already reported.",
"milestone": "3.10.0",
"contributors": [
"rodrigok"
]
},
{
"pr": "19912",
"title": "Chore: Fix Caddy download URL in Snaps",
"userLogin": "geekgonecrazy",
"contributors": [
"geekgonecrazy",
"web-flow"
]
},
{
"pr": "19923",
"title": "[FIX] Issue with oembed",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19922",
"title": "Language update from LingoHub 🤖 on 2020-12-21Z",
"userLogin": "lingohub[bot]",
"contributors": [
null,
"sampaiodiego"
]
},
{
"pr": "19901",
"title": "Remove Heroku from readme",
"userLogin": "geekgonecrazy",
"contributors": [
"geekgonecrazy",
"web-flow"
]
},
{
"pr": "19875",
"title": "[FIX] RoomForeword",
"userLogin": "ggazzo",
"contributors": [
"ggazzo",
"web-flow"
]
},
{
"pr": "19879",
"title": "[FIX] User Info 'Local Time' translation keyword",
"userLogin": "J4r3tt",
"milestone": "3.10.0",
"contributors": [
"J4r3tt"
]
},
{
"pr": "19886",
"title": "[FIX] Issue with oembed",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19892",
"title": "[NEW] Update Checker Description",
"userLogin": "MartinSchoeler",
"milestone": "3.10.0",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19763",
"title": "[FIX] Some apps were not correctly enabled during startup in HA environments",
"userLogin": "thassiov",
"milestone": "3.9.2",
"contributors": [
"thassiov",
"web-flow",
"sampaiodiego"
]
},
{
"pr": "19876",
"title": "Regression: Fix member list Actions",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
},
{
"pr": "19874",
"title": "Regression: Fix Room Files for DMs",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
},
{
"pr": "19580",
"title": "[IMPROVE] Rewrite Room Files as React Component",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"ggazzo",
"web-flow"
]
},
{
"pr": "19870",
"title": "[FIX] User email showing [object Object]",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19867",
"title": "Regression: RoomMembers Permission",
"userLogin": "dougfabris",
"contributors": [
"dougfabris"
]
},
{
"pr": "19862",
"title": "[FIX] Download my data with file uploads",
"userLogin": "sampaiodiego",
"milestone": "3.9.2",
"contributors": [
"sampaiodiego",
"web-flow"
]
},
{
"pr": "19841",
"title": "[IMPROVE] Rewrite contextualbar RoomMembers as React Component ",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"ggazzo",
"web-flow"
]
},
{
"pr": "19863",
"title": "Chore: Change Youtube test to verify if has an iframe with max-width",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
},
{
"pr": "19854",
"title": "[FIX] Problem with attachment render",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19842",
"title": "[FIX] Forgot password endpoint return status",
"userLogin": "g-thome",
"milestone": "3.9.2",
"contributors": [
"g-thome"
]
},
{
"pr": "19844",
"title": "Bump ini from 1.3.5 to 1.3.8 in /ee/server/services",
"userLogin": "dependabot[bot]",
"contributors": [
"dependabot[bot]",
"web-flow"
]
},
{
"pr": "19768",
"title": "Chore: Update Pull Request template",
"userLogin": "rodrigok",
"description": "Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress.\r\n- Moved the checklists to inside comments\r\n- Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog\r\n- Remove the screenshot section, they can be added inside the description\r\n- Changed the proposed changes title to incentivizing the usage of images and videos",
"contributors": [
"rodrigok",
"web-flow"
]
},
{
"pr": "19598",
"title": "Chore: Remove extra parentheses from return type",
"userLogin": "ArnoSaine",
"contributors": [
"ArnoSaine",
"web-flow"
]
},
{
"pr": "19831",
"title": "Regression: Failed autolinker and markdown rendering",
"userLogin": "tassoevan",
"milestone": "3.10.0",
"contributors": [
"tassoevan"
]
},
{
"pr": "19825",
"title": "[FIX] Spotify oEmbed",
"userLogin": "tassoevan",
"milestone": "3.10.0",
"contributors": [
"tassoevan"
]
},
{
"pr": "19654",
"title": "Message parsing and rendering - Phase 1",
"userLogin": "tassoevan",
"milestone": "3.10.0",
"contributors": [
"tassoevan",
"web-flow"
]
},
{
"pr": "19817",
"title": "[FIX] Issue with special message rendering",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19816",
"title": "Regression: UserInfoWithData endpoint variable",
"userLogin": "dougfabris",
"contributors": [
"dougfabris"
]
},
{
"pr": "19793",
"title": "[FIX][ENTERPRISE] Omnichannel Department form is not correctly storing the list of departments allowed for forwarding",
"userLogin": "rafaelblink",
"milestone": "3.9.2",
"contributors": [
"rafaelblink",
"renatobecker",
"web-flow"
]
},
{
"pr": "19807",
"title": "Regression: User Info Context bar breaking.",
"userLogin": "gabriellsh",
"contributors": [
"gabriellsh"
]
},
{
"pr": "19803",
"title": "[IMPROVE] Rewrite contextualbar RoomMembers - AddUsers as React Component",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"web-flow",
"ggazzo"
]
},
{
"pr": "19762",
"title": "[FIX] 'Not Allowed' in message auditing",
"userLogin": "MartinSchoeler",
"milestone": "3.9.2",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19806",
"title": "Regression: fix broken members list",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19805",
"title": "[FIX] Custom Avatar",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler"
]
},
{
"pr": "19796",
"title": "Improve Docker container size by adding chown to ADD command",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
},
{
"pr": "19787",
"title": "Regression: roomInfo folder structure",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"ggazzo"
]
},
{
"pr": "19764",
"title": "[IMPROVE] Replace useClipboard",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"ggazzo"
]
},
{
"pr": "19761",
"title": "Regression: contextualBar folder structure",
"userLogin": "dougfabris",
"contributors": [
"dougfabris"
]
},
{
"pr": "19759",
"title": "[IMPROVE] Replace usePrefersReducedMotion",
"userLogin": "dougfabris",
"contributors": [
"dougfabris"
]
},
{
"pr": "19734",
"title": "[FIX] Image preview for image URLs on messages",
"userLogin": "g-thome",
"milestone": "3.9.1",
"contributors": [
"g-thome"
]
},
{
"pr": "19746",
"title": "[FIX] Sidebar presence will now correctly update for Omnichannel rooms",
"userLogin": "alansikora",
"milestone": "3.9.1",
"contributors": [
"alansikora"
]
},
{
"pr": "19749",
"title": "[FIX] Startup error when using MongoDB with a password containing special characters",
"userLogin": "sampaiodiego",
"milestone": "3.9.1",
"contributors": [
"sampaiodiego"
]
},
{
"pr": "19729",
"title": "[FIX] File Tab Order",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler",
"tassoevan",
"web-flow"
]
},
{
"pr": "19727",
"title": "[FIX] Emails not showing up in Admin/Users",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler",
"ggazzo"
]
},
{
"pr": "19748",
"title": "Regression: Add Members showing the wrong template",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"tassoevan",
"web-flow"
]
},
{
"pr": "19753",
"title": "Regression: \"My Account\" page doesn't load",
"userLogin": "g-thome",
"contributors": [
"g-thome"
]
},
{
"pr": "19516",
"title": "[FIX] Add fallback message when show notification content is disabled",
"userLogin": "youssef-md",
"milestone": "3.10.0",
"contributors": [
"youssef-md"
]
},
{
"pr": "19631",
"title": "Frontend folder structure",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
},
{
"pr": "19736",
"title": "bump fuselage",
"userLogin": "ggazzo",
"contributors": [
"ggazzo"
]
},
{
"pr": "19496",
"title": "[IMPROVE] Removed useEndpointDataExperimental hook usage",
"userLogin": "tassoevan",
"milestone": "3.10.0",
"contributors": [
"tassoevan"
]
},
{
"pr": "19723",
"title": "Merge EE and Community translations and LingoHub manual sync",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
},
{
"pr": "19725",
"title": "[FIX] Sidebar UI disappearing",
"userLogin": "gabriellsh",
"milestone": "3.9.1",
"contributors": [
"gabriellsh"
]
},
{
"pr": "19672",
"title": "[IMPROVE] Rewrite NotificationPreferences to React component",
"userLogin": "tiagoevanp",
"milestone": "3.10.0",
"contributors": [
"tiagoevanp",
"ggazzo",
"web-flow"
]
},
{
"pr": "19701",
"title": "[NEW] Custom scroll",
"userLogin": "MartinSchoeler",
"contributors": [
"MartinSchoeler",
"ggazzo",
"web-flow"
]
},
{
"pr": "19694",
"title": "[IMPROVE] Rewrite contextualbar RoomMembers - InviteUsers",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"ggazzo",
"web-flow"
]
},
{
"pr": "19674",
"title": "[IMPROVE] Rewrite contextualbar OTR panel",
"userLogin": "dougfabris",
"contributors": [
"dougfabris",
"ggazzo",
"web-flow"
]
},
{
"pr": "19690",
"title": "[FIX] UIKit Modal not scrolling",
"userLogin": "ggazzo",
"contributors": [
"ggazzo",
"web-flow"
]
},
{
"pr": "19720",
"title": "Merge master into develop & Set version to 3.10.0-develop",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego",
"web-flow"
]
}
]
},
"3.10.0-rc.1": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.21.0-alpha.4235",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": [
{
"pr": "19948",
"title": "Regression: Omnichannel Custom Fields Form no longer working after refactoring",
"userLogin": "renatobecker",
"description": "The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side.\r\nWhen the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears.",
"milestone": "3.10.0",
"contributors": [
"renatobecker"
]
},
{
"pr": "19941",
"title": "Regression: UserCard \"See full profile\" link broken",
"userLogin": "dougfabris",
"milestone": "3.10.0",
"contributors": [
"dougfabris",
"ggazzo"
]
},
{
"pr": "19944",
"title": "Regression: Admin Sidebar Scroll",
"userLogin": "gabriellsh",
"milestone": "3.10.0",
"contributors": [
"gabriellsh"
]
}
]
},
"3.10.0-rc.2": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.21.0-alpha.4235",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": [
{
"pr": "19950",
"title": "Regression: Fix sorting indicators on Admin Users page",
"userLogin": "alansikora",
"milestone": "3.10.0",
"contributors": [
"alansikora"
]
},
{
"pr": "19978",
"title": "Regression: Fix oembed",
"userLogin": "sampaiodiego",
"contributors": [
"sampaiodiego"
]
},
{
"pr": "19935",
"title": "[FIX] Status on searchlist",
"userLogin": "gabriellsh",
"milestone": "3.9.4",
"contributors": [
"gabriellsh",
"tassoevan",
"web-flow"
]
},
{
"pr": "19830",
"title": "[FIX] Omnichannel Departments Canned Responses",
"userLogin": "gabriellsh",
"milestone": "3.9.4",
"contributors": [
"gabriellsh",
"web-flow",
"renatobecker",
"dougfabris"
]
},
{
"pr": "19951",
"title": "Regression: Check permissions properly when fetching rooms in Omnichannel Directory",
"userLogin": "rafaelblink",
"milestone": "3.10.0",
"contributors": [
"rafaelblink",
"renatobecker",
"web-flow"
]
},
{
"pr": "19968",
"title": "Regression: Add missing translations on the Omnichannel Contact Center(Directory)",
"userLogin": "rafaelblink",
"milestone": "3.10.0",
"contributors": [
"rafaelblink",
"renatobecker"
]
},
{
"pr": "19946",
"title": "Regression: Header Styles fixes",
"userLogin": "ggazzo",
"milestone": "3.10.0",
"contributors": [
"ggazzo",
"tassoevan",
"web-flow"
]
},
{
"pr": "19945",
"title": "[FIX] Room scrolling to top after returns to a opened room",
"userLogin": "ggazzo",
"milestone": "3.9.4",
"contributors": [
"ggazzo",
"tassoevan",
"web-flow"
]
}
]
},
"3.10.0-rc.3": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.21.0-alpha.4235",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": [
{
"pr": "19981",
"title": "Regression: polishing licenses endpoints ",
"userLogin": "g-thome",
"contributors": [
"g-thome",
"sampaiodiego"
]
},
{
"pr": "19980",
"title": "Regression: Double Scrollbars on tables",
"userLogin": "gabriellsh",
"description": "Before:\r\n![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png)\r\n\r\n\r\nAfter:\r\n![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png)",
"contributors": [
"gabriellsh",
"ggazzo"
]
},
{
"pr": "19955",
"title": "Regression: Add currently running instance to instances.get endpoint",
"userLogin": "g-thome",
"milestone": "3.10.0",
"contributors": [
"g-thome",
"sampaiodiego"
]
}
]
},
"3.10.0-rc.4": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.21.0-alpha.4235",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": []
},
"3.10.0": {
"node_version": "12.18.4",
"npm_version": "6.14.8",
"apps_engine_version": "1.21.0-alpha.4235",
"mongo_versions": [
"3.4",
"3.6",
"4.0"
],
"pull_requests": []
}
}
}

@ -3,12 +3,11 @@
module.exports = {
require: [
'ts-node/register',
'babel-mocha-es6-compiler',
'babel-polyfill',
'@babel/register',
],
reporter: 'spec',
ui: 'bdd',
extension: 'js,ts',
extension: ['js', 'ts'],
spec: [
'app/**/*.tests.js',
'app/**/*.tests.ts',

@ -5,17 +5,17 @@ const fg = require('fast-glob');
const checkFiles = async (path, source, fix = false) => {
const sourceFile = JSON.parse(fs.readFileSync(`${ path }${ source }`, 'utf8'));
const regexVar = /__([a-zA-Z_]+?)__/g;
const regexVar = /__[a-zA-Z_]+__/g;
const usedKeys = Object.entries(sourceFile)
.filter(([, value]) => regexVar.exec(value))
.map(([key, value]) => {
const replaces = value.match(regexVar);
return {
key,
replaces,
};
});
})
.filter(({ replaces }) => !!replaces);
const validateKeys = (json) =>
usedKeys
@ -23,7 +23,7 @@ const checkFiles = async (path, source, fix = false) => {
.reduce((prev, cur) => {
const { key, replaces } = cur;
const miss = replaces.filter((replace) => json[key].indexOf(replace) === -1);
const miss = replaces.filter((replace) => json[key] && json[key].indexOf(replace) === -1);
if (miss.length > 0) {
prev.push({ key, miss });
@ -35,18 +35,18 @@ const checkFiles = async (path, source, fix = false) => {
const i18nFiles = await fg([`${ path }/**/*.i18n.json`]);
const removeMissingKeys = () => {
const allKeys = Object.keys(sourceFile);
i18nFiles.forEach((file) => {
const json = JSON.parse(fs.readFileSync(file, 'utf8'));
if (Object.keys(json).length === 0) {
return;
}
const invalidKeys = validateKeys(json).map(({ key }) => key);
const validKeys = allKeys.filter((key) => !invalidKeys.includes(key));
validateKeys(json)
.forEach(({ key }) => {
json[key] = null;
});
fs.writeFileSync(file, JSON.stringify(json, validKeys, 2));
fs.writeFileSync(file, JSON.stringify(json, null, 2));
});
};
@ -87,7 +87,6 @@ const checkFiles = async (path, source, fix = false) => {
(async () => {
try {
await checkFiles('./packages/rocketchat-i18n', '/i18n/en.i18n.json', process.argv[2] === '--fix');
await checkFiles('./ee', '/i18n/en.i18n.json', process.argv[2] === '--fix');
} catch (e) {
console.error(e);
process.exit(1);

@ -1,6 +1,6 @@
#!/bin/bash
curl -SLf "https://releases.rocket.chat/3.9.3/download/" -o rocket.chat.tgz
curl -SLf "https://releases.rocket.chat/3.10.0/download/" -o rocket.chat.tgz
tar xf rocket.chat.tgz --strip 1

@ -32,7 +32,7 @@ caddy_arch=_linux_$caddy_arch
echo "Downloading Caddy for $caddy_os/$caddy_arch$caddy_arm..."
caddy_file="caddy_linux_$caddy_arch${caddy_arm}_custom$caddy_dl_ext"
caddy_url="https://github.com/mholt/caddy/releases/download/$caddy_version/caddy_$caddy_version$caddy_arch$caddy_arm.tar.gz"
caddy_url="https://github.com/caddyserver/caddy/releases/download/$caddy_version/caddy_$caddy_version$caddy_arch$caddy_arm.tar.gz"
echo "$caddy_url"
curl -L "$caddy_url" -o "$caddy_file"

@ -7,7 +7,7 @@
# 5. `snapcraft snap`
name: rocketchat-server
version: 3.9.3
version: 3.10.0
summary: Rocket.Chat server
description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/
confinement: strict

@ -1,4 +1,3 @@
app/theme/client/vendor/fontello/css/fontello.css
app/meteor-autocomplete/client/autocomplete.css
app/katex/katex.min.css
app/emoji-emojione/client/*.css

@ -1,9 +1,251 @@
# 3.10.0
`2020-12-29 · 6 🎉 · 10 🚀 · 29 🐛 · 39 🔍 · 20 👩💻👨💻`
### Engine versions
- Node: `12.18.4`
- NPM: `6.14.8`
- MongoDB: `3.4, 3.6, 4.0`
- Apps-Engine: `1.21.0-alpha.4235`
### 🎉 New features
- Custom scroll ([#19701](https://github.com/RocketChat/Rocket.Chat/pull/19701))
- Omnichannel Contact Center (Directory) ([#19931](https://github.com/RocketChat/Rocket.Chat/pull/19931))
- REST Endpoint `instances.get` ([#19926](https://github.com/RocketChat/Rocket.Chat/pull/19926))
Returns an array of instances on the cluster.
- REST endpoints to add and retrieve Enterprise licenses ([#19925](https://github.com/RocketChat/Rocket.Chat/pull/19925))
- Update Checker Description ([#19892](https://github.com/RocketChat/Rocket.Chat/pull/19892))
- User preference for audio notifications ([#19924](https://github.com/RocketChat/Rocket.Chat/pull/19924))
![image](https://user-images.githubusercontent.com/40830821/102808922-dfe32b00-439f-11eb-9268-6d0cf69dc64c.png)
### 🚀 Improvements
- Removed useEndpointDataExperimental hook usage ([#19496](https://github.com/RocketChat/Rocket.Chat/pull/19496))
- Replace useClipboard ([#19764](https://github.com/RocketChat/Rocket.Chat/pull/19764))
- Replace usePrefersReducedMotion ([#19759](https://github.com/RocketChat/Rocket.Chat/pull/19759))
- Rewrite contextualbar OTR panel ([#19674](https://github.com/RocketChat/Rocket.Chat/pull/19674))
- Rewrite contextualbar RoomMembers - AddUsers as React Component ([#19803](https://github.com/RocketChat/Rocket.Chat/pull/19803))
- Rewrite contextualbar RoomMembers - InviteUsers ([#19694](https://github.com/RocketChat/Rocket.Chat/pull/19694))
- Rewrite contextualbar RoomMembers as React Component ([#19841](https://github.com/RocketChat/Rocket.Chat/pull/19841))
- Rewrite NotificationPreferences to React component ([#19672](https://github.com/RocketChat/Rocket.Chat/pull/19672))
- Rewrite Room Files as React Component ([#19580](https://github.com/RocketChat/Rocket.Chat/pull/19580))
- Show all screen when printing screen ([#19928](https://github.com/RocketChat/Rocket.Chat/pull/19928))
### 🐛 Bug fixes
- 'Not Allowed' in message auditing ([#19762](https://github.com/RocketChat/Rocket.Chat/pull/19762))
- **ENTERPRISE:** Omnichannel Department form is not correctly storing the list of departments allowed for forwarding ([#19793](https://github.com/RocketChat/Rocket.Chat/pull/19793))
- Add fallback message when show notification content is disabled ([#19516](https://github.com/RocketChat/Rocket.Chat/pull/19516) by [@youssef-md](https://github.com/youssef-md))
- Admin Users screen sorting showing deactivated users in wrong order ([#19898](https://github.com/RocketChat/Rocket.Chat/pull/19898))
- Custom Avatar ([#19805](https://github.com/RocketChat/Rocket.Chat/pull/19805))
- Download my data with file uploads ([#19862](https://github.com/RocketChat/Rocket.Chat/pull/19862))
- Emails not showing up in Admin/Users ([#19727](https://github.com/RocketChat/Rocket.Chat/pull/19727))
- File Tab Order ([#19729](https://github.com/RocketChat/Rocket.Chat/pull/19729))
- Forgot password endpoint return status ([#19842](https://github.com/RocketChat/Rocket.Chat/pull/19842))
- Group DMs title when user changes his/her name ([#19834](https://github.com/RocketChat/Rocket.Chat/pull/19834))
- Hightlights validation on Account Preferences page ([#19902](https://github.com/RocketChat/Rocket.Chat/pull/19902) by [@aKn1ghtOut](https://github.com/aKn1ghtOut))
This PR fixes two issues in the account settings "preferences" panel.
Once set, the "Highlighted Words" setting cannot be reset to an empty string. This was fixed by changing the string validation from checking the length to checking the type of variable.
Secondly, it tracks the changes to correctly identify if changes after the last "save changes" action have been made, using an "updates" state variable, instead of just comparing against the initialValue that does not change on clicking "save changes".
- Image preview for image URLs on messages ([#19734](https://github.com/RocketChat/Rocket.Chat/pull/19734))
- Issue with oembed ([#19923](https://github.com/RocketChat/Rocket.Chat/pull/19923))
- Issue with oembed ([#19886](https://github.com/RocketChat/Rocket.Chat/pull/19886))
- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817))
- Omnichannel Departments Canned Responses ([#19830](https://github.com/RocketChat/Rocket.Chat/pull/19830))
- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854))
- Room scrolling to top after returns to a opened room ([#19945](https://github.com/RocketChat/Rocket.Chat/pull/19945))
- RoomForeword ([#19875](https://github.com/RocketChat/Rocket.Chat/pull/19875))
- Sidebar presence will now correctly update for Omnichannel rooms ([#19746](https://github.com/RocketChat/Rocket.Chat/pull/19746))
- Sidebar UI disappearing ([#19725](https://github.com/RocketChat/Rocket.Chat/pull/19725))
- Some apps were not correctly enabled during startup in HA environments ([#19763](https://github.com/RocketChat/Rocket.Chat/pull/19763))
- Spotify oEmbed ([#19825](https://github.com/RocketChat/Rocket.Chat/pull/19825))
- Startup error when using MongoDB with a password containing special characters ([#19749](https://github.com/RocketChat/Rocket.Chat/pull/19749))
- Status on searchlist ([#19935](https://github.com/RocketChat/Rocket.Chat/pull/19935))
- UIKit Modal not scrolling ([#19690](https://github.com/RocketChat/Rocket.Chat/pull/19690))
- Update base image in Dockerfile.rhel ([#19036](https://github.com/RocketChat/Rocket.Chat/pull/19036) by [@andykrohg](https://github.com/andykrohg))
- User email showing [object Object] ([#19870](https://github.com/RocketChat/Rocket.Chat/pull/19870))
- User Info 'Local Time' translation keyword ([#19879](https://github.com/RocketChat/Rocket.Chat/pull/19879) by [@J4r3tt](https://github.com/J4r3tt))
<details>
<summary>🔍 Minor changes</summary>
- bump fuselage ([#19736](https://github.com/RocketChat/Rocket.Chat/pull/19736))
- Bump ini from 1.3.5 to 1.3.8 in /ee/server/services ([#19844](https://github.com/RocketChat/Rocket.Chat/pull/19844) by [@dependabot[bot]](https://github.com/dependabot[bot]))
- Bump systeminformation from 4.30.1 to 4.33.0 in /ee/server/services ([#19929](https://github.com/RocketChat/Rocket.Chat/pull/19929) by [@dependabot[bot]](https://github.com/dependabot[bot]))
- Chore: Fix Caddy download URL in Snaps ([#19912](https://github.com/RocketChat/Rocket.Chat/pull/19912))
- Chore: Add watch.settings to events whitelist ([#19850](https://github.com/RocketChat/Rocket.Chat/pull/19850))
- Chore: Change Youtube test to verify if has an iframe with max-width ([#19863](https://github.com/RocketChat/Rocket.Chat/pull/19863))
- Chore: Remove extra parentheses from return type ([#19598](https://github.com/RocketChat/Rocket.Chat/pull/19598) by [@ArnoSaine](https://github.com/ArnoSaine))
- Chore: Update Pull Request template ([#19768](https://github.com/RocketChat/Rocket.Chat/pull/19768))
Improve the template of Pull Requests in order to make it clear reducing duplicated information and removing the visible checklists that were generating noise and misunderstanding with the PR progress.
- Moved the checklists to inside comments
- Merge the changelog and proposed changes sections to have a single source of description that goes to the changelog
- Remove the screenshot section, they can be added inside the description
- Changed the proposed changes title to incentivizing the usage of images and videos
- Frontend folder structure ([#19631](https://github.com/RocketChat/Rocket.Chat/pull/19631))
- Improve Docker container size by adding chown to ADD command ([#19796](https://github.com/RocketChat/Rocket.Chat/pull/19796))
- Improve: Report Weekly Active Users to statistics ([#19843](https://github.com/RocketChat/Rocket.Chat/pull/19843))
Add the fields `uniqueUsersOfLastWeek`, `uniqueDevicesOfLastWeek` and `uniqueOSOfLastWeek` to the statistics report among the daily and monthly already reported.
- Language update from LingoHub 🤖 on 2020-12-21Z ([#19922](https://github.com/RocketChat/Rocket.Chat/pull/19922))
- Merge EE and Community translations and LingoHub manual sync ([#19723](https://github.com/RocketChat/Rocket.Chat/pull/19723))
- Merge master into develop & Set version to 3.10.0-develop ([#19720](https://github.com/RocketChat/Rocket.Chat/pull/19720))
- Message parsing and rendering - Phase 1 ([#19654](https://github.com/RocketChat/Rocket.Chat/pull/19654))
- Regression: "My Account" page doesn't load ([#19753](https://github.com/RocketChat/Rocket.Chat/pull/19753))
- Regression: Add currently running instance to instances.get endpoint ([#19955](https://github.com/RocketChat/Rocket.Chat/pull/19955))
- Regression: Add Members showing the wrong template ([#19748](https://github.com/RocketChat/Rocket.Chat/pull/19748))
- Regression: Add missing translations on the Omnichannel Contact Center(Directory) ([#19968](https://github.com/RocketChat/Rocket.Chat/pull/19968))
- Regression: Admin Sidebar Scroll ([#19944](https://github.com/RocketChat/Rocket.Chat/pull/19944))
- Regression: Check permissions properly when fetching rooms in Omnichannel Directory ([#19951](https://github.com/RocketChat/Rocket.Chat/pull/19951))
- Regression: contextualBar folder structure ([#19761](https://github.com/RocketChat/Rocket.Chat/pull/19761))
- Regression: Double Scrollbars on tables ([#19980](https://github.com/RocketChat/Rocket.Chat/pull/19980))
Before:
![image](https://user-images.githubusercontent.com/40830821/103242719-0ec84680-4936-11eb-87a7-68b6eea8de7b.png)
After:
![image](https://user-images.githubusercontent.com/40830821/103242680-ee988780-4935-11eb-99e2-a95de99f78f1.png)
- Regression: Failed autolinker and markdown rendering ([#19831](https://github.com/RocketChat/Rocket.Chat/pull/19831))
- Regression: fix broken members list ([#19806](https://github.com/RocketChat/Rocket.Chat/pull/19806))
- Regression: Fix member list Actions ([#19876](https://github.com/RocketChat/Rocket.Chat/pull/19876))
- Regression: Fix oembed ([#19978](https://github.com/RocketChat/Rocket.Chat/pull/19978))
- Regression: Fix Room Files for DMs ([#19874](https://github.com/RocketChat/Rocket.Chat/pull/19874))
- Regression: Fix sorting indicators on Admin Users page ([#19950](https://github.com/RocketChat/Rocket.Chat/pull/19950))
- Regression: Header Styles fixes ([#19946](https://github.com/RocketChat/Rocket.Chat/pull/19946))
- Regression: Omnichannel Custom Fields Form no longer working after refactoring ([#19948](https://github.com/RocketChat/Rocket.Chat/pull/19948))
The Omnichannel `Custom Fields` form is not working anymore after some refactorings on client-side.
When the user clicks on `Custom Field` in the Omnichannel menu, a blank page appears.
- Regression: polishing licenses endpoints ([#19981](https://github.com/RocketChat/Rocket.Chat/pull/19981))
- Regression: roomInfo folder structure ([#19787](https://github.com/RocketChat/Rocket.Chat/pull/19787))
- Regression: RoomMembers Permission ([#19867](https://github.com/RocketChat/Rocket.Chat/pull/19867))
- Regression: User Info Context bar breaking. ([#19807](https://github.com/RocketChat/Rocket.Chat/pull/19807))
- Regression: UserCard "See full profile" link broken ([#19941](https://github.com/RocketChat/Rocket.Chat/pull/19941))
- Regression: UserInfoWithData endpoint variable ([#19816](https://github.com/RocketChat/Rocket.Chat/pull/19816))
- Remove Heroku from readme ([#19901](https://github.com/RocketChat/Rocket.Chat/pull/19901))
- Rewrite: Room Header ([#19808](https://github.com/RocketChat/Rocket.Chat/pull/19808))
</details>
### 👩💻👨💻 Contributors 😍
- [@ArnoSaine](https://github.com/ArnoSaine)
- [@J4r3tt](https://github.com/J4r3tt)
- [@aKn1ghtOut](https://github.com/aKn1ghtOut)
- [@andykrohg](https://github.com/andykrohg)
- [@dependabot[bot]](https://github.com/dependabot[bot])
- [@youssef-md](https://github.com/youssef-md)
### 👩💻👨💻 Core Team 🤓
- [@MartinSchoeler](https://github.com/MartinSchoeler)
- [@alansikora](https://github.com/alansikora)
- [@dougfabris](https://github.com/dougfabris)
- [@g-thome](https://github.com/g-thome)
- [@gabriellsh](https://github.com/gabriellsh)
- [@geekgonecrazy](https://github.com/geekgonecrazy)
- [@ggazzo](https://github.com/ggazzo)
- [@rafaelblink](https://github.com/rafaelblink)
- [@renatobecker](https://github.com/renatobecker)
- [@rodrigok](https://github.com/rodrigok)
- [@sampaiodiego](https://github.com/sampaiodiego)
- [@tassoevan](https://github.com/tassoevan)
- [@thassiov](https://github.com/thassiov)
- [@tiagoevanp](https://github.com/tiagoevanp)
# 3.9.3
`2020-12-18 · 2 🐛 · 1 👩💻👨💻`
### Important XSS Security hotfix
### Engine versions
- Node: `12.18.4`
- NPM: `6.14.8`
@ -112,8 +354,8 @@
- Bundle Size Client ([#19533](https://github.com/RocketChat/Rocket.Chat/pull/19533))
temporarily removes some codeblock languages
Moved some libraries to dynamic imports
temporarily removes some codeblock languages
Moved some libraries to dynamic imports
Removed some shared code not used on the client side
- Forward Omnichannel room to agent in another department ([#19576](https://github.com/RocketChat/Rocket.Chat/pull/19576) by [@mrfigueiredo](https://github.com/mrfigueiredo))
@ -296,6 +538,26 @@
- [@sampaiodiego](https://github.com/sampaiodiego)
- [@tiagoevanp](https://github.com/tiagoevanp)
# 3.8.4
`2020-12-18 · 2 🐛 · 1 👩💻👨💻`
### Engine versions
- Node: `12.18.4`
- NPM: `6.14.8`
- MongoDB: `3.4, 3.6, 4.0`
- Apps-Engine: `1.19.0`
### 🐛 Bug fixes
- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817))
- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854))
### 👩💻👨💻 Core Team 🤓
- [@MartinSchoeler](https://github.com/MartinSchoeler)
# 3.8.3
`2020-12-05 · 1 🐛 · 1 👩💻👨💻`
@ -669,6 +931,26 @@
- [@thassiov](https://github.com/thassiov)
- [@tiagoevanp](https://github.com/tiagoevanp)
# 3.7.4
`2020-12-18 · 2 🐛 · 1 👩💻👨💻`
### Engine versions
- Node: `12.18.4`
- NPM: `6.14.8`
- MongoDB: `3.4, 3.6, 4.0`
- Apps-Engine: `1.18.0`
### 🐛 Bug fixes
- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817))
- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854))
### 👩💻👨💻 Core Team 🤓
- [@MartinSchoeler](https://github.com/MartinSchoeler)
# 3.7.3
`2020-12-05 · 1 🐛 · 1 👩💻👨💻`
@ -1130,10 +1412,8 @@
- **2FA:** Password enforcement setting and 2FA protection when saving settings or resetting E2E encryption ([#18640](https://github.com/RocketChat/Rocket.Chat/pull/18640))
- Increase the 2FA remembering time from 5min to 30min
- Add new setting to enforce 2FA password fallback (enabled only for new installations)
- Increase the 2FA remembering time from 5min to 30min
- Add new setting to enforce 2FA password fallback (enabled only for new installations)
- Require 2FA to save settings and reset E2E Encryption keys
- **Omnichannel:** Allow set other agent status via method `livechat:changeLivechatStatus ` ([#18571](https://github.com/RocketChat/Rocket.Chat/pull/18571))
@ -1151,7 +1431,7 @@
- 2FA by Email setting showing for the user even when disabled by the admin ([#18473](https://github.com/RocketChat/Rocket.Chat/pull/18473))
The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication
The option to disable/enable the **Two-factor authentication via Email** at `Account > Security > Two Factor Authentication
` was visible even when the setting **Enable Two Factor Authentication via Email** at `Admin > Accounts > Two Factor Authentication` was disabled leading to misbehavior since the functionality was disabled.
- Agents enabledDepartment attribute not set on collection ([#18614](https://github.com/RocketChat/Rocket.Chat/pull/18614) by [@paulobernardoaf](https://github.com/paulobernardoaf))
@ -1501,16 +1781,13 @@
- Mention autocomplete UI and performance improvements ([#18309](https://github.com/RocketChat/Rocket.Chat/pull/18309))
* New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5)
* The UI shows whenever the user is not a member of the room
* The UI shows when the suggestion came from the last messages for quick selection/reply
* The suggestions follow this order:
* The user with the exact username and member of the room
* The user with the exact username but not a member of the room (if allowed to list non-members)
* The users containing the text in username, name or nickname and member of the room
* New setting to configure the number of suggestions `Admin > Layout > User Interface > Number of users' autocomplete suggestions` (default 5)
* The UI shows whenever the user is not a member of the room
* The UI shows when the suggestion came from the last messages for quick selection/reply
* The suggestions follow this order:
* The user with the exact username and member of the room
* The user with the exact username but not a member of the room (if allowed to list non-members)
* The users containing the text in username, name or nickname and member of the room
* The users containing the text in username, name or nickname and not a member of the room (if allowed to list non-members)
- Message action styles ([#18190](https://github.com/RocketChat/Rocket.Chat/pull/18190))
@ -1852,10 +2129,10 @@
- Split NOTIFICATIONS_SCHEDULE_DELAY into three separate variables ([#17669](https://github.com/RocketChat/Rocket.Chat/pull/17669) by [@jazztickets](https://github.com/jazztickets))
Email notification delay can now be customized with the following environment variables:
NOTIFICATIONS_SCHEDULE_DELAY_ONLINE
NOTIFICATIONS_SCHEDULE_DELAY_AWAY
NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE
Email notification delay can now be customized with the following environment variables:
NOTIFICATIONS_SCHEDULE_DELAY_ONLINE
NOTIFICATIONS_SCHEDULE_DELAY_AWAY
NOTIFICATIONS_SCHEDULE_DELAY_OFFLINE
Setting the value to -1 disable notifications for that type.
- Threads ([#17416](https://github.com/RocketChat/Rocket.Chat/pull/17416))
@ -2255,11 +2532,11 @@
- **ENTERPRISE:** Omnichannel Last-Chatted Agent Preferred option ([#17666](https://github.com/RocketChat/Rocket.Chat/pull/17666))
If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks:
1 - The visitor object for any stored agent that the visitor has previously talked to;
2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room;
If activated, this feature will store the last agent that assisted each Omnichannel visitor when a conversation is taken. So, when a visitor returns(it works with any entry point, Livechat, Facebook, REST API, and so on) and starts a new chat, the routing system checks:
1 - The visitor object for any stored agent that the visitor has previously talked to;
2 - If a previous agent is not found, the system will try to find a previous conversation of the same visitor. If a room is found, the system will get the previous agent from the room;
After this process, if an agent has been found, the system will check the agent's availability to assist the new chat. If it's not available, then the routing system will get the next available agent in the queue.
- **ENTERPRISE:** Support for custom Livechat registration form fields ([#17581](https://github.com/RocketChat/Rocket.Chat/pull/17581))
@ -2364,12 +2641,9 @@
- Notification sounds ([#17616](https://github.com/RocketChat/Rocket.Chat/pull/17616))
* Global CDN config was ignored when loading the sound files
* Upload of custom sounds wasn't getting the file extension correctly
* Some translations were missing
* Global CDN config was ignored when loading the sound files
* Upload of custom sounds wasn't getting the file extension correctly
* Some translations were missing
* Edit and delete of custom sounds were not working correctly
- Omnichannel departments are not saved when the offline channel name is not defined ([#17553](https://github.com/RocketChat/Rocket.Chat/pull/17553))
@ -2657,19 +2931,14 @@
- Better Push and Email Notification logic ([#17357](https://github.com/RocketChat/Rocket.Chat/pull/17357))
We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules:
- When the user is online the notification is scheduled to be sent in 120 seconds
- When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away
- When the user is offline the notification is scheduled to be sent right away
- When the user reads a channel all the notifications for that user are removed (clear queue)
- When a notification is processed to be sent to a user and there are other scheduled notifications:
- All the scheduled notifications for that user are rescheduled to now
We are still using the same logic to define which notifications every new message will generate, it takes some servers' settings, users's preferences and subscriptions' settings in consideration to determine who will receive each notification type (desktop, audio, email and mobile push), but now it doesn't check the user's status (online, away, offline) for email and mobile push notifications but send those notifications to a new queue with the following rules:
- When the user is online the notification is scheduled to be sent in 120 seconds
- When the user is away the notification is scheduled to be sent in 120 seconds minus the amount of time he is away
- When the user is offline the notification is scheduled to be sent right away
- When the user reads a channel all the notifications for that user are removed (clear queue)
- When a notification is processed to be sent to a user and there are other scheduled notifications:
- All the scheduled notifications for that user are rescheduled to now
- The current notification goes back to the queue to be processed ordered by creation date
- Buttons to check/uncheck all users and channels on import ([#17207](https://github.com/RocketChat/Rocket.Chat/pull/17207))
@ -3032,7 +3301,7 @@
- Translation via MS translate ([#16363](https://github.com/RocketChat/Rocket.Chat/pull/16363) by [@mrsimpson](https://github.com/mrsimpson))
Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages.
Adds Microsoft's translation service (https://translator.microsoft.com/) as a provider for translation of messages.
In addition to implementing the interface (similar to google and DeepL), a small change has been done in order to display the translation provider on the UI.
- Two Factor authentication via email ([#15949](https://github.com/RocketChat/Rocket.Chat/pull/15949))
@ -4103,6 +4372,26 @@
- [@sampaiodiego](https://github.com/sampaiodiego)
- [@tassoevan](https://github.com/tassoevan)
# 2.4.14
`2020-12-18 · 2 🐛 · 1 👩💻👨💻`
### Engine versions
- Node: `8.17.0`
- NPM: `6.13.4`
- MongoDB: `3.4, 3.6, 4.0`
- Apps-Engine: `1.11.2`
### 🐛 Bug fixes
- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817))
- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854))
### 👩💻👨💻 Core Team 🤓
- [@MartinSchoeler](https://github.com/MartinSchoeler)
# 2.4.12
`2020-05-11 · 1 🐛 · 1 👩💻👨💻`
@ -5622,6 +5911,25 @@
- [@sampaiodiego](https://github.com/sampaiodiego)
- [@tassoevan](https://github.com/tassoevan)
# 1.3.5
`2020-12-18 · 2 🐛 · 1 👩💻👨💻`
### Engine versions
- Node: `8.11.4`
- NPM: `6.4.1`
- Apps-Engine: `1.5.1`
### 🐛 Bug fixes
- Issue with special message rendering ([#19817](https://github.com/RocketChat/Rocket.Chat/pull/19817))
- Problem with attachment render ([#19854](https://github.com/RocketChat/Rocket.Chat/pull/19854))
### 👩💻👨💻 Core Team 🤓
- [@MartinSchoeler](https://github.com/MartinSchoeler)
# 1.3.3
`2019-11-19 · 2 🐛 · 2 👩💻👨💻`
@ -14532,4 +14840,4 @@
- [@graywolf336](https://github.com/graywolf336)
- [@marceloschmidt](https://github.com/marceloschmidt)
- [@rodrigok](https://github.com/rodrigok)
- [@sampaiodiego](https://github.com/sampaiodiego)
- [@sampaiodiego](https://github.com/sampaiodiego)

@ -26,7 +26,6 @@
* [IndieHosters](#indiehosters)
* [Ubuntu 16.04](#ubuntu-1604)
* [Cloudron.io](#cloudronio)
* [Heroku](#heroku)
* [Helm Kubernetes](#helm-kubernetes)
* [Scalingo](#scalingo)
* [Sloppy.io](#sloppyio)
@ -142,11 +141,6 @@ Install Rocket.Chat on [Cloudron](https://cloudron.io) Smartserver:
[![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=chat.rocket.cloudronapp)
## Heroku
Host your own Rocket.Chat server for **FREE** with [One-Click Deploy](https://heroku.com/deploy).
[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/RocketChat/Rocket.Chat/tree/master)
## Helm Kubernetes
Deploy on Kubernetes using the official [helm chart](https://github.com/helm/charts/tree/master/stable/rocketchat).

@ -0,0 +1,6 @@
import { APIClass } from '.';
export declare const API: {
v1: APIClass;
default: APIClass;
};

@ -37,5 +37,6 @@ import './v1/webdav';
import './v1/oauthapps';
import './v1/custom-sounds';
import './v1/custom-user-status';
import './v1/instances';
export { API, APIClass, defaultRateLimiterOptions } from './api';

@ -1,7 +1,6 @@
import s from 'underscore.string';
import { Users } from '../../../models/server/raw';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { escapeRegExp } from '../../../../lib/escapeRegExp';
export async function findUsersToAutocomplete({ uid, selector }) {
if (!await hasPermissionAsync(uid, 'view-outside-room')) {
@ -23,7 +22,7 @@ export async function findUsersToAutocomplete({ uid, selector }) {
limit: 10,
};
const users = await Users.findActiveByUsernameOrNameRegexWithExceptionsAndConditions(new RegExp(s.escapeRegExp(selector.term), 'i'), exceptions, conditions, options).toArray();
const users = await Users.findActiveByUsernameOrNameRegexWithExceptionsAndConditions(new RegExp(escapeRegExp(selector.term), 'i'), exceptions, conditions, options).toArray();
return {
items: users,

@ -0,0 +1,28 @@
import { getInstanceConnection } from '../../../../server/stream/streamBroadcast';
import { hasPermission } from '../../../authorization/server';
import { API } from '../api';
import InstanceStatus from '../../../models/server/models/InstanceStatus';
import { IInstanceStatus } from '../../../../definition/IInstanceStatus';
API.v1.addRoute('instances.get', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'view-statistics')) {
return API.v1.unauthorized();
}
const instances = InstanceStatus.find().fetch();
return API.v1.success({
instances: instances.map((instance: IInstanceStatus) => {
const connection = getInstanceConnection(instance);
if (connection) {
delete connection.instanceRecord;
}
return {
...instance,
connection,
};
}),
});
},
});

@ -5,7 +5,6 @@ import { check } from 'meteor/check';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { EJSON } from 'meteor/ejson';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import s from 'underscore.string';
import { hasRole, hasPermission } from '../../../authorization/server';
import { Info } from '../../../utils/server';
@ -15,6 +14,7 @@ import { API } from '../api';
import { getDefaultUserFields } from '../../../utils/server/functions/getDefaultUserFields';
import { getURL } from '../../../utils/lib/getURL';
import { StdOut } from '../../../logger/server/streamer';
import { escapeHTML } from '../../../../lib/escapeHTML';
// DEPRECATED
@ -128,9 +128,9 @@ API.v1.addRoute('shield.svg', { authRequired: false, rateLimiterOptions: { numRe
const width = leftSize + rightSize;
const height = 20;
channel = s.escapeHTML(channel);
text = s.escapeHTML(text);
name = s.escapeHTML(name);
channel = escapeHTML(channel);
text = escapeHTML(text);
name = escapeHTML(name);
return {
headers: { 'Content-Type': 'image/svg+xml;charset=utf-8' },

@ -1,432 +0,0 @@
.rc-apps-section,
.rc-apps-marketplace {
display: flex;
overflow: auto;
flex-direction: column;
height: 100vh;
padding: 1.25rem 2rem;
font-size: 14px;
&.page-settings .rc-apps-container {
a {
color: var(--rc-color-button-primary);
font-weight: 500;
}
}
h1 {
margin-bottom: 0;
letter-spacing: 0;
text-transform: initial;
color: var(--color-dark-medium);
font-size: 22px;
font-weight: normal;
line-height: 28px;
}
h2 {
margin-top: 10px;
margin-bottom: 0;
letter-spacing: 0;
text-transform: initial;
color: var(--color-dark);
font-size: 16px;
font-weight: 500;
line-height: 24px;
}
h3 {
margin-bottom: 0;
text-align: left;
letter-spacing: 0;
text-transform: initial;
color: var(--color-gray);
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
.rc-apps-container {
margin-top: 0;
padding-bottom: 15px;
}
.rc-apps-container__header {
padding-top: 10px;
border-bottom: 1.5px solid var(--color-gray-lightest);
}
/*
.js-install {
margin-top: 6px;
} */
.content {
/* display: block !important; */
padding: 0 !important;
> .rc-apps-container {
display: block;
overflow-y: scroll;
padding: 0 !important;
}
> .rc-apps-details {
display: block;
}
}
.rc-apps-category {
margin-right: 8px;
padding: 8px;
text-align: left;
letter-spacing: -0.17px;
text-transform: uppercase;
color: #9da2a9;
border-radius: 2px;
background: var(--color-gray-lightest);
font-size: 12px;
font-weight: 500;
}
.app-enable-loading .loading-animation {
margin-left: 50px;
justify-content: left;
}
.apps-error {
display: flex;
flex-direction: column;
width: 100%;
height: calc(100% - 60px);
padding: 25px 40px;
font-size: 45px;
align-items: center;
justify-content: center;
}
.rc-table-avatar {
width: 40px;
height: 40px;
margin: 0 7px;
}
.rc-table-info {
height: 40px;
margin: 0 7px;
}
.rc-app-price {
position: relative;
top: -3px;
}
.rc-table-td--medium {
width: 300px;
}
.rc-table td {
padding: 0.5rem 0;
padding-right: 10px;
}
&__wrap-actions {
& > .loading {
display: none;
}
&.loading {
& > .loading {
display: block;
font-size: 11px;
font-weight: 600;
& > .rc-icon--loading {
animation: spin 1s linear infinite;
}
}
& > .apps-installer {
display: none;
}
}
}
.arrow-up {
transform: rotate(180deg);
}
&.page-settings .content .rocket-form .section {
padding: 0 2.5em;
border-bottom: none;
&:hover {
background-color: var(--rc-color-primary-lightest);
}
}
.rc-table-content {
display: flex;
overflow-x: auto;
flex-direction: column;
flex: 1 1 100%;
height: 100vh;
margin-top: 20px;
& .rc-form-filters {
margin: 8px 0;
}
& .js-sort {
cursor: pointer;
&.is-sorting .table-fake-th .rc-icon {
opacity: 1;
}
}
& .table-fake-th {
&:hover .rc-icon {
opacity: 1;
}
& .rc-icon {
transition: opacity 0.3s;
opacity: 0;
font-size: 1rem;
}
}
& tbody .rc-table-tr .rc-apps-section__app-menu-trigger {
visibility: hidden;
}
& tbody .rc-table-tr:hover .rc-apps-section__app-menu-trigger {
visibility: visible;
}
& tbody .rc-table-tr:not(.table-no-click):not(.table-no-pointer):hover {
background-color: #f7f8fa;
}
& .rc-table-info {
margin: 0;
justify-content: center;
& .rc-table-title,
& .rc-table-subtitle {
font-size: 0.875rem;
line-height: 1.25rem;
}
& .rc-apps-categories {
display: flex;
height: 1.25rem;
margin: 0 -0.25rem;
align-items: center;
flex-wrap: wrap;
& .rc-apps-category {
overflow: hidden;
flex: 0 0 auto;
box-sizing: border-box;
margin: 0.125rem 0.25rem;
padding: 0.0625rem 0.25rem;
text-transform: none;
text-overflow: ellipsis;
color: var(--color-gray);
background-color: var(--color-gray-lightest);
font-size: 0.625rem;
font-weight: 500;
line-height: 0.875rem;
}
}
}
}
@media (width <= 700px) {
.rc-table-content {
& th:not(:first-child),
& td:not(:first-child) {
display: none;
}
}
}
&__app-menu-trigger {
position: relative;
display: flex;
flex: 0 0 auto;
margin-left: auto;
padding: 0;
font-size: 0.875rem;
line-height: 1.25rem;
align-items: center;
appearance: none;
margin-inline-start: auto;
&:active {
transform: translateY(2px);
opacity: 0.9;
}
&:active::before {
top: -2px;
}
&::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: "";
cursor: pointer;
}
& .rc-icon {
margin: 0;
}
}
&__spinning-icon {
animation: spin 1s linear infinite;
}
&__button--working {
opacity: 0.6;
}
&__status {
width: 100%;
color: var(--rc-color-primary-light);
line-height: 40px;
&--warning {
color: var(--rc-color-alert);
}
&--failed {
color: var(--rc-color-error);
}
}
&__status-column {
width: 150px;
}
tr .rc-apps-section__table-button--hideable {
visibility: hidden;
}
tr .rc-apps-section__table-button--working,
tr:hover .rc-apps-section__table-button--hideable {
visibility: visible;
}
.rc-apps-section__table-button--working {
opacity: 0.6;
}
}
.rc-game {
&__list {
.rc-table-title {
width: 135px;
}
}
&__container {
height: calc(100% - 79px);
}
&__close {
position: absolute;
top: -30px;
right: -25px;
cursor: pointer;
color: #e9ebee;
font-size: 20px;
&:hover {
color: white;
}
}
&__main {
min-width: 400px;
height: 100%;
}
}
.rc-game-modal {
&__container {
margin: -14px -14px -18px;
}
&__main {
min-height: 730px;
}
}
.rc-icon.game-center__invite-players-icon.game-center__invite-players-icon--plus {
font-size: 18px;
&:hover {
color: #1d74f5;
}
}
@keyframes play90 {
0% {
right: -798px;
}
100% {
right: 2px;
}
}

@ -3,6 +3,7 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { modal } from '../../../ui-utils/client';
import { APIClient, t, handleError } from '../../../utils/client';
import './gameCenter.html';
const getExternalComponents = async (instance) => {
try {

@ -1,31 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { APIClient } from '../../../utils/client';
import { TabBar } from '../../../ui-utils/client';
import { settings } from '../../../settings/client';
import './gameCenter.html';
Meteor.startup(function() {
Tracker.autorun(async function() {
if (!settings.get('Apps_Game_Center_enabled')) {
return TabBar.removeButton('gameCenter');
}
const { externalComponents } = await APIClient.get('apps/externalComponents');
if (!externalComponents.length) {
return TabBar.removeButton('gameCenter');
}
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'gameCenter',
i18nTitle: 'Apps_Game_Center',
icon: 'game',
template: 'GameCenter',
order: -1,
});
});
});

@ -0,0 +1,28 @@
import { useMemo } from 'react';
import { useSetting } from '../../../../client/contexts/SettingsContext';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { useEndpointData } from '../../../../client/hooks/useEndpointData';
import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState';
addAction('game-center', () => {
const enabled = useSetting('Apps_Game_Center_enabled');
const { value = { externalComponents: [] }, phase: state, error } = useEndpointData('/apps/externalComponents');
const hasExternalComponents = value && value.externalComponents.length > 0;
const hasError = !!error;
return useMemo(() =>
(enabled
&& state === AsyncStatePhase.LOADING
&& !hasError
&& hasExternalComponents
? {
groups: ['channel', 'group', 'direct'],
id: 'game-center',
title: 'Apps_Game_Center',
icon: 'game',
template: 'GameCenter',
order: -1,
} : null), [enabled, hasError, hasExternalComponents, state]);
});

@ -3,7 +3,6 @@ import { Match } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import _ from 'underscore';
import s from 'underscore.string';
import * as Mailer from '../../../mailer/server/api';
import { settings } from '../../../settings/server';
@ -18,6 +17,8 @@ import {
} from '../lib/restrictLoginAttempts';
import './settings';
import { getClientAddress } from '../../../../server/lib/getClientAddress';
import { escapeHTML } from '../../../../lib/escapeHTML';
import { escapeRegExp } from '../../../../lib/escapeRegExp';
Accounts.config({
forbidClientAccountCreation: true,
@ -47,9 +48,9 @@ Accounts.emailTemplates.userToActivate = {
const email = options.reason ? 'Accounts_Admin_Email_Approval_Needed_With_Reason_Default' : 'Accounts_Admin_Email_Approval_Needed_Default';
return Mailer.replace(TAPi18n.__(email), {
name: s.escapeHTML(options.name),
email: s.escapeHTML(options.email),
reason: s.escapeHTML(options.reason),
name: escapeHTML(options.name),
email: escapeHTML(options.email),
reason: escapeHTML(options.reason),
});
},
};
@ -69,7 +70,7 @@ Accounts.emailTemplates.userActivated = {
const action = active ? activated : 'Deactivated';
return Mailer.replace(TAPi18n.__(`Accounts_Email_${ action }`), {
name: s.escapeHTML(name),
name: escapeHTML(name),
});
},
};
@ -121,8 +122,8 @@ Accounts.emailTemplates.enrollAccount.subject = function(user) {
Accounts.emailTemplates.enrollAccount.html = function(user = {}/* , url*/) {
return Mailer.replace(enrollAccountTemplate, {
name: s.escapeHTML(user.name),
email: user.emails && user.emails[0] && s.escapeHTML(user.emails[0].address),
name: escapeHTML(user.name),
email: user.emails && user.emails[0] && escapeHTML(user.emails[0].address),
});
};
@ -370,7 +371,7 @@ Accounts.validateNewUser(function(user) {
}
let domainWhiteList = settings.get('Accounts_AllowedDomainsList');
if (_.isEmpty(s.trim(domainWhiteList))) {
if (_.isEmpty(domainWhiteList?.trim())) {
return true;
}
@ -378,7 +379,7 @@ Accounts.validateNewUser(function(user) {
if (user.emails && user.emails.length > 0) {
const email = user.emails[0].address;
const inWhiteList = domainWhiteList.some((domain) => email.match(`@${ RegExp.escape(domain) }$`));
const inWhiteList = domainWhiteList.some((domain) => email.match(`@${ escapeRegExp(domain) }$`));
if (inWhiteList === false) {
throw new Meteor.Error('error-invalid-domain');

@ -1,12 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { hasAtLeastOnePermission } from './hasPermission';
import { registerAdminSidebarItem } from '../../../client/admin';
import { CachedCollectionManager } from '../../ui-cached-collection';
import { APIClient } from '../../utils/client';
import { Roles } from '../../models/client';
import { rolesStreamer } from './lib/streamer';
import { registerAdminSidebarItem } from '../../../client/views/admin';
Meteor.startup(() => {
CachedCollectionManager.onLogin(async () => {

@ -1,76 +1,49 @@
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { Tracker } from 'meteor/tracker';
import s from 'underscore.string';
import Autolinker from 'autolinker';
import { escapeRegExp } from '../../../lib/escapeRegExp';
export const createAutolinkerMessageRenderer = (config) =>
(message) => {
if (!message.html?.trim()) {
return message;
}
let msgParts;
let regexTokens;
if (message.tokens && message.tokens.length) {
regexTokens = new RegExp(`(${ (message.tokens || []).map(({ token }) => escapeRegExp(token)) })`, 'g');
msgParts = message.html.split(regexTokens);
} else {
msgParts = [message.html];
}
message.html = msgParts
.map((msgPart) => {
if (regexTokens && regexTokens.test(msgPart)) {
return msgPart;
}
return Autolinker.link(msgPart, {
...config,
stripTrailingSlash: false,
replaceFn: (match) => {
const token = `=!=${ Random.id() }=!=`;
const tag = match.buildTag();
if (~match.matchedText.indexOf(Meteor.absoluteUrl())) {
tag.setAttr('target', '');
}
message.tokens = message.tokens ?? [];
message.tokens.push({
token,
text: tag.toAnchorString(),
});
return token;
} });
})
.join('');
import { settings } from '../../settings';
import { callbacks } from '../../callbacks';
import { escapeRegExp } from '../../../client/lib/escapeRegExp';
let config;
Tracker.autorun(function() {
config = {
stripPrefix: settings.get('AutoLinker_StripPrefix'),
urls: {
schemeMatches: settings.get('AutoLinker_Urls_Scheme'),
wwwMatches: settings.get('AutoLinker_Urls_www'),
tldMatches: settings.get('AutoLinker_Urls_TLD'),
},
email: settings.get('AutoLinker_Email'),
phone: settings.get('AutoLinker_Phone'),
twitter: false,
stripTrailingSlash: false,
};
});
const renderMessage = (message) => {
if (!s.trim(message.html)) {
return message;
}
let msgParts;
let regexTokens;
if (message.tokens && message.tokens.length) {
regexTokens = new RegExp(`(${ (message.tokens || []).map(({ token }) => escapeRegExp(token)) })`, 'g');
msgParts = message.html.split(regexTokens);
} else {
msgParts = [message.html];
}
message.html = msgParts
.map((msgPart) => {
if (regexTokens && regexTokens.test(msgPart)) {
return msgPart;
}
return Autolinker.link(msgPart, {
...config,
replaceFn: (match) => {
const token = `=!=${ Random.id() }=!=`;
const tag = match.buildTag();
if (~match.matchedText.indexOf(Meteor.absoluteUrl())) {
tag.setAttr('target', '');
}
message.tokens.push({
token,
text: tag.toAnchorString(),
});
return token;
} });
})
.join('');
return message;
};
Tracker.autorun(function() {
if (settings.get('AutoLinker') !== true) {
return callbacks.remove('renderMessage', 'autolinker');
}
callbacks.add('renderMessage', renderMessage, callbacks.priority.MEDIUM, 'autolinker');
});
};

@ -1 +1 @@
import './client';
export { createAutolinkerMessageRenderer } from './client';

@ -1,4 +1,8 @@
import './lib/actionButton';
import './lib/tabBar';
export { AutoTranslate } from './lib/autotranslate';
export {
AutoTranslate,
createAutoTranslateMessageRenderer,
createAutoTranslateMessageStreamHandler,
} from './lib/autotranslate';

@ -8,8 +8,10 @@ import { MessageAction } from '../../../ui-utils';
import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { Messages } from '../../../models';
Meteor.startup(function() {
Tracker.autorun(function() {
Meteor.startup(() => {
AutoTranslate.init();
Tracker.autorun(() => {
if (settings.get('AutoTranslate_Enabled') && hasAtLeastOnePermission(['auto-translate'])) {
MessageAction.addButton({
id: 'translate',

@ -4,28 +4,31 @@ import _ from 'underscore';
import mem from 'mem';
import { Subscriptions, Messages } from '../../../models';
import { callbacks } from '../../../callbacks';
import { settings } from '../../../settings';
import { hasAtLeastOnePermission, hasPermission } from '../../../authorization';
import { CachedCollectionManager } from '../../../ui-cached-collection';
import { hasPermission } from '../../../authorization';
import { call } from '../../../ui-utils/client';
let userLanguage = 'en';
let username = '';
Meteor.startup(() => Tracker.autorun(() => {
const user = Meteor.user();
if (!user) {
return;
}
userLanguage = user.language || 'en';
username = user.username;
}));
Meteor.startup(() => {
Tracker.autorun(() => {
const user = Meteor.user();
if (!user) {
return;
}
userLanguage = user.language || 'en';
username = user.username;
});
});
export const AutoTranslate = {
findSubscriptionByRid: mem((rid) => Subscriptions.findOne({ rid })),
initialized: false,
providersMetadata: {},
messageIdsToWait: {},
supportedLanguages: [],
findSubscriptionByRid: mem((rid) => Subscriptions.findOne({ rid })),
getLanguage(rid) {
let subscription = {};
if (rid) {
@ -60,89 +63,78 @@ export const AutoTranslate = {
},
init() {
this.supportedLanguages = [];
this.providersMetadata = {};
if (this.initialized) {
return;
}
Tracker.autorun((c) => {
Tracker.autorun(async (c) => {
const uid = Meteor.userId();
if (!uid) {
if (!uid || !hasPermission('auto-translate')) {
return;
}
Meteor.call('autoTranslate.getProviderUiMetadata', (err, metadata) => {
this.providersMetadata = metadata;
});
if (!hasPermission('auto-translate')) {
return;
}
Meteor.call('autoTranslate.getSupportedLanguages', 'en', (err, languages) => {
this.supportedLanguages = languages || [];
});
c.stop();
});
Tracker.autorun(() => {
Subscriptions.find().observeChanges({
changed: (id, fields) => {
if (fields.hasOwnProperty('autoTranslate') || fields.hasOwnProperty('autoTranslateLanguage')) {
mem.clear(this.findSubscriptionByRid);
}
},
});
[this.providersMetadata, this.supportedLanguages] = await Promise.all([
call('autoTranslate.getProviderUiMetadata'),
call('autoTranslate.getSupportedLanguages', 'en'),
]);
});
Tracker.autorun(() => {
if (settings.get('AutoTranslate_Enabled') && hasAtLeastOnePermission(['auto-translate'])) {
callbacks.add('renderMessage', (message) => {
const subscription = this.findSubscriptionByRid(message.rid);
const autoTranslateLanguage = this.getLanguage(message.rid);
if (message.u && message.u._id !== Meteor.userId()) {
if (!message.translations) {
message.translations = {};
}
if (!!(subscription && subscription.autoTranslate) !== !!message.autoTranslateShowInverse) {
message.translations.original = message.html;
if (message.translations[autoTranslateLanguage]) {
message.html = message.translations[autoTranslateLanguage];
}
if (message.attachments && message.attachments.length > 0) {
message.attachments = this.translateAttachments(message.attachments, autoTranslateLanguage);
}
}
} else if (message.attachments && message.attachments.length > 0) {
message.attachments = this.translateAttachments(message.attachments, autoTranslateLanguage);
}
return message;
}, callbacks.priority.HIGH - 3, 'autotranslate');
callbacks.add('streamMessage', (message) => {
if (message.u && message.u._id !== Meteor.userId()) {
const subscription = this.findSubscriptionByRid(message.rid);
const language = this.getLanguage(message.rid);
if (subscription && subscription.autoTranslate === true && ((message.msg && (!message.translations || !message.translations[language])))) { // || (message.attachments && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; }))
Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } });
} else if (this.messageIdsToWait[message._id] !== undefined && subscription && subscription.autoTranslate !== true) {
Messages.update({ _id: message._id }, { $set: { autoTranslateShowInverse: true }, $unset: { autoTranslateFetching: true } });
delete this.messageIdsToWait[message._id];
} else if (message.autoTranslateFetching === true) {
Messages.update({ _id: message._id }, { $unset: { autoTranslateFetching: true } });
}
}
}, callbacks.priority.HIGH - 3, 'autotranslate-stream');
} else {
callbacks.remove('renderMessage', 'autotranslate');
callbacks.remove('streamMessage', 'autotranslate-stream');
}
Subscriptions.find().observeChanges({
changed: (id, fields) => {
if (fields.hasOwnProperty('autoTranslate') || fields.hasOwnProperty('autoTranslateLanguage')) {
mem.clear(this.findSubscriptionByRid);
}
},
});
this.initialized = true;
},
};
Meteor.startup(function() {
CachedCollectionManager.onLogin(() => {
AutoTranslate.init();
});
});
export const createAutoTranslateMessageRenderer = () => {
AutoTranslate.init();
return (message) => {
const subscription = AutoTranslate.findSubscriptionByRid(message.rid);
const autoTranslateLanguage = AutoTranslate.getLanguage(message.rid);
if (message.u && message.u._id !== Meteor.userId()) {
if (!message.translations) {
message.translations = {};
}
if (!!(subscription && subscription.autoTranslate) !== !!message.autoTranslateShowInverse) {
message.translations.original = message.html;
if (message.translations[autoTranslateLanguage]) {
message.html = message.translations[autoTranslateLanguage];
}
if (message.attachments && message.attachments.length > 0) {
message.attachments = AutoTranslate.translateAttachments(message.attachments, autoTranslateLanguage);
}
}
} else if (message.attachments && message.attachments.length > 0) {
message.attachments = AutoTranslate.translateAttachments(message.attachments, autoTranslateLanguage);
}
return message;
};
};
export const createAutoTranslateMessageStreamHandler = () => {
AutoTranslate.init();
return (message) => {
if (message.u && message.u._id !== Meteor.userId()) {
const subscription = AutoTranslate.findSubscriptionByRid(message.rid);
const language = AutoTranslate.getLanguage(message.rid);
if (subscription && subscription.autoTranslate === true && ((message.msg && (!message.translations || !message.translations[language])))) { // || (message.attachments && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; }))
Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } });
} else if (AutoTranslate.messageIdsToWait[message._id] !== undefined && subscription && subscription.autoTranslate !== true) {
Messages.update({ _id: message._id }, { $set: { autoTranslateShowInverse: true }, $unset: { autoTranslateFetching: true } });
delete AutoTranslate.messageIdsToWait[message._id];
} else if (message.autoTranslateFetching === true) {
Messages.update({ _id: message._id }, { $unset: { autoTranslateFetching: true } });
}
}
};
};

@ -1,23 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { settings } from '../../../settings';
import { hasAtLeastOnePermission } from '../../../authorization';
import { TabBar } from '../../../ui-utils';
Meteor.startup(function() {
Tracker.autorun(function() {
if (settings.get('AutoTranslate_Enabled') && hasAtLeastOnePermission(['auto-translate'])) {
return TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'autotranslate',
i18nTitle: 'Auto_Translate',
icon: 'language',
template: 'AutoTranslate',
full: true,
order: 20,
});
}
TabBar.removeButton('autotranslate');
});
});

@ -0,0 +1,19 @@
import { lazy, useMemo } from 'react';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { usePermission } from '../../../../client/contexts/AuthorizationContext';
import { useSetting } from '../../../../client/contexts/SettingsContext';
addAction('autotranslate', () => {
const hasPermission = usePermission('auto-translate');
const autoTranslateEnabled = useSetting('AutoTranslate_Enabled');
return useMemo(() => (hasPermission && autoTranslateEnabled ? {
groups: ['channel', 'group', 'direct'],
id: 'autotranslate',
title: 'Auto_Translate',
icon: 'language',
template: lazy(() => import('../../../../client/views/room/contextualBar/AutoTranslate')),
order: 20,
full: true,
} : null), [autoTranslateEnabled, hasPermission]);
});

@ -1,12 +1,12 @@
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
import s from 'underscore.string';
import { settings } from '../../settings';
import { callbacks } from '../../callbacks';
import { Subscriptions, Messages } from '../../models';
import { Markdown } from '../../markdown/server';
import { Logger } from '../../logger';
import { escapeHTML } from '../../../lib/escapeHTML';
const Providers = Symbol('Providers');
const Provider = Symbol('Provider');
@ -273,7 +273,7 @@ export class AutoTranslate {
if (message.msg) {
Meteor.defer(() => {
let targetMessage = Object.assign({}, message);
targetMessage.html = s.escapeHTML(String(targetMessage.msg));
targetMessage.html = escapeHTML(String(targetMessage.msg));
targetMessage = this.tokenize(targetMessage);
const translations = this._translateMessage(targetMessage, targetLanguages);

@ -1,9 +1,5 @@
import './startup/messageTypes';
import './startup/tabBar';
import './startup/trackSettingsChange';
import './views/channelSettings.html';
import './views/channelSettings';
import './views/Multiselect';
import './stylesheets/channel-settings.css';
export { ChannelSettings } from './lib/ChannelSettings';

@ -1,6 +1,6 @@
import { Meteor } from 'meteor/meteor';
import s from 'underscore.string';
import { escapeHTML } from '../../../../lib/escapeHTML';
import { MessageTypes } from '../../../ui-utils';
import { t } from '../../../utils';
@ -24,7 +24,7 @@ Meteor.startup(function() {
data(message) {
return {
user_by: message.u && message.u.username,
room_topic: s.escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
room_topic: escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
};
},
});
@ -48,7 +48,7 @@ Meteor.startup(function() {
data(message) {
return {
user_by: message.u && message.u.username,
room_announcement: s.escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
room_announcement: escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
};
},
});
@ -60,7 +60,7 @@ Meteor.startup(function() {
data(message) {
return {
user_by: message.u && message.u.username,
room_description: s.escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
room_description: escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
};
},
});

@ -1,16 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { TabBar } from '../../../ui-utils';
Meteor.startup(() => {
TabBar.addButton({
groups: ['channel', 'group'],
id: 'channel-settings',
anonymous: true,
i18nTitle: 'Room_Info',
icon: 'info-circled',
template: 'channelSettings',
order: 7,
full: true,
});
});

@ -0,0 +1,14 @@
import { FC, lazy, LazyExoticComponent } from 'react';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
addAction('channel-settings', {
groups: ['channel', 'group'],
id: 'channel-settings',
anonymous: true,
full: true,
title: 'Room_Info',
icon: 'info-circled',
template: lazy(() => import('../../../../client/views/room/contextualBar/Info')) as LazyExoticComponent<FC>,
order: 7,
});

@ -1,48 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { callbacks } from '../../../callbacks';
import { RoomManager } from '../../../ui-utils';
import { roomTypes } from '../../../utils';
import { ChatRoom, ChatSubscription } from '../../../models';
Meteor.startup(function() {
const roomSettingsChangedCallback = (msg) => {
Tracker.nonreactive(() => {
if (msg.t === 'room_changed_privacy') {
if (Session.get('openedRoom') === msg.rid) {
const type = FlowRouter.current().route.name === 'channel' ? 'c' : 'p';
RoomManager.close(type + FlowRouter.getParam('name'));
const subscription = ChatSubscription.findOne({ rid: msg.rid });
const route = subscription.t === 'c' ? 'channel' : 'group';
FlowRouter.go(route, { name: subscription.name }, FlowRouter.current().queryParams);
}
}
});
return msg;
};
callbacks.add('streamMessage', roomSettingsChangedCallback, callbacks.priority.HIGH, 'room-settings-changed');
const roomNameChangedCallback = (msg) => {
Tracker.nonreactive(() => {
if (msg.t === 'r') {
if (Session.get('openedRoom') === msg.rid) {
const room = ChatRoom.findOne(msg.rid);
if (room.name !== FlowRouter.getParam('name')) {
RoomManager.close(room.t + FlowRouter.getParam('name'));
roomTypes.openRouteLink(room.t, room, FlowRouter.current().queryParams);
}
}
}
});
return msg;
};
callbacks.add('streamMessage', roomNameChangedCallback, callbacks.priority.HIGH, 'room-name-changed');
});

@ -1,133 +0,0 @@
.rtl .flex-tab {
direction: rtl;
& .channel-settings {
& .editing {
padding-right: 8px;
padding-left: 80px;
}
& .buttons {
right: auto;
left: 1px;
border-radius: 4px 0 0 4px;
}
}
}
.flex-tab {
& .channel-settings {
& ul {
& li {
margin-bottom: 20px;
}
}
& label {
display: block;
margin-bottom: 5px;
font-size: 14px;
font-weight: bold;
}
& .current-setting {
display: inline-block;
width: calc(100% - 38px);
min-height: 20px;
margin-top: 3px;
cursor: pointer;
vertical-align: middle;
word-wrap: break-word;
font-size: 14px;
&[data-edit="false"] {
cursor: inherit;
user-select: initial;
}
}
& .editing {
padding-right: 80px;
font-size: 14px;
}
& .buttons {
position: absolute;
top: -1px;
right: 10px;
border-radius: 0 4px 4px 0;
& .button {
padding: 8px;
}
}
& .button {
display: inline-block;
visibility: hidden;
padding: 8px;
vertical-align: middle;
font-size: 12px;
}
& .submit {
margin-top: 30px;
text-align: center;
}
& .boolean {
font-size: 0;
& > label {
display: inline-block;
width: calc(100% - 45px);
vertical-align: middle;
}
& .setting-block {
display: inline-block;
width: 40px;
margin-left: -5px;
vertical-align: middle;
}
}
& .setting-block {
position: relative;
display: flex;
font-size: 0;
& .loading-animation {
top: 30px;
}
&:hover {
& .button {
visibility: visible;
}
}
}
& nav {
text-align: right;
}
}
}

@ -1,7 +0,0 @@
<template name="channelSettings">
{{#if editing}}
{{> channelSettingsEditing channelData }}
{{else}}
{{> channelSettingsInfo channelData }}
{{/if}}
</template>

@ -1,32 +0,0 @@
import { HTML } from 'meteor/htmljs';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { ChatRoom } from '../../../models';
import { createTemplateForComponent } from '../../../../client/reactAdapters';
createTemplateForComponent('channelSettingsEditing', () => import('../../../../client/channel/ChannelInfo/EditChannel'), {
renderContainerView: () => HTML.DIV({ class: 'contextual-bar' }), // eslint-disable-line new-cap
});
createTemplateForComponent('channelSettingsInfo', () => import('../../../../client/channel/ChannelInfo/RoomInfo'), {
renderContainerView: () => HTML.DIV({ class: 'contextual-bar' }), // eslint-disable-line new-cap
});
Template.channelSettings.helpers({
channelData() {
const { editing } = Template.instance();
return {
...Template.currentData(),
openEditing: () => editing.set(true),
};
},
editing() {
return Template.instance().editing.get();
},
});
Template.channelSettings.onCreated(function() {
this.room = ChatRoom.findOne(this.data && this.data.rid);
this.editing = new ReactiveVar(false);
});

@ -1,6 +1,6 @@
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { registerAdminRoute } from '../../../client/admin';
import { registerAdminRoute } from '../../../client/views/admin';
import { t } from '../../utils';
registerAdminRoute('/chatpal', {

@ -1,24 +1,13 @@
import s from 'underscore.string';
import { settings } from '../../settings';
import { callbacks } from '../../callbacks';
import './style.css';
//
// HexColorPreview is a named function that will process Colors
// @param {Object} message - The message object
//
export const createHexColorPreviewMessageRenderer = () =>
(message) => {
if (!message.html?.trim()) {
return message;
}
function HexColorPreview(message) {
let msg;
if (s.trim(message.html) && settings.get('HexColorPreview_Enabled')) {
msg = message.html;
msg = msg.replace(/(?:^|\s|\n)(#[A-Fa-f0-9]{3}([A-Fa-f0-9]{3})?)\b/g, function(match, completeColor) {
return match.replace(completeColor, `<div class="message-color"><div class="message-color-sample" style="background-color:${ completeColor }"></div>${ completeColor.toUpperCase() }</div>`);
});
message.html = msg;
}
return message;
}
const regex = /(?:^|\s|\n)(#[A-Fa-f0-9]{3}([A-Fa-f0-9]{3})?)\b/g;
callbacks.add('renderMessage', HexColorPreview, callbacks.priority.MEDIUM, 'hexcolor');
message.html = message.html.replace(regex, (match, completeColor) => match.replace(completeColor, `<div class="message-color"><div class="message-color-sample" style="background-color:${ completeColor }"></div>${ completeColor.toUpperCase() }</div>`));
return message;
};

@ -1 +1 @@
import './client';
export { createHexColorPreviewMessageRenderer } from './client';

@ -1,7 +1,5 @@
.message-color {
display: inline-block;
font-weight: 100;
}
.message-color-sample {

@ -1,6 +1,5 @@
// Templates
import './views/creationDialog/CreateDiscussion';
import './views/DiscussionTabbar';
// Other UI extensions
import './lib/messageTypes/discussionMessage';

@ -1,17 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { TabBar } from '../../ui-utils/client';
import { settings } from '../../settings';
Meteor.startup(function() {
return TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'discussions',
i18nTitle: 'Discussions',
icon: 'discussion',
template: 'discussionsTabbar',
full: true,
order: 1,
condition: () => settings.get('Discussion_enabled'),
});
});

@ -0,0 +1,18 @@
import { useMemo, lazy, LazyExoticComponent, FC } from 'react';
import { addAction } from '../../../client/views/room/lib/Toolbox';
import { useSetting } from '../../../client/contexts/SettingsContext';
addAction('discussions', () => {
const discussionEnabled = useSetting('Discussion_enabled');
return useMemo(() => (discussionEnabled ? {
groups: ['channel', 'group', 'direct'],
id: 'discussions',
title: 'Discussions',
icon: 'discussion',
template: lazy(() => import('../../../client/views/room/contextualBar/Discussions')) as LazyExoticComponent<FC>,
full: true,
order: 1,
} : null), [discussionEnabled]);
});

@ -1,11 +0,0 @@
import { Template } from 'meteor/templating';
import './DiscussionTabbar.html';
Template.discussionsTabbar.helpers({
close() {
const { data } = Template.instance();
const { tabBar } = data;
return () => tabBar.close();
},
});

@ -1,37 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { hasAllPermission } from '../../authorization';
import { call, TabBar } from '../../ui-utils';
import { ChatRoom } from '../../models';
import { settings } from '../../settings';
Meteor.startup(() => {
Tracker.autorun(() => {
if (settings.get('E2E_Enable')) {
TabBar.addButton({
groups: ['direct', 'group'],
id: 'e2e',
i18nTitle: 'E2E',
icon: 'key',
class: () => (ChatRoom.findOne(Session.get('openedRoom')) || {}).encrypted && 'enabled',
action: () => {
const room = ChatRoom.findOne(Session.get('openedRoom'));
call('saveRoomSettings', room._id, 'encrypted', !room.encrypted);
},
order: 13,
condition: () => {
const session = Session.get('openedRoom');
const room = ChatRoom.findOne(session);
if (room && room.t === 'd') {
return true;
}
return hasAllPermission('edit-room', session);
},
});
} else {
TabBar.removeButton('e2e');
}
});
});

@ -0,0 +1,28 @@
import { useMemo } from 'react';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { addAction } from '../../../client/views/room/lib/Toolbox';
import { useSetting } from '../../../client/contexts/SettingsContext';
import { usePermission } from '../../../client/contexts/AuthorizationContext';
import { useMethod } from '../../../client/contexts/ServerContext';
addAction('e2e', ({ room }) => {
const e2eEnabled = useSetting('E2E_Enable');
const hasPermission = usePermission('edit-room', room._id);
const toggleE2E = useMethod('saveRoomSettings');
const action = useMutableCallback(() => {
toggleE2E(room._id, 'encrypted', !room.encrypted);
});
const enabledOnRoom = !!room.encrypted;
return useMemo(() => (e2eEnabled && hasPermission ? {
groups: ['direct', 'group'],
id: 'e2e',
title: enabledOnRoom ? 'E2E_disable' : 'E2E_enable',
icon: 'key',
order: 13,
action,
} : null), [action, e2eEnabled, enabledOnRoom, hasPermission]);
});

@ -1,5 +1,5 @@
import { registerAdminSidebarItem } from '../../../../client/views/admin';
import { hasPermission } from '../../../authorization';
import { registerAdminSidebarItem } from '../../../../client/admin';
registerAdminSidebarItem({
href: 'emoji-custom',

@ -7,7 +7,7 @@ import { RoomManager } from '../../../ui-utils/client';
import { emoji, EmojiPicker } from '../../../emoji/client';
import { CachedCollectionManager } from '../../../ui-cached-collection/client';
import { APIClient } from '../../../utils/client';
import { escapeRegExp } from '../../../../client/lib/escapeRegExp';
import { escapeRegExp } from '../../../../lib/escapeRegExp';
export const getEmojiUrlFromName = function(name, extension) {
Session.get;

@ -1,10 +1,4 @@
import s from 'underscore.string';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { getUserPreference } from '../../utils';
import { isIE11 } from '../../ui-utils/client/lib/isIE11';
import { callbacks } from '../../callbacks';
import { emoji } from '../lib/rocketchat';
/*
@ -12,76 +6,73 @@ import { emoji } from '../lib/rocketchat';
* @param {Object} message - The message object
*/
const emojiParser = function(message) {
let html = s.trim(message.html);
if (html) {
// &#39; to apostrophe (') for emojis such as :')
html = html.replace(/&#39;/g, '\'');
const emojiParser = (message) => {
if (!message.html?.trim()) {
return message;
}
let html = message.html.trim();
// &#39; to apostrophe (') for emojis such as :')
html = html.replace(/&#39;/g, '\'');
// '<br>' to ' <br> ' for emojis such at line breaks
html = html.replace(/<br>/g, ' <br> ');
// '<br>' to ' <br> ' for emojis such at line breaks
html = html.replace(/<br>/g, ' <br> ');
html = Object.entries(emoji.packages).reduce((value, [, emojiPackage]) => emojiPackage.render(value), html);
html = Object.entries(emoji.packages).reduce((value, [, emojiPackage]) => emojiPackage.render(value), html);
const checkEmojiOnly = document.createElement('div');
const checkEmojiOnly = document.createElement('div');
checkEmojiOnly.innerHTML = html;
checkEmojiOnly.innerHTML = html;
const emojis = Array.from(checkEmojiOnly.querySelectorAll('.emoji:not(:empty), .emojione:not(:empty)'));
const emojis = Array.from(checkEmojiOnly.querySelectorAll('.emoji:not(:empty), .emojione:not(:empty)'));
let hasText = false;
let hasText = false;
if (!isIE11()) {
const filter = (node) => {
if (node.nodeType === Node.ELEMENT_NODE && (
node.classList.contains('emojione')
if (!isIE11()) {
const filter = (node) => {
if (node.nodeType === Node.ELEMENT_NODE && (
node.classList.contains('emojione')
|| node.classList.contains('emoji')
)) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
};
const walker = document.createTreeWalker(
checkEmojiOnly,
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
filter,
);
while (walker.nextNode()) {
if (walker.currentNode.nodeType === Node.TEXT_NODE && walker.currentNode.nodeValue.trim() !== '') {
hasText = true;
break;
}
)) {
return NodeFilter.FILTER_REJECT;
}
const emojiOnly = emojis.length && !hasText;
if (emojiOnly) {
for (let i = 0, len = emojis.length; i < len; i++) {
const { classList } = emojis[i];
classList.add('big');
}
html = checkEmojiOnly.innerHTML;
return NodeFilter.FILTER_ACCEPT;
};
const walker = document.createTreeWalker(
checkEmojiOnly,
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
filter,
);
while (walker.nextNode()) {
if (walker.currentNode.nodeType === Node.TEXT_NODE && walker.currentNode.nodeValue.trim() !== '') {
hasText = true;
break;
}
}
const emojiOnly = emojis.length && !hasText;
if (emojiOnly) {
for (let i = 0, len = emojis.length; i < len; i++) {
const { classList } = emojis[i];
classList.add('big');
}
html = checkEmojiOnly.innerHTML;
}
}
// apostrophe (') back to &#39;
html = html.replace(/\'/g, '&#39;');
// apostrophe (') back to &#39;
html = html.replace(/\'/g, '&#39;');
// line breaks ' <br> ' back to '<br>'
html = html.replace(/ <br> /g, '<br>');
}
// line breaks ' <br> ' back to '<br>'
html = html.replace(/ <br> /g, '<br>');
return { ...message, html };
};
Tracker.autorun(() => {
if (!getUserPreference(Meteor.userId(), 'useEmojis')) {
return callbacks.remove('renderMessage', 'emoji');
}
callbacks.add('renderMessage', emojiParser, callbacks.priority.LOW, 'emoji');
});
export { emojiParser };
export const createEmojiMessageRenderer = () => emojiParser;

@ -4,7 +4,7 @@ import { ReactiveDict } from 'meteor/reactive-dict';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import { escapeRegExp } from '../../../client/lib/escapeRegExp';
import { escapeRegExp } from '../../../lib/escapeRegExp';
import '../../theme/client/imports/components/emojiPicker.css';
import { t } from '../../utils/client';
import { EmojiPicker } from './lib/EmojiPicker';

@ -1,10 +1,4 @@
import './emojiParser';
import { EmojiPicker } from './lib/EmojiPicker';
import { renderEmoji } from './lib/emojiRenderer';
import { emoji } from '../lib/rocketchat';
export {
renderEmoji,
emoji,
EmojiPicker,
};
export { EmojiPicker } from './lib/EmojiPicker';
export { renderEmoji } from './lib/emojiRenderer';
export { emoji } from '../lib/rocketchat';
export { createEmojiMessageRenderer } from './emojiParser';

@ -1,90 +1,70 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { settings } from '../../settings';
import { callbacks } from '../../callbacks';
const getVisionAttributes = (attachment) => {
const attributes = {};
const labels = [];
if (attachment.labels && attachment.labels.length > 0) {
attachment.labels.forEach((label) => {
labels.push({ label });
});
}
if (attachment.safeSearch && attachment.safeSearch && attachment.safeSearch.adult === true) {
labels.push({ label: 'NSFW', bgColor: 'red', fontColor: 'white' });
}
if (attachment.safeSearch && attachment.safeSearch.violence === true) {
labels.push({ label: 'Violence', bgColor: 'red', fontColor: 'white' });
}
if (attachment.colors && attachment.colors.length > 0) {
attributes.color = `#${ attachment.colors[0] }`;
}
if (attachment.logos && attachment.logos.length > 0) {
labels.push({ label: `Logo: ${ attachment.logos[0] }` });
}
if (attachment.faces && attachment.faces.length > 0) {
let faceCount = 0;
attachment.faces.forEach((face) => {
const faceAttributes = [];
if (face.joy) {
faceAttributes.push('Joy');
}
if (face.sorrow) {
faceAttributes.push('Sorrow');
}
if (face.anger) {
faceAttributes.push('Anger');
}
if (face.surprise) {
faceAttributes.push('Surprise');
}
if (faceAttributes.length > 0) {
labels.push({ label: `Face ${ ++faceCount }: ${ faceAttributes.join(', ') }` });
}
});
}
if (labels.length > 0) {
attributes.labels = labels;
}
return attributes;
};
const GoogleVision = {
getVisionAttributes(attachment) {
const attributes = {};
const labels = [];
if (attachment.labels && attachment.labels.length > 0) {
attachment.labels.forEach((label) => {
labels.push({ label });
});
}
if (attachment.safeSearch && attachment.safeSearch && attachment.safeSearch.adult === true) {
labels.push({ label: 'NSFW', bgColor: 'red', fontColor: 'white' });
}
if (attachment.safeSearch && attachment.safeSearch.violence === true) {
labels.push({ label: 'Violence', bgColor: 'red', fontColor: 'white' });
export const createGoogleVisionMessageRenderer = () =>
(message) => {
if (!message.attachments?.length) {
return message;
}
if (attachment.colors && attachment.colors.length > 0) {
attributes.color = `#${ attachment.colors[0] }`;
}
if (attachment.logos && attachment.logos.length > 0) {
labels.push({ label: `Logo: ${ attachment.logos[0] }` });
}
if (attachment.faces && attachment.faces.length > 0) {
let faceCount = 0;
attachment.faces.forEach((face) => {
const faceAttributes = [];
if (face.joy) {
faceAttributes.push('Joy');
}
if (face.sorrow) {
faceAttributes.push('Sorrow');
}
if (face.anger) {
faceAttributes.push('Anger');
}
if (face.surprise) {
faceAttributes.push('Surprise');
}
if (faceAttributes.length > 0) {
labels.push({ label: `Face ${ ++faceCount }: ${ faceAttributes.join(', ') }` });
}
});
}
if (labels.length > 0) {
attributes.labels = labels;
}
return attributes;
},
init() {
Tracker.autorun(() => {
if (settings.get('GoogleVision_Enable')) {
callbacks.add('renderMessage', (message) => {
if (message.attachments && message.attachments.length > 0) {
for (const index in message.attachments) {
if (message.attachments.hasOwnProperty(index)) {
const attachment = message.attachments[index];
message.attachments[index] = Object.assign(message.attachments[index], this.getVisionAttributes(attachment));
}
}
}
return message;
}, callbacks.priority.HIGH - 3, 'googlevision');
message.attachments = message.attachments.map((attachment) =>
Object.assign(attachment, getVisionAttributes(attachment)));
callbacks.add('streamMessage', (message) => {
if (message.attachments && message.attachments.length > 0) {
for (const index in message.attachments) {
if (message.attachments.hasOwnProperty(index)) {
const attachment = message.attachments[index];
message.attachments[index] = Object.assign(message.attachments[index], this.getVisionAttributes(attachment));
}
}
}
}, callbacks.priority.HIGH - 3, 'googlevision-stream');
} else {
callbacks.remove('renderMessage', 'googlevision');
callbacks.remove('streamMessage', 'googlevision-stream');
}
});
},
};
return message;
};
export const createGoogleVisionMessageStreamHandler = () =>
(message) => {
if (!message.attachments?.length) {
return message;
}
message.attachments = message.attachments.map((attachment) =>
Object.assign(attachment, getVisionAttributes(attachment)));
Meteor.startup(function() {
GoogleVision.init();
});
return message;
};

@ -1 +1,4 @@
import './googlevision';
export {
createGoogleVisionMessageRenderer,
createGoogleVisionMessageStreamHandler,
} from './googlevision';

@ -1,39 +1,18 @@
/*
* Highlights is a named function that will process Highlights
* @param {Object} message - The message object
*/
import _ from 'underscore';
import s from 'underscore.string';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { highlightWords, getRegexHighlight, getRegexHighlightUrl } from './helper';
import { callbacks } from '../../callbacks';
import { getUserPreference } from '../../utils';
Tracker.autorun(() => {
const toHighlight = (getUserPreference(Meteor.userId(), 'highlights') || []).filter((highlight) => !s.isBlank(highlight)).map((highlight) => ({
export const createHighlightWordsMessageRenderer = ({ wordsToHighlight }) => {
const highlights = wordsToHighlight.map((highlight) => ({
highlight,
regex: getRegexHighlight(highlight),
urlRegex: getRegexHighlightUrl(highlight),
}));
if (!toHighlight.length) {
return callbacks.remove('renderMessage', 'highlight-words');
}
function HighlightWordsClient(message) {
let msg = message;
if (!_.isString(message)) {
if (!s.trim(message.html)) {
return message;
}
msg = message.html;
return (message) => {
if (!message.html?.trim()) {
return message;
}
message.html = highlightWords(msg, toHighlight);
message.html = highlightWords(message.html, highlights);
return message;
}
callbacks.add('renderMessage', HighlightWordsClient, callbacks.priority.MEDIUM + 1, 'highlight-words');
});
};
};

@ -1,4 +1,4 @@
import s from 'underscore.string';
import { escapeRegExp } from '../../../lib/escapeRegExp';
export const checkHighlightedWordsInUrls = (msg, urlRegex) => msg.match(urlRegex);
@ -14,9 +14,9 @@ export const removeHighlightedUrls = (msg, highlight, urlMatches) => {
const highlightTemplate = '$1<span class="highlight-text">$2</span>$3';
export const getRegexHighlight = (highlight) => new RegExp(`(^|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(${ s.escapeRegExp(highlight) })($|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(?![^<]*>|[^<>]*<\\/)`, 'gmi');
export const getRegexHighlight = (highlight) => new RegExp(`(^|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(${ escapeRegExp(highlight) })($|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(?![^<]*>|[^<>]*<\\/)`, 'gmi');
export const getRegexHighlightUrl = (highlight) => new RegExp(`https?:\/\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)(${ s.escapeRegExp(highlight) })\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)`, 'gmi');
export const getRegexHighlightUrl = (highlight) => new RegExp(`https?:\/\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)(${ escapeRegExp(highlight) })\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)`, 'gmi');
export const highlightWords = (msg, highlights) => highlights.reduce((msg, { highlight, regex, urlRegex }) => {
const urlMatches = checkHighlightedWordsInUrls(msg, urlRegex);

@ -1 +1 @@
import './client';
export { createHighlightWordsMessageRenderer } from './client';

@ -1,5 +1,5 @@
import { hasAtLeastOnePermission } from '../../authorization';
import { registerAdminSidebarItem } from '../../../client/admin';
import { registerAdminSidebarItem } from '../../../client/views/admin';
registerAdminSidebarItem({
href: 'admin-integrations',

@ -1,21 +1,12 @@
import s from 'underscore.string';
import { settings } from '../../settings';
import { callbacks } from '../../callbacks';
export const createIssueLinksMessageRenderer = ({ template }) => (message) => {
if (!message.html?.trim()) {
return message;
}
//
// IssueLink is a named function that will add issue links
// @param {Object} message - The message object
//
message.html = message.html.replace(/(?:^|\s|\n)(#[0-9]+)\b/g, (match, issueNumber) => {
const url = template.replace('%s', issueNumber.substring(1));
return match.replace(issueNumber, `<a href="${ url }" target="_blank">${ issueNumber }</a>`);
});
function IssueLink(message) {
if (s.trim(message.html) && settings.get('IssueLinks_Enabled')) {
message.html = message.html.replace(/(?:^|\s|\n)(#[0-9]+)\b/g, function(match, issueNumber) {
const url = settings.get('IssueLinks_Template').replace('%s', issueNumber.substring(1));
return match.replace(issueNumber, `<a href="${ url }" target="_blank">${ issueNumber }</a>`);
});
}
return message;
}
callbacks.add('renderMessage', IssueLink, callbacks.priority.MEDIUM, 'issuelink');
};

@ -1 +1 @@
import './client';
export { createIssueLinksMessageRenderer } from './client';

@ -1,5 +0,0 @@
{
"globals": {
"Npm" : false
}
}

@ -1,10 +1,11 @@
import { Random } from 'meteor/random';
import { Tracker } from 'meteor/tracker';
import _ from 'underscore';
import s from 'underscore.string';
import katex from 'katex';
import { callbacks } from '../../callbacks';
import { settings } from '../../settings';
import { escapeHTML } from '../../../lib/escapeHTML';
import { unescapeHTML } from '../../../lib/unescapeHTML';
import 'katex/dist/katex.min.css';
import './style.css';
class Boundary {
length() {
@ -17,29 +18,29 @@ class Boundary {
}
class Katex {
constructor(katex) {
constructor(katex, { dollarSyntax, parenthesisSyntax }) {
this.katex = katex;
this.delimitersMap = [
{
opener: '\\[',
closer: '\\]',
displayMode: true,
enabled: () => this.isParenthesisSyntaxEnabled(),
enabled: () => parenthesisSyntax,
}, {
opener: '\\(',
closer: '\\)',
displayMode: false,
enabled: () => this.isParenthesisSyntaxEnabled(),
enabled: () => parenthesisSyntax,
}, {
opener: '$$',
closer: '$$',
displayMode: true,
enabled: () => this.isDollarSyntaxEnabled(),
enabled: () => dollarSyntax,
}, {
opener: '$',
closer: '$',
displayMode: false,
enabled: () => this.isDollarSyntaxEnabled(),
enabled: () => dollarSyntax,
},
];
}
@ -58,7 +59,7 @@ class Katex {
}
// Take the first delimiter found
const minPos = Math.min.apply(Math, positions);
const minPos = Math.min(...positions);
const matchIndex = matches.findIndex(({ pos }) => pos === minPos);
@ -109,7 +110,7 @@ class Katex {
const before = str.substr(0, match.outer.start);
const after = str.substr(match.outer.end);
let latex = match.inner.extract(str);
latex = s.unescapeHTML(latex);
latex = unescapeHTML(latex);
return {
before,
latex,
@ -129,7 +130,7 @@ class Katex {
});
} catch ({ message }) {
return `<div class="katex-error katex-${ displayMode ? 'block' : 'inline' }-error">`
+ `${ s.escapeHTML(message) }</div>`;
+ `${ escapeHTML(message) }</div>`;
}
}
@ -153,15 +154,15 @@ class Katex {
}
renderMessage = (message) => {
if (_.isString(message)) {
if (typeof message === 'string') {
return this.render(message, this.renderLatex);
}
if (!s.trim(message.html)) {
if (!message.html?.trim()) {
return message;
}
if (message.tokens == null) {
if (!message.tokens) {
message.tokens = [];
}
@ -176,31 +177,9 @@ class Katex {
return message;
}
isEnabled = () => settings.get('Katex_Enabled')
isDollarSyntaxEnabled = () => settings.get('Katex_Dollar_Syntax')
isParenthesisSyntaxEnabled = () => settings.get('Katex_Parenthesis_Syntax')
}
Tracker.autorun(async () => {
if (!settings.get('Katex_Enabled')) {
return callbacks.remove('renderMessage', 'katex');
}
const [katex] = await Promise.all([import('katex'), import('./style.css'), import('../katex.min.css')]);
const instance = new Katex(katex);
callbacks.add('renderMessage', instance.renderMessage, callbacks.priority.HIGH + 1, 'katex');
});
export default {
isEnabled: () => settings.get('Katex_Enabled'),
isDollarSyntaxEnabled: () => settings.get('Katex_Dollar_Syntax'),
isParenthesisSyntaxEnabled: () => settings.get('Katex_Parenthesis_Syntax'),
export const createKatexMessageRendering = (options) => {
const instance = new Katex(katex, options);
return (message) => instance.renderMessage(message);
};

@ -1 +0,0 @@
../../node_modules/katex/dist/katex.min.css

@ -1,10 +0,0 @@
import { settings } from '../../settings';
export default {
isEnabled: () => settings.get('Katex_Enabled'),
isDollarSyntaxEnabled: () => settings.get('Katex_Dollar_Syntax'),
isParenthesisSyntaxEnabled: () => settings.get('Katex_Parenthesis_Syntax'),
};

@ -1,3 +1 @@
import './settings';
export { Katex } from '../lib/katex';

@ -1,89 +0,0 @@
import { Session } from 'meteor/session';
import { TabBar } from '../../ui-utils';
import { Rooms } from '../../models';
import { hasAllPermission } from '../../authorization';
import { roomTypes } from '../../utils/client';
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'rocket-search',
i18nTitle: 'Search_Messages',
icon: 'magnifier',
template: 'RocketSearch',
order: 4,
});
TabBar.addButton({
groups: ['direct'],
id: 'user-info',
i18nTitle: 'User_Info',
icon: 'user',
template: 'membersList',
order: 5,
condition() {
const rid = Session.get('openedRoom');
const room = Rooms.findOne({
_id: rid,
});
return room && !roomTypes.getConfig(room.t).isGroupChat(room);
},
});
TabBar.addButton({
groups: ['direct'],
id: 'user-info-group',
i18nTitle: 'Members',
icon: 'team',
template: 'membersList',
order: 5,
condition() {
const rid = Session.get('openedRoom');
const room = Rooms.findOne({
_id: rid,
});
return room && roomTypes.getConfig(room.t).isGroupChat(room);
},
});
TabBar.addButton({
groups: ['channel', 'group'],
id: 'members-list',
i18nTitle: 'Members',
icon: 'team',
template: 'membersList',
order: 5,
condition() {
const rid = Session.get('openedRoom');
const room = Rooms.findOne({
_id: rid,
});
if (!room || !room.broadcast) {
return true;
}
return hasAllPermission('view-broadcast-member-list', rid);
},
});
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'uploaded-files-list',
i18nTitle: 'Files',
icon: 'clip',
template: 'uploadedFilesList',
order: 6,
});
TabBar.addButton({
groups: ['channel', 'group', 'direct'],
id: 'keyboard-shortcut-list',
i18nTitle: 'Keyboard_Shortcuts_Title',
icon: 'keyboard',
template: 'KeyboardShortcuts',
full: true,
order: 99,
});

@ -1,7 +1,6 @@
import '../lib/startup/settingsOnLoadSiteUrl';
import '../lib/MessageTypes';
import './CustomTranslations';
import './defaultTabBars';
import './OAuthProxy';
import './UserDeleted';
import './lib/startup/commands';

@ -1,6 +1,8 @@
import { Meteor } from 'meteor/meteor';
import s from 'underscore.string';
import { escapeRegExp } from '../../../../lib/escapeRegExp';
export const checkEmailAvailability = function(email) {
return !Meteor.users.findOne({ 'emails.address': { $regex: new RegExp(`^${ s.trim(s.escapeRegExp(email)) }$`, 'i') } });
return !Meteor.users.findOne({ 'emails.address': { $regex: new RegExp(`^${ s.trim(escapeRegExp(email)) }$`, 'i') } });
};

@ -1,18 +1,19 @@
import { Meteor } from 'meteor/meteor';
import s from 'underscore.string';
import { escapeRegExp } from '../../../../lib/escapeRegExp';
import { settings } from '../../../settings';
let usernameBlackList = [];
const toRegExp = (username) => new RegExp(`^${ s.escapeRegExp(username).trim() }$`, 'i');
const toRegExp = (username) => new RegExp(`^${ escapeRegExp(username).trim() }$`, 'i');
settings.get('Accounts_BlockedUsernameList', (key, value) => {
usernameBlackList = ['all', 'here'].concat(value.split(',')).map(toRegExp);
});
const usernameIsBlocked = (username, usernameBlackList) => usernameBlackList.length
&& usernameBlackList.some((restrictedUsername) => restrictedUsername.test(s.trim(s.escapeRegExp(username))));
&& usernameBlackList.some((restrictedUsername) => restrictedUsername.test(s.trim(escapeRegExp(username))));
export const checkUsernameAvailability = function(username) {
if (usernameIsBlocked(username, usernameBlackList)) {

@ -4,6 +4,7 @@ import { Logger } from '../../../logger';
import { settings } from '../../../settings';
import { Users } from '../../../models/server';
import { hasPermission } from '../../../authorization';
import { escapeRegExp } from '../../../../lib/escapeRegExp';
const logger = new Logger('getFullUserData');
@ -129,6 +130,6 @@ export const getFullUserData = function({ userId, filter, limit: l }) {
return Users.findByUsername(userToRetrieveFullUserData.username, options);
}
const usernameReg = new RegExp(s.escapeRegExp(username), 'i');
const usernameReg = new RegExp(escapeRegExp(username), 'i');
return Users.findByUsernameNameOrEmailAddress(usernameReg, options);
};

@ -15,7 +15,7 @@ export function shouldNotifyAudio({
roomType,
isThread,
}) {
if (disableAllMessageNotifications && audioNotifications == null && !hasReplyToThread) {
if (disableAllMessageNotifications && audioNotifications == null && !isHighlighted && !hasMentionToUser && !hasReplyToThread) {
return false;
}

@ -8,6 +8,7 @@ import { roomTypes } from '../../../../utils';
import { metrics } from '../../../../metrics';
import { callbacks } from '../../../../callbacks';
import { getURL } from '../../../../utils/server';
import { escapeHTML } from '../../../../../lib/escapeHTML';
let advice = '';
let goToMessage = '';
@ -23,8 +24,8 @@ Meteor.startup(() => {
function getEmailContent({ message, user, room }) {
const lng = (user && user.language) || settings.get('Language') || 'en';
const roomName = s.escapeHTML(`#${ roomTypes.getRoomName(room.t, room) }`);
const userName = s.escapeHTML(settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username);
const roomName = escapeHTML(`#${ roomTypes.getRoomName(room.t, room) }`);
const userName = escapeHTML(settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username);
const roomType = roomTypes.getConfig(room.t);
@ -39,7 +40,7 @@ function getEmailContent({ message, user, room }) {
return header;
}
let messageContent = s.escapeHTML(message.msg);
let messageContent = escapeHTML(message.msg);
if (message.t === 'e2e') {
messageContent = TAPi18n.__('Encrypted_message', { lng });
@ -66,10 +67,10 @@ function getEmailContent({ message, user, room }) {
return fileHeader;
}
let content = `${ s.escapeHTML(message.file.name) }`;
let content = `${ escapeHTML(message.file.name) }`;
if (message.attachments && message.attachments.length === 1 && message.attachments[0].description !== '') {
content += `<br/><br/>${ s.escapeHTML(message.attachments[0].description) }`;
content += `<br/><br/>${ escapeHTML(message.attachments[0].description) }`;
}
return `${ fileHeader }:<br/><br/>${ content }`;
@ -85,10 +86,10 @@ function getEmailContent({ message, user, room }) {
let content = '';
if (attachment.title) {
content += `${ s.escapeHTML(attachment.title) }<br/>`;
content += `${ escapeHTML(attachment.title) }<br/>`;
}
if (attachment.text) {
content += `${ s.escapeHTML(attachment.text) }<br/>`;
content += `${ escapeHTML(attachment.text) }<br/>`;
}
return `${ header }:<br/><br/>${ content }`;

@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import s from 'underscore.string';
import { escapeRegExp } from '../../../../../lib/escapeRegExp';
import { callbacks } from '../../../../callbacks';
import { settings } from '../../../../settings';
@ -39,7 +39,7 @@ export function replaceMentionedUsernamesWithFullNames(message, mentions) {
}
mentions.forEach((mention) => {
if (mention.name) {
message = message.replace(new RegExp(s.escapeRegExp(`@${ mention.username }`), 'g'), mention.name);
message = message.replace(new RegExp(escapeRegExp(`@${ mention.username }`), 'g'), mention.name);
}
});
return message;
@ -57,7 +57,7 @@ export function messageContainsHighlight(message, highlights) {
if (! highlights || highlights.length === 0) { return false; }
return highlights.some(function(highlight) {
const regexp = new RegExp(s.escapeRegExp(highlight), 'i');
const regexp = new RegExp(escapeRegExp(highlight), 'i');
return regexp.test(message.msg);
});
}

@ -50,7 +50,7 @@ export async function getPushData({ room, message, userId, senderUsername, sende
if (shouldOmitMessage && settings.get('Push_request_content_from_server')) {
messageText = TAPi18n.__('You_have_a_new_message', { lng });
} else if (!settings.get('Push_show_message')) {
messageText = ' ';
messageText = TAPi18n.__('You_have_a_new_message', { lng });
} else {
messageText = notificationMessage;
}

@ -11,6 +11,7 @@ import { passwordPolicy } from '../lib/passwordPolicy';
import { validateEmailDomain } from '../lib';
import { validateUserRoles } from '../../../../ee/app/authorization/server/validateUserRoles';
import { saveUserIdentity } from './saveUserIdentity';
import { escapeHTML } from '../../../../lib/escapeHTML';
import { checkEmailAvailability, checkUsernameAvailability, setUserAvatar, setEmail, setStatusText } from '.';
@ -33,13 +34,13 @@ function _sendUserEmail(subject, html, userData) {
subject,
html,
data: {
email: s.escapeHTML(userData.email),
password: s.escapeHTML(userData.password),
email: escapeHTML(userData.email),
password: escapeHTML(userData.password),
},
};
if (typeof userData.name !== 'undefined') {
email.data.name = s.escapeHTML(userData.name);
email.data.name = escapeHTML(userData.name);
}
try {

@ -38,7 +38,7 @@ export function saveUserIdentity(userId, { _id, name: rawName, username: rawUser
}
// if coming from old username, update all references
if (previousUsername && usernameChanged) {
if ((previousUsername && usernameChanged) || (previousName && previousUsername)) {
if (typeof rawUsername !== 'undefined') {
Messages.updateAllUsernamesByUserId(user._id, username);
Messages.updateUsernameOfEditByUserId(user._id, username);

@ -6,6 +6,7 @@ import { hasPermission } from '../../../authorization';
import { RateLimiter, validateEmailDomain } from '../lib';
import * as Mailer from '../../../mailer';
import { settings } from '../../../settings';
import { escapeHTML } from '../../../../lib/escapeHTML';
import { checkEmailAvailability } from '.';
@ -24,7 +25,7 @@ const _sendEmailChangeNotification = function(to, newEmail) {
subject,
html,
data: {
email: s.escapeHTML(newEmail),
email: escapeHTML(newEmail),
},
};

@ -3,37 +3,52 @@ import { Rooms, Subscriptions, Users } from '../../../models/server';
const getFname = (members) => members.map(({ name, username }) => name || username).join(', ');
const getName = (members) => members.map(({ username }) => username).join(',');
export const updateGroupDMsName = (user) => {
if (!user.username) {
function getUsersWhoAreInTheSameGroupDMsAs(user) {
// add all users to single array so we can fetch details from them all at once
const rooms = Rooms.findGroupDMsByUids(user._id, { fields: { uids: 1 } });
if (rooms.count() === 0) {
return;
}
const userIds = new Set();
const users = new Map();
const rooms = Rooms.findGroupDMsByUids(user._id, { fields: { uids: 1 } });
rooms.forEach((room) => room.uids.forEach((uid) => uid !== user._id && userIds.add(uid)));
if (rooms.count() === 0) {
Users.findByIds([...userIds], { fields: { username: 1, name: 1 } })
.forEach((user) => users.set(user._id, user));
return users;
}
function sortUsersAlphabetically(u1, u2) {
return (u1.name || u1.username).localeCompare(u2.name || u2.username);
}
export const updateGroupDMsName = (userThatChangedName) => {
if (!userThatChangedName.username) {
return;
}
// add all users to single array so we can fetch details from them all at once
rooms.forEach((room) => room.uids.forEach((uid) => uid !== user._id && userIds.add(uid)));
const users = getUsersWhoAreInTheSameGroupDMsAs(userThatChangedName);
if (!users) {
return;
}
Users.findByIds([...userIds], { fields: { username: 1, name: 1 } })
.forEach((user) => users.set(user._id, user));
users.set(userThatChangedName._id, userThatChangedName);
const rooms = Rooms.findGroupDMsByUids(userThatChangedName._id, { fields: { uids: 1 } });
const getMembers = (uids) => uids.map((uid) => users.get(uid)).filter(Boolean);
// loop rooms to update the subcriptions from them all
rooms.forEach((room) => {
const members = getMembers(room.uids);
const sortedMembers = members.sort((u1, u2) => (u1.name || u1.username).localeCompare(u2.name || u2.username));
const sortedMembers = members.sort(sortUsersAlphabetically);
const subs = Subscriptions.findByRoomId(room._id, { fields: { _id: 1, 'u._id': 1 } });
subs.forEach((sub) => {
const otherMembers = sortedMembers.filter(({ _id }) => _id !== sub.u._id);
Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers));
});
});

@ -1,9 +1,9 @@
import s from 'underscore.string';
import moment from 'moment';
import { Rooms, Subscriptions } from '../../../models/server';
import { settings } from '../../../settings/server';
import { callbacks } from '../../../callbacks/server';
import { escapeRegExp } from '../../../../lib/escapeRegExp';
/**
* Chechs if a messages contains a user highlight
@ -18,7 +18,7 @@ export function messageContainsHighlight(message, highlights) {
if (! highlights || highlights.length === 0) { return false; }
return highlights.some(function(highlight) {
const regexp = new RegExp(s.escapeRegExp(highlight), 'i');
const regexp = new RegExp(escapeRegExp(highlight), 'i');
return regexp.test(message.msg);
});
}

@ -1,23 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { TabBar } from '../../../ui-utils/client';
import { settings } from '../../../settings/client';
Meteor.startup(function() {
Tracker.autorun(function() {
if (!settings.get('Omnichannel_External_Frame_Enabled')) {
return TabBar.removeButton('omnichannelExternalFrame');
}
TabBar.addButton({
groups: ['live'],
id: 'omnichannelExternalFrame',
i18nTitle: 'Omnichannel_External_Frame',
icon: 'cube',
template: 'ExternalFrameContainer',
order: -1,
});
});
});

@ -0,0 +1,18 @@
import { useMemo } from 'react';
import { useSetting } from '../../../../client/contexts/SettingsContext';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
addAction('omnichannel-external-frame', () => {
const enabled = useSetting('Omnichannel_External_Frame_Enabled');
return useMemo(() => (enabled
? {
groups: ['live'],
id: 'omnichannel-external-frame',
title: 'Omnichannel_External_Frame',
icon: 'cube',
template: 'ExternalFrameContainer',
order: -1,
} : null), [enabled]);
});

@ -1,29 +0,0 @@
import { callbacks } from '../../../callbacks';
callbacks.add('onCreateRoomTabBar', (info) => {
const { tabBar, room } = info;
if (!tabBar) {
return info;
}
if (!room || !room.t || room.t !== 'l') {
return info;
}
const button = tabBar.getButtons().find((button) => button.id === 'visitor-info');
if (!button) {
return info;
}
const { template, i18nTitle: label, icon } = button;
tabBar.setTemplate(template);
tabBar.setData({
label,
icon,
});
tabBar.open();
return info;
});

@ -2,7 +2,7 @@ import '../lib/messageTypes';
import './roomType';
import './route';
import './ui';
import './hooks/onCreateRoomTabBar';
import './tabBar';
import './startup/notifyUnreadRooms';
import './views/app/dialog/closeRoom';
import './stylesheets/livechat.css';

@ -1,7 +1,7 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { AccountBox } from '../../ui-utils';
import '../../../client/omnichannel/routes';
import '../../../client/views/omnichannel/routes';
export const livechatManagerRoutes = FlowRouter.group({
prefix: '/omnichannel',

@ -0,0 +1,19 @@
import { addAction } from '../../../client/views/room/lib/Toolbox';
addAction('visitor-info', {
groups: ['live'],
id: 'visitor-info',
title: 'Visitor_Info',
icon: 'info-circled',
template: 'visitorInfo',
order: 0,
});
addAction('contact-chat-history', {
groups: ['live'],
id: 'contact-chat-history',
title: 'Contact_Chat_History',
icon: 'clock',
template: 'contactChatHistory',
order: 11,
});

@ -2,7 +2,7 @@ import { Tracker } from 'meteor/tracker';
import { settings } from '../../settings';
import { hasAllPermission } from '../../authorization';
import { AccountBox, TabBar, MessageTypes } from '../../ui-utils';
import { AccountBox, MessageTypes } from '../../ui-utils';
Tracker.autorun((c) => {
// import omnichannel tabbar templates right away if omnichannel enabled
@ -21,30 +21,6 @@ AccountBox.addItem({
condition: () => settings.get('Livechat_enabled') && hasAllPermission('view-livechat-manager'),
});
TabBar.addButton({
groups: ['live'],
id: 'visitor-info',
i18nTitle: 'Visitor_Info',
icon: 'info-circled',
template: 'visitorInfo',
order: 0,
});
TabBar.addButton({
groups: ['live'],
id: 'contact-chat-history',
i18nTitle: 'Contact_Chat_History',
icon: 'clock',
template: 'contactChatHistory',
order: 11,
});
TabBar.addGroup('message-search', ['live']);
TabBar.addGroup('starred-messages', ['live']);
TabBar.addGroup('uploaded-files-list', ['live']);
TabBar.addGroup('push-notifications', ['live']);
TabBar.addGroup('video', ['live']);
MessageTypes.registerType({
id: 'livechat-close',
system: true,

@ -60,11 +60,6 @@ Template.contactChatHistory.onCreated(async function() {
this.returnChatHistoryList = () => {
this.showChatHistoryMessages.set(false);
this.chatHistoryMessagesContext.set();
this.tabBar.setData({
label: 'Contact_Chat_History',
icon: 'clock',
});
};
this.autorun(async () => {

@ -41,7 +41,7 @@
{{#if servedBy}}<li><strong>{{_ "Agent"}}</strong>: {{servedBy.username}}</li>{{/if}}
{{#if facebook}}<li><i class="icon-facebook"></i>{{_ "Facebook_Page"}}: {{facebook.page.name}}</li>{{/if}}
{{#if sms}}<li><i class="i con-mobile"></i>{{_ "SMS_Enabled"}}</li>{{/if}}
{{#if topic}}<li><strong>{{_ "Topic"}}</strong>: {{{RocketChatMarkdown topic}}}</li>{{/if}}
{{#if topic}}<li><strong>{{_ "Topic"}}</strong>: {{{markdown topic}}}</li>{{/if}}
{{#if tags}}<li><strong>{{_ "Tags"}}</strong>: {{joinTags}}</li>{{/if}}
{{#if closedAt}}<li><strong>{{_ "Closed_At"}}</strong>: {{roomClosedDateTime}} <strong>{{_ "by"}}:</strong> {{roomClosedBy}}</li>{{/if}}
{{> Template.dynamic template=customInfoTemplate data=roomDataContext}}

@ -18,6 +18,7 @@ import { APIClient } from '../../../../../utils/client';
import { RoomManager } from '../../../../../ui-utils/client';
import { DateFormat } from '../../../../../lib/client';
import { getCustomFormTemplate } from '../customTemplates/register';
import { Markdown } from '../../../../../markdown/client';
const isSubscribedToRoom = () => {
const data = Template.currentData();
@ -244,6 +245,10 @@ Template.visitorInfo.helpers({
const { requestedAt } = this;
return DateFormat.formatDateAndTime(requestedAt);
},
markdown(text) {
return Markdown.parse(text);
},
});
Template.visitorInfo.events({

@ -19,9 +19,6 @@ const validateDateParams = (property, date) => {
API.v1.addRoute('livechat/rooms', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'view-livechat-manager')) {
return API.v1.unauthorized();
}
const { offset, count } = this.getPaginationItems();
const { sort, fields } = this.parseJsonQuery();
const { agents, departmentId, open, tags, roomName } = this.requestParams();
@ -32,6 +29,12 @@ API.v1.addRoute('livechat/rooms', { authRequired: true }, {
check(open, Match.Maybe(String));
check(tags, Match.Maybe([String]));
const hasAdminAccess = hasPermission(this.userId, 'view-livechat-rooms');
const hasAgentAccess = hasPermission(this.userId, 'view-l-room') && agents?.includes(this.userId) && agents?.length === 1;
if (!hasAdminAccess && !hasAgentAccess) {
return API.v1.unauthorized();
}
createdAt = validateDateParams('createdAt', createdAt);
closedAt = validateDateParams('closedAt', closedAt);

@ -17,7 +17,7 @@ class LivechatRoomRoute extends RoomTypeRouteConfig {
constructor() {
super({
name: 'live',
path: '/live/:id',
path: '/live/:id/:tab?/:context?',
});
}
@ -122,19 +122,19 @@ export default class LivechatRoomType extends RoomTypeConfig {
if (!room || !room.v || room.v.username !== username) {
return false;
}
const button = instance.tabBar.getButtons().find((button) => button.id === 'visitor-info');
if (!button) {
return false;
}
const { template, i18nTitle: label, icon } = button;
instance.tabBar.setTemplate(template);
instance.tabBar.setData({
label,
icon,
});
instance.tabBar.open();
// const button = instance.tabBar.getButtons({ room }).find((button) => button.id === 'visitor-info');
// if (!button) {
// return false;
// }
// const { template, i18nTitle: label, icon } = button;
// instance.tabBar.setTemplate(template);
// instance.tabBar.setData({
// label,
// icon,
// });
instance.tabBar.openUserInfo();
return true;
}
}

@ -1,5 +1,4 @@
import s from 'underscore.string';
import { escapeRegExp } from '../../../../../lib/escapeRegExp';
import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { LivechatCustomField } from '../../../../models/server/raw';
@ -8,7 +7,7 @@ export async function findLivechatCustomFields({ userId, text, pagination: { off
throw new Error('error-not-authorized');
}
const query = { ...text && { $or: [{ label: new RegExp(s.escapeRegExp(text), 'i') }, { _id: new RegExp(s.escapeRegExp(text), 'i') }] } };
const query = { ...text && { $or: [{ label: new RegExp(escapeRegExp(text), 'i') }, { _id: new RegExp(escapeRegExp(text), 'i') }] } };
const cursor = await LivechatCustomField.find(query, {
sort: sort || { label: 1 },

@ -1,5 +1,4 @@
import s from 'underscore.string';
import { escapeRegExp } from '../../../../../lib/escapeRegExp';
import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { LivechatDepartment, LivechatDepartmentAgents } from '../../../../models/server/raw';
@ -9,7 +8,7 @@ export async function findDepartments({ userId, text, pagination: { offset, coun
}
const query = {
...text && { name: new RegExp(s.escapeRegExp(text), 'i') },
...text && { name: new RegExp(escapeRegExp(text), 'i') },
};
const cursor = LivechatDepartment.find(query, {

@ -1,12 +1,11 @@
import s from 'underscore.string';
import { escapeRegExp } from '../../../../../lib/escapeRegExp';
import { hasAllPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { Users } from '../../../../models/server/raw';
async function findUsers({ role, text, pagination: { offset, count, sort } }) {
const query = {};
if (text) {
const filterReg = new RegExp(s.escapeRegExp(text), 'i');
const filterReg = new RegExp(escapeRegExp(text), 'i');
Object.assign(query, { $or: [{ username: filterReg }, { name: filterReg }, { 'emails.address': filterReg }] });
}

@ -144,6 +144,7 @@ export async function findVisitorsByEmailOrPhoneOrNameOrUsername({ userId, term,
phone: 1,
livechatData: 1,
visitorEmails: 1,
lastChat: 1,
},
});

@ -9,3 +9,4 @@ import './v1/customField.js';
import './v1/room.js';
import './v1/videoCall.js';
import './v1/transfer.js';
import './v1/contact.js';

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save