diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index dd85ed699b76..d6d6abd54ed1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,42 +1,253 @@ # Contributing to Rocket.Chat -:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: +**First off, thanks for taking the time to contribute! :tada::+1:** -The following is a set of guidelines for contributing to Rocket.Chat and its packages, which are hosted in the [Rocket.Chat Organization](https://github.com/RocketChat) on GitHub. +> There are many ways to contribute to Rocket.Chat even if you're not technical or a developer: +> +> * Email us at marketing@rocket.chat to tell us how much you love the project +> * Write about us in your blogs +> * Fix some small typos in our [documentation](https://docs.rocket.chat/contributing) +> * Become our [GitHub sponsor](https://github.com/sponsors/RocketChat) +> * Tell others about us and help us spread the word +> +> Every bit of contribution is appreciated 🙂 thank you! + +The following is a set of guidelines for contributing to Rocket.Chat, which are hosted in the [Rocket.Chat Organization](https://github.com/RocketChat) on GitHub. __Note:__ If there's a feature you'd like, there's a bug you'd like to fix, or you'd just like to get involved please raise an issue and start a conversation. We'll help as much as we can so you can get contributing - although we may not always be able to respond right away :) -## ECMAScript 2015 vs CoffeeScript +## Setup + +Your development workstation needs to have at least 8GB or RAM to be able to build the Rocket.Chat's source code. + +Rocket.Chat runs on top of [Meteor](https://www.meteor.com/). To run it on development mode you need to [install Meteor](https://www.meteor.com/install) and clone/download the Rocket.Chat's code, then just open the code folder and run: +```shell +meteor npm install && meteor +``` +It should build and run the application and database for you, now you can access the UI on (http://localhost:3000) + +It's not necessary to install Nodejs or NPM, every time you need to use them you can run `meteor node` or `meteor npm`. + +It's important to always run the NPM commands using `meteor npm` to ensure that you are installing the modules using the right Nodejs version. + +## Coding + +We provide a [.editorconfig](../.editorconfig) file that will help you to keep some standards in place. + +### ECMAScript vs TypeScript + +We are currently adopting TypeScript as the default language on our projects, the current codebase will be migrated incrementally from JavaScript to TypeScript. + +While we still have a lot of JavaScript files you should not create new ones. As much as possible new code contributions should be in **TypeScript**. -While we still have a lot of CoffeeScript files you should not create new ones. New code contributions should be in **ECMAScript 2015**. +### Blaze vs React -## Coding standards +We are currently adopting React over Blaze as our UI engine, the current codebase is under migration and will continue. You will still find Blaze templates in our code. Code changes or contributions may need to be made in Blaze while we continue to evolve our components library. -Most of the coding standards are covered by `.editorconfig` and `.eslintrc.js`. +[Fuselage](https://github.com/RocketChat/Rocket.Chat.Fuselage) is our component library based on React, check it out when contributing to the Rocket.Chat UI and feel free to contribute new components or fixes. + +### Standards + +Most of the coding standards are covered by ESLint configured at [.eslintrc](../.eslintrc), and most of them came from our own [ESLint Config Package](https://github.com/RocketChat/eslint-config-rocketchat). Things not covered by `eslint`: -* `exports`/`module.exports` should be at the end of the file -* Longer, descriptive variable names are preferred, e.g. `error` vs `err` +* Prefer longer/descriptive variable names, e.g. `error` vs `err`, unless dealing with common record properties already shortened, e.g. `rid` and `uid` +* Use return early pattern. [See more](https://blog.timoxley.com/post/47041269194/avoid-else-return-early) +* Prefer `Promise` over `callbacks` +* Prefer `await` over `then/catch` +* Don't create queries outside models, the query description should be inside the model class. +* Don't hardcode fields inside models. Same method can be used for different purposes, using different fields. +* Prefer create REST endpoints over Meteor methods +* Prefer call REST endpoints over Meteor methods when both are available +* v1 REST endpoints should follow the following pattern: `/api/v1/dashed-namespace.camelCaseAction` +* Prefer TypeScript over JavaScript. Check [ECMAScript vs TypeScript](#ecmascript-vs-typescript) -We acknowledge all the code does not meet these standards but we are working to change this over time. +#### Blaze +* Import the HTML file from it's sibling JS/TS file ### Syntax check Before submitting a PR you should get no errors on `eslint`. -To check your files, first install `eslint`: +To check your files run: + +```shell +meteor npm run lint +``` + +## Tests + +There are 2 types of tests we run on Rocket.Chat, **Unit** tests and **End to End** tests. The major difference is that End to End tests require a Rocket.Chat instance running to execute the API and UI checks. + +### End to End Tests + +First you need to run a Rocket.Chat server on **Test Mode** and on a **Empty Database**: +```shell +# Running with a local mongodb database +MONGO_URL=mongodb://localhost/empty MONGO_OPLOG_URL=mongodb://localhost/local TEST_MODE=true meteor +``` +```shell +# Running with a local mongodb database but cleaning it before +mongo --eval "db.dropDatabase()" empty && MONGO_URL=mongodb://localhost/empty MONGO_OPLOG_URL=mongodb://localhost/local TEST_MODE=true meteor +``` + +Now you can run the tests: +```shell +meteor npm test +``` + +### Unit Tests + +Unit tests are simpler to setup and run. They do not require a working Rocket.Chat instance. +```shell +meteor npm run testunit +``` + +It's possible to run on watch mode as well: +```shell +meteor npm run testunit-watch +``` + + + +## Before Push your code + +It's important to run the lint and tests before push your code or submit a Pull Request, otherwise your contribution may fail quickly on the CI. Reviewers are forced to demand fixes and the review of your contribution will be further delayed. + +Rocket.Chat uses [husky](https://www.npmjs.com/package/husky) to run the **lint** and **unit tests** before proceed to the code push process, so you may notice a delay when pushing your code to your repository. + +## Choosing a good PR title + +It is very important to note that we use PR titles when creating our change log. Keep this in mind when you title your PR. Make sure the title makes sense to a person reading a releases' change log! + +Keep your PR's title as short and concise as possible, use PR's description section, which you can find in the PR's template, to provide more details into the changelog. + +Good titles require thinking from a user's point of view. Don't get technical and talk code or architecture. What is the actual user-facing feature or the bug fixed? For example: + +``` +[NEW] Allow search permissions and settings by name instead of only ID +``` + +Even it's being something new in the code the users already expect the filter to filter by what they see (translations), a better one would be: + +``` +[FIX] Permissions' search doesn't filter base on presented translation, only on internal ids +``` + +## Choosing the right PR tag + +You can use several tags do describe your PR, i.e.: `[FIX]`, `[NEW]`, etc. You can use the descriptions below to better understand the meaning of each one, and decide which one you should use: + +### `[NEW]` + +#### When +- When adding a new feature that is important to the end user + +#### How + +Do not start repeating the section (`Add ...` or `New ...`) +Always describe what's being fixed, improved or added and not *how* it was fixed, improved or added. + +Exemple of **bad** PR titles: + +``` +[NEW] Add ability to set tags in the Omnichannel room closing dialog +[NEW] Adds ability for Rocket.Chat Apps to create discussions +[NEW] Add MMS support to Voxtelesys +[NEW] Add Color variable to left sidebar +``` + +Exemple of **good** PR titles: ``` -npm install -g eslint +[NEW] Ability to set tags in the Omnichannel room closing dialog +[NEW] Ability for Rocket.Chat Apps to create discussions +[NEW] MMS support to Voxtelesys +[NEW] Color variable to left sidebar ``` -Then run: +### `[FIX]` + +#### When +- When fixing something not working or behaving wrong from the end user perspective + +#### How + +Always describe what's being fixed and not *how* it was fixed. + +Exemple of a **bad** PR title: ``` -eslint . +[FIX] Add Content-Type for public files with JWT ``` -# Contributor License Agreement +Exemple of a **good** PR title: + +``` +[FIX] Missing Content-Type header for public files with JWT +``` + +### `[IMPROVE]` + +#### When +- When a change enhances a not buggy behavior. When in doubt if it's a Improve or Fix prefer to use as fix. + +#### How +Always describe what's being improved and not *how* it was improved. + +Exemple of **good** PR title: + +``` +[IMPROVE] Displays Nothing found on admin sidebar when search returns nothing +``` + +### `[BREAK]` + +#### When +- When the changes affect a working feature + +##### Back-End +- When the API contract (data structure and endpoints) are limited, expanded as required or removed +- When the business logic (permissions and roles) are limited, expanded (without migration) or removed + +##### Front-End +- When the change limits (format, size, etc) or removes the ability of read or change the data (when the limitation was not caused by the back-end) + +### Second tag e.g. `[NEW][ENTERPRISE]` + +Use a second tag to group entries on the change log, we currently use it only for the Enterprise items but we are going to expand it's usage soon, please do not use it until we create a patter for it. + +### Minor Changes + +For those PRs that aren't important for the end user, we are working on a better pattern, but for now please use the same tags, use them without the brackets and in camel case: + +``` +Fix: Missing Content-Type header for public files with JWT +``` + +All those PRs will be grouped under the `Minor changes` section which is collapsed, so users can expand it to check for those minor things but they are not visible directly on changelog. + +## Security Best Practices + +- Never expose unnecessary data to the APIs' responses +- Always check for permissions or create new ones when you must expose sensitive data +- Never provide new APIs without rate limiters +- Always escape the user's input when rendering data +- Always limit the user's input size on server side +- Always execute the validations on the server side even when executing on the client side as well + +## Performance Best Practices + +- Prefer inform the fields you want, and only the necessary ones, when querying data from database over query the full documents +- Limit the number of returned records to a reasonable value +- Check if the query is using indexes, it it's not create new indexes +- Prefer queues over long executions +- Create new metrics to mesure things whenever possible +- Cache data and returns whenever possible + +## Contributor License Agreement + +To have your contribution accepted you must sign our [Contributor License Agreement](https://cla-assistant.io/RocketChat/Rocket.Chat). In case you submit a Pull Request before sign the CLA GitHub will alert you with a new comment asking you to sign and will block the Pull Request from be merged by us. Please review and sign our CLA at https://cla-assistant.io/RocketChat/Rocket.Chat diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 2d74d00f0e17..bf8c1bed7ff9 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -227,7 +227,7 @@ jobs: MONGO_URL: mongodb://localhost:27017/rocketchat MONGO_OPLOG_URL: mongodb://localhost:27017/local run: | - for i in $(seq 1 5); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && xvfb-run --auto-servernum npm test && s=0 && break || s=$? && sleep 1; done; (exit $s) + for i in $(seq 1 5); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && xvfb-run --auto-servernum npm run testci && s=0 && break || s=$? && sleep 1; done; (exit $s) # notification: # runs-on: ubuntu-latest diff --git a/.scripts/start.js b/.scripts/start.js index aa9e3b334757..1159290bbbb9 100644 --- a/.scripts/start.js +++ b/.scripts/start.js @@ -112,7 +112,7 @@ function startChimp() { startProcess({ name: 'Chimp', command: 'npm', - params: ['run', 'testci'], + params: ['test'], // command: 'exit', // params: ['2'], options: { diff --git a/app/accounts/index.js b/app/accounts/index.js deleted file mode 100644 index ca39cd0df4b1..000000000000 --- a/app/accounts/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './server/index'; diff --git a/app/action-links/client/index.js b/app/action-links/client/index.js index 34c929c096f9..c09886562a2d 100644 --- a/app/action-links/client/index.js +++ b/app/action-links/client/index.js @@ -1,5 +1,4 @@ -import { actionLinks } from '../both/lib/actionLinks'; -import './lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; import './init'; import './stylesheets/actionLinks.css'; diff --git a/app/action-links/client/init.js b/app/action-links/client/init.js index 2865be4279c9..b5f218b1ca18 100644 --- a/app/action-links/client/init.js +++ b/app/action-links/client/init.js @@ -1,14 +1,14 @@ import { Blaze } from 'meteor/blaze'; import { Template } from 'meteor/templating'; -import { handleError } from '../../utils'; -import { fireGlobalEvent, Layout } from '../../ui-utils'; +import { handleError } from '../../utils/client'; +import { fireGlobalEvent, Layout } from '../../ui-utils/client'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; -import { actionLinks } from '../both/lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; Template.room.events({ - 'click .action-link'(event, instance) { + 'click [data-actionlink]'(event, instance) { event.preventDefault(); event.stopPropagation(); diff --git a/app/action-links/client/lib/actionLinks.js b/app/action-links/client/lib/actionLinks.js index 4391eda94afb..b3d911398dad 100644 --- a/app/action-links/client/lib/actionLinks.js +++ b/app/action-links/client/lib/actionLinks.js @@ -1,27 +1,58 @@ import { Meteor } from 'meteor/meteor'; -import { handleError } from '../../../utils'; -import { actionLinks } from '../../both/lib/actionLinks'; -// Action Links Handler. This method will be called off the client. +import { handleError } from '../../../utils/client'; +import { Messages, Subscriptions } from '../../../models/client'; -actionLinks.run = (name, messageId, instance) => { - const message = actionLinks.getMessage(name, messageId); +// Action Links namespace creation. +export const actionLinks = { + actions: {}, + register(name, funct) { + actionLinks.actions[name] = funct; + }, + getMessage(name, messageId) { + const userId = Meteor.userId(); + if (!userId) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: 'actionLinks.getMessage' }); + } + + const message = Messages.findOne({ _id: messageId }); + if (!message) { + throw new Meteor.Error('error-invalid-message', 'Invalid message', { function: 'actionLinks.getMessage' }); + } + + const subscription = Subscriptions.findOne({ + rid: message.rid, + 'u._id': userId, + }); + if (!subscription) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { function: 'actionLinks.getMessage' }); + } + + if (!message.actionLinks || !message.actionLinks[name]) { + throw new Meteor.Error('error-invalid-actionlink', 'Invalid action link', { function: 'actionLinks.getMessage' }); + } - const actionLink = message.actionLinks[name]; + return message; + }, + run(name, messageId, instance) { + const message = actionLinks.getMessage(name, messageId); - let ranClient = false; + const actionLink = message.actionLinks[name]; - if (actionLinks && actionLinks.actions && actionLinks.actions[actionLink.method_id]) { - // run just on client side - actionLinks.actions[actionLink.method_id](message, actionLink.params, instance); + let ranClient = false; - ranClient = true; - } + if (actionLinks && actionLinks.actions && actionLinks.actions[actionLink.method_id]) { + // run just on client side + actionLinks.actions[actionLink.method_id](message, actionLink.params, instance); - // and run on server side - Meteor.call('actionLinkHandler', name, messageId, (err) => { - if (err && !ranClient) { - handleError(err); + ranClient = true; } - }); + + // and run on server side + Meteor.call('actionLinkHandler', name, messageId, (err) => { + if (err && !ranClient) { + handleError(err); + } + }); + }, }; diff --git a/app/action-links/index.js b/app/action-links/index.js deleted file mode 100644 index a67eca871efb..000000000000 --- a/app/action-links/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -if (Meteor.isClient) { - module.exports = require('./client/index.js'); -} -if (Meteor.isServer) { - module.exports = require('./server/index.js'); -} diff --git a/app/action-links/server/actionLinkHandler.js b/app/action-links/server/actionLinkHandler.js index 067f727e3dda..7ccd1b05775b 100644 --- a/app/action-links/server/actionLinkHandler.js +++ b/app/action-links/server/actionLinkHandler.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { actionLinks } from '../both/lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; // Action Links Handler. This method will be called off the client. Meteor.methods({ diff --git a/app/action-links/server/index.js b/app/action-links/server/index.js index b1c484f79888..a6fb9f92b743 100644 --- a/app/action-links/server/index.js +++ b/app/action-links/server/index.js @@ -1,4 +1,4 @@ -import { actionLinks } from '../both/lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; import './actionLinkHandler'; export { diff --git a/app/action-links/both/lib/actionLinks.js b/app/action-links/server/lib/actionLinks.js similarity index 93% rename from app/action-links/both/lib/actionLinks.js rename to app/action-links/server/lib/actionLinks.js index c87c712e079b..3f7b2f2e5775 100644 --- a/app/action-links/both/lib/actionLinks.js +++ b/app/action-links/server/lib/actionLinks.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { Messages, Subscriptions } from '../../../models'; +import { Messages, Subscriptions } from '../../../models/server'; // Action Links namespace creation. export const actionLinks = { diff --git a/app/api/index.js b/app/api/index.js deleted file mode 100644 index ca39cd0df4b1..000000000000 --- a/app/api/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './server/index'; diff --git a/app/api/server/v1/assets.js b/app/api/server/v1/assets.js index eacf92ae31cd..108f9649ffe6 100644 --- a/app/api/server/v1/assets.js +++ b/app/api/server/v1/assets.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import Busboy from 'busboy'; -import { RocketChatAssets } from '../../../assets'; +import { RocketChatAssets } from '../../../assets/server'; import { API } from '../api'; API.v1.addRoute('assets.setAsset', { authRequired: true }, { diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 5a8be4493a00..65aee384de9d 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -539,7 +539,6 @@ API.v1.addRoute('users.setPreferences', { authRequired: true }, { mobileNotifications: Match.Maybe(String), enableAutoAway: Match.Maybe(Boolean), highlights: Match.Maybe(Array), - desktopNotificationDuration: Match.Maybe(Number), desktopNotificationRequireInteraction: Match.Maybe(Boolean), messageViewMode: Match.Maybe(Number), hideUsernames: Match.Maybe(Boolean), diff --git a/app/assets/index.js b/app/assets/index.js deleted file mode 100644 index ca39cd0df4b1..000000000000 --- a/app/assets/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './server/index'; diff --git a/app/bigbluebutton/index.js b/app/bigbluebutton/index.js deleted file mode 100644 index ba58589ba3d7..000000000000 --- a/app/bigbluebutton/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './server/bigbluebutton-api'; diff --git a/app/bigbluebutton/server/index.js b/app/bigbluebutton/server/index.js new file mode 100644 index 000000000000..b6be696a20bd --- /dev/null +++ b/app/bigbluebutton/server/index.js @@ -0,0 +1 @@ +export { default } from './bigbluebutton-api'; diff --git a/app/blockstack/server/routes.js b/app/blockstack/server/routes.js index ee4c7cc087df..81902030e426 100644 --- a/app/blockstack/server/routes.js +++ b/app/blockstack/server/routes.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { WebApp } from 'meteor/webapp'; import { settings } from '../../settings'; -import { RocketChatAssets } from '../../assets'; +import { RocketChatAssets } from '../../assets/server'; WebApp.connectHandlers.use('/_blockstack/manifest', Meteor.bindEnvironment(function(req, res) { const name = settings.get('Site_Name'); diff --git a/app/bot-helpers/index.js b/app/bot-helpers/index.js deleted file mode 100644 index f5778a23b606..000000000000 --- a/app/bot-helpers/index.js +++ /dev/null @@ -1 +0,0 @@ -import './server/index'; diff --git a/app/crowd/server/crowd.js b/app/crowd/server/crowd.js index c91d2de399e4..6bbf95614358 100644 --- a/app/crowd/server/crowd.js +++ b/app/crowd/server/crowd.js @@ -9,6 +9,7 @@ import { _setRealName } from '../../lib'; import { Users } from '../../models'; import { settings } from '../../settings'; import { hasRole } from '../../authorization'; +import { deleteUser } from '../../lib/server/functions'; const logger = new Logger('CROWD', {}); @@ -203,6 +204,13 @@ export class CROWD { const response = self.crowdClient.searchSync('user', `email=" ${ email } "`); if (!response || response.users.length === 0) { logger.warn('Could not find user in CROWD with username or email:', crowd_username, email); + if (settings.get('CROWD_Remove_Orphaned_Users') === true) { + logger.info('Removing user:', crowd_username); + Meteor.defer(function() { + deleteUser(user._id); + logger.info('User removed:', crowd_username); + }); + } return; } crowd_username = response.users[0].name; diff --git a/app/crowd/server/settings.js b/app/crowd/server/settings.js index 29307d957a78..b58362967294 100644 --- a/app/crowd/server/settings.js +++ b/app/crowd/server/settings.js @@ -14,6 +14,7 @@ Meteor.startup(function() { this.add('CROWD_APP_PASSWORD', '', { type: 'password', enableQuery, i18nLabel: 'Password', secret: true }); this.add('CROWD_Sync_User_Data', false, { type: 'boolean', enableQuery, i18nLabel: 'Sync_Users' }); this.add('CROWD_Sync_Interval', 'Every 60 mins', { type: 'string', enableQuery: enableSyncQuery, i18nLabel: 'Sync_Interval', i18nDescription: 'Crowd_sync_interval_Description' }); + this.add('CROWD_Remove_Orphaned_Users', false, { type: 'boolean', public: true, i18nLabel: 'Crowd_Remove_Orphaned_Users' }); this.add('CROWD_Clean_Usernames', true, { type: 'boolean', enableQuery, i18nLabel: 'Clean_Usernames', i18nDescription: 'Crowd_clean_usernames_Description' }); this.add('CROWD_Allow_Custom_Username', true, { type: 'boolean', i18nLabel: 'CROWD_Allow_Custom_Username' }); this.add('CROWD_Test_Connection', 'crowd_test_connection', { type: 'action', actionText: 'Test_Connection', i18nLabel: 'Test_Connection' }); diff --git a/app/integrations/server/api/api.js b/app/integrations/server/api/api.js index db79dc0943bd..aa55b3bd1fad 100644 --- a/app/integrations/server/api/api.js +++ b/app/integrations/server/api/api.js @@ -12,7 +12,7 @@ import moment from 'moment'; import { logger } from '../logger'; import { processWebhookMessage } from '../../../lib'; -import { API, APIClass, defaultRateLimiterOptions } from '../../../api'; +import { API, APIClass, defaultRateLimiterOptions } from '../../../api/server'; import * as Models from '../../../models'; import { settings } from '../../../settings/server'; diff --git a/app/lib/server/lib/sendNotificationsOnMessage.js b/app/lib/server/lib/sendNotificationsOnMessage.js index 834eac239b0e..f8db60fc1e77 100644 --- a/app/lib/server/lib/sendNotificationsOnMessage.js +++ b/app/lib/server/lib/sendNotificationsOnMessage.js @@ -112,7 +112,6 @@ export const sendNotification = async ({ user: sender, message, room, - duration: subscription.desktopNotificationDuration, }); } @@ -176,7 +175,6 @@ export const sendNotification = async ({ const project = { $project: { audioNotifications: 1, - desktopNotificationDuration: 1, desktopNotifications: 1, emailNotifications: 1, mobilePushNotifications: 1, diff --git a/app/lib/server/startup/settings.js b/app/lib/server/startup/settings.js index 63241e0b0bed..281206ec273c 100644 --- a/app/lib/server/startup/settings.js +++ b/app/lib/server/startup/settings.js @@ -236,11 +236,6 @@ settings.addGroup('Accounts', function() { public: true, i18nLabel: 'Idle_Time_Limit', }); - this.add('Accounts_Default_User_Preferences_desktopNotificationDuration', 0, { - type: 'int', - public: true, - i18nLabel: 'Notification_Duration', - }); this.add('Accounts_Default_User_Preferences_desktopNotificationRequireInteraction', false, { type: 'boolean', public: true, diff --git a/app/livechat/client/index.js b/app/livechat/client/index.js index 26013edf70a5..0174884f532f 100644 --- a/app/livechat/client/index.js +++ b/app/livechat/client/index.js @@ -9,3 +9,4 @@ import './stylesheets/livechat.css'; import './views/sideNav/livechat'; import './views/sideNav/livechatFlex'; import './externalFrame'; +import './lib/messageTypes'; diff --git a/app/livechat/client/lib/messageTypes.js b/app/livechat/client/lib/messageTypes.js new file mode 100644 index 000000000000..0e3353f31366 --- /dev/null +++ b/app/livechat/client/lib/messageTypes.js @@ -0,0 +1,5 @@ +import { actionLinks } from '../../../action-links/client'; + +actionLinks.register('createLivechatCall', function(message, params, instance) { + instance.tabBar.open('video'); +}); diff --git a/app/livechat/client/views/app/livechatAgents.html b/app/livechat/client/views/app/livechatAgents.html index bb57a86497e9..a32d04763b89 100644 --- a/app/livechat/client/views/app/livechatAgents.html +++ b/app/livechat/client/views/app/livechatAgents.html @@ -51,9 +51,11 @@ {{#table fixed='true' onScroll=onTableScroll}} -
{{_ "Name"}}
-
{{_ "Username"}}
-
{{_ "Email"}}
+
{{_ "Name"}}
+
{{_ "Username"}}
+
{{_ "Email"}}
+
{{_ "Status"}}
+
{{_ "Service"}}
 
@@ -79,6 +81,8 @@ {{username}} {{emailAddress}} + {{status}} + {{statusService}} diff --git a/app/livechat/client/views/app/livechatAgents.js b/app/livechat/client/views/app/livechatAgents.js index 56c45da892ae..af3b68b88854 100644 --- a/app/livechat/client/views/app/livechatAgents.js +++ b/app/livechat/client/views/app/livechatAgents.js @@ -90,6 +90,10 @@ Template.livechatAgents.helpers({ data: Template.instance().tabBarData.get(), }; }, + statusService() { + const { status, statusLivechat } = this; + return statusLivechat === 'available' && status !== 'offline' ? t('Available') : t('Unavailable'); + }, }); const DEBOUNCE_TIME_FOR_SEARCH_AGENTS_IN_MS = 300; diff --git a/app/livechat/client/views/app/livechatCurrentChats.js b/app/livechat/client/views/app/livechatCurrentChats.js index b8493dfeab53..bb5dbd0915d4 100644 --- a/app/livechat/client/views/app/livechatCurrentChats.js +++ b/app/livechat/client/views/app/livechatCurrentChats.js @@ -506,8 +506,6 @@ Template.livechatCurrentChats.onCreated(async function() { this.customFields.set(customFields); } }); - - this.loadDefaultFilters(); }); Template.livechatCurrentChats.onRendered(function() { @@ -516,4 +514,6 @@ Template.livechatCurrentChats.onRendered(function() { todayHighlight: true, format: moment.localeData().longDateFormat('L').toLowerCase(), }); + + this.loadDefaultFilters(); }); diff --git a/app/livechat/imports/server/rest/agent.js b/app/livechat/imports/server/rest/agent.js index b928501dc2c1..170af7636ff2 100644 --- a/app/livechat/imports/server/rest/agent.js +++ b/app/livechat/imports/server/rest/agent.js @@ -1,6 +1,6 @@ import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findAgentDepartments } from '../../../server/api/lib/agents'; API.v1.addRoute('livechat/agents/:agentId/departments', { authRequired: true }, { diff --git a/app/livechat/imports/server/rest/appearance.js b/app/livechat/imports/server/rest/appearance.js index f8345f7d31dc..c7fe16243a78 100644 --- a/app/livechat/imports/server/rest/appearance.js +++ b/app/livechat/imports/server/rest/appearance.js @@ -1,4 +1,4 @@ -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findAppearance } from '../../../server/api/lib/appearance'; API.v1.addRoute('livechat/appearance', { authRequired: true }, { diff --git a/app/livechat/imports/server/rest/dashboards.js b/app/livechat/imports/server/rest/dashboards.js index 2537942e74dc..af8972296b20 100644 --- a/app/livechat/imports/server/rest/dashboards.js +++ b/app/livechat/imports/server/rest/dashboards.js @@ -1,6 +1,6 @@ import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { hasPermission } from '../../../../authorization/server'; import { findAllChatsStatus, diff --git a/app/livechat/imports/server/rest/departments.js b/app/livechat/imports/server/rest/departments.js index 8a255185f86f..9ec4113ba66a 100644 --- a/app/livechat/imports/server/rest/departments.js +++ b/app/livechat/imports/server/rest/departments.js @@ -1,6 +1,6 @@ import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { hasPermission } from '../../../../authorization'; import { LivechatDepartment, LivechatDepartmentAgents } from '../../../../models'; import { Livechat } from '../../../server/lib/Livechat'; diff --git a/app/livechat/imports/server/rest/facebook.js b/app/livechat/imports/server/rest/facebook.js index cce8c53a7165..b4b8efa55034 100644 --- a/app/livechat/imports/server/rest/facebook.js +++ b/app/livechat/imports/server/rest/facebook.js @@ -2,7 +2,7 @@ import crypto from 'crypto'; import { Random } from 'meteor/random'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { LivechatRooms, LivechatVisitors } from '../../../../models'; import { settings } from '../../../../settings'; import { Livechat } from '../../../server/lib/Livechat'; diff --git a/app/livechat/imports/server/rest/inquiries.js b/app/livechat/imports/server/rest/inquiries.js index a553c875fe84..3abfc5eee735 100644 --- a/app/livechat/imports/server/rest/inquiries.js +++ b/app/livechat/imports/server/rest/inquiries.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { hasPermission } from '../../../../authorization'; import { Users, LivechatDepartment, LivechatInquiry } from '../../../../models'; import { findInquiries, findOneInquiryByRoomId } from '../../../server/api/lib/inquiries'; diff --git a/app/livechat/imports/server/rest/integrations.js b/app/livechat/imports/server/rest/integrations.js index 08d9d064892a..6b7aed33d89f 100644 --- a/app/livechat/imports/server/rest/integrations.js +++ b/app/livechat/imports/server/rest/integrations.js @@ -1,4 +1,4 @@ -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findIntegrationSettings } from '../../../server/api/lib/integrations'; API.v1.addRoute('livechat/integrations.settings', { authRequired: true }, { diff --git a/app/livechat/imports/server/rest/messages.js b/app/livechat/imports/server/rest/messages.js index a40557231b53..f5ad166c8c70 100644 --- a/app/livechat/imports/server/rest/messages.js +++ b/app/livechat/imports/server/rest/messages.js @@ -1,7 +1,7 @@ import { check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findExternalMessages } from '../../../server/api/lib/messages'; API.v1.addRoute('livechat/messages.external/:roomId', { authRequired: true }, { diff --git a/app/livechat/imports/server/rest/officeHour.js b/app/livechat/imports/server/rest/officeHour.js index f321a31ea5a3..7b2cd02497ee 100644 --- a/app/livechat/imports/server/rest/officeHour.js +++ b/app/livechat/imports/server/rest/officeHour.js @@ -1,4 +1,4 @@ -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findLivechatOfficeHours } from '../../../server/api/lib/officeHour'; API.v1.addRoute('livechat/office-hours', { authRequired: true }, { diff --git a/app/livechat/imports/server/rest/queue.js b/app/livechat/imports/server/rest/queue.js index d5f319f2e3c0..43b586431ea2 100644 --- a/app/livechat/imports/server/rest/queue.js +++ b/app/livechat/imports/server/rest/queue.js @@ -1,4 +1,4 @@ -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findQueueMetrics } from '../../../server/api/lib/queue'; API.v1.addRoute('livechat/queue', { authRequired: true }, { diff --git a/app/livechat/imports/server/rest/rooms.js b/app/livechat/imports/server/rest/rooms.js index d7556006597a..052da5db3963 100644 --- a/app/livechat/imports/server/rest/rooms.js +++ b/app/livechat/imports/server/rest/rooms.js @@ -1,7 +1,7 @@ import { Match, check } from 'meteor/check'; import { hasPermission } from '../../../../authorization/server'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findRooms } from '../../../server/api/lib/rooms'; const validateDateParams = (property, date) => { diff --git a/app/livechat/imports/server/rest/sms.js b/app/livechat/imports/server/rest/sms.js index edebecb583a7..f813e7def804 100644 --- a/app/livechat/imports/server/rest/sms.js +++ b/app/livechat/imports/server/rest/sms.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { LivechatRooms, LivechatVisitors, LivechatDepartment } from '../../../../models'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { SMS } from '../../../../sms'; import { Livechat } from '../../../server/lib/Livechat'; diff --git a/app/livechat/imports/server/rest/triggers.js b/app/livechat/imports/server/rest/triggers.js index ca6ebe7a12a0..de3d0b57f27b 100644 --- a/app/livechat/imports/server/rest/triggers.js +++ b/app/livechat/imports/server/rest/triggers.js @@ -1,6 +1,6 @@ import { check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findTriggers, findTriggerById } from '../../../server/api/lib/triggers'; API.v1.addRoute('livechat/triggers', { authRequired: true }, { diff --git a/app/livechat/imports/server/rest/upload.js b/app/livechat/imports/server/rest/upload.js index 3d28f420402e..4c27811749a6 100644 --- a/app/livechat/imports/server/rest/upload.js +++ b/app/livechat/imports/server/rest/upload.js @@ -6,7 +6,7 @@ import { settings } from '../../../../settings'; import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models'; import { fileUploadIsValidContentType } from '../../../../utils'; import { FileUpload } from '../../../../file-upload'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; let maxFileSize; diff --git a/app/livechat/imports/server/rest/users.js b/app/livechat/imports/server/rest/users.js index 04e1815b4f07..f0c88aa25c59 100644 --- a/app/livechat/imports/server/rest/users.js +++ b/app/livechat/imports/server/rest/users.js @@ -2,7 +2,7 @@ import { check } from 'meteor/check'; import _ from 'underscore'; import { hasPermission } from '../../../../authorization'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { Users } from '../../../../models'; import { Livechat } from '../../../server/lib/Livechat'; import { findAgents, findManagers } from '../../../server/api/lib/users'; diff --git a/app/livechat/imports/server/rest/visitors.js b/app/livechat/imports/server/rest/visitors.js index 42b5a20b25d7..c828e9552e58 100644 --- a/app/livechat/imports/server/rest/visitors.js +++ b/app/livechat/imports/server/rest/visitors.js @@ -1,7 +1,7 @@ import { check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findVisitorInfo, findVisitedPages, findChatHistory } from '../../../server/api/lib/visitors'; API.v1.addRoute('livechat/visitors.info', { authRequired: true }, { diff --git a/app/livechat/lib/messageTypes.js b/app/livechat/lib/messageTypes.js index c68e120f0542..8f870923bd28 100644 --- a/app/livechat/lib/messageTypes.js +++ b/app/livechat/lib/messageTypes.js @@ -1,12 +1,6 @@ -import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Livechat } from 'meteor/rocketchat:livechat'; import { MessageTypes } from '../../ui-utils'; -import { actionLinks } from '../../action-links'; -import { Notifications } from '../../notifications'; -import { Messages, LivechatRooms } from '../../models'; -import { settings } from '../../settings'; MessageTypes.registerType({ id: 'livechat_navigation_history', @@ -60,29 +54,3 @@ MessageTypes.registerType({ system: true, message: 'New_videocall_request', }); - -actionLinks.register('createLivechatCall', function(message, params, instance) { - if (Meteor.isClient) { - instance.tabBar.open('video'); - } -}); - -actionLinks.register('denyLivechatCall', function(message/* , params*/) { - if (Meteor.isServer) { - const user = Meteor.user(); - - Messages.createWithTypeRoomIdMessageAndUser('command', message.rid, 'endCall', user); - Notifications.notifyRoom(message.rid, 'deleteMessage', { _id: message._id }); - - const language = user.language || settings.get('Language') || 'en'; - - Livechat.closeRoom({ - user, - room: LivechatRooms.findOneById(message.rid), - comment: TAPi18n.__('Videocall_declined', { lng: language }), - }); - Meteor.defer(() => { - Messages.setHiddenById(message._id); - }); - } -}); diff --git a/app/livechat/server/api/lib/transfer.js b/app/livechat/server/api/lib/transfer.js new file mode 100644 index 000000000000..60070dfc2645 --- /dev/null +++ b/app/livechat/server/api/lib/transfer.js @@ -0,0 +1,27 @@ +import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; +import { Messages } from '../../../../models/server/raw'; + +const normalizeTransferHistory = ({ transferData }) => transferData; +export async function findLivechatTransferHistory({ userId, rid, pagination: { offset, count, sort } }) { + if (!await hasPermissionAsync(userId, 'view-livechat-rooms')) { + throw new Error('error-not-authorized'); + } + + const cursor = await Messages.find({ rid, t: 'livechat_transfer_history' }, { + fields: { transferData: 1 }, + sort: sort || { ts: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + const messages = await cursor.toArray(); + const history = messages.map(normalizeTransferHistory); + + return { + history, + count: history.length, + offset, + total, + }; +} diff --git a/app/livechat/server/api/rest.js b/app/livechat/server/api/rest.js index 3731e72f6b63..a63794bf1db0 100644 --- a/app/livechat/server/api/rest.js +++ b/app/livechat/server/api/rest.js @@ -8,3 +8,4 @@ import './v1/message.js'; import './v1/customField.js'; import './v1/room.js'; import './v1/videoCall.js'; +import './v1/transfer.js'; diff --git a/app/livechat/server/api/v1/agent.js b/app/livechat/server/api/v1/agent.js index 68ceef87da14..7e2329058542 100644 --- a/app/livechat/server/api/v1/agent.js +++ b/app/livechat/server/api/v1/agent.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findRoom, findGuest, findAgent, findOpenRoom } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; diff --git a/app/livechat/server/api/v1/config.js b/app/livechat/server/api/v1/config.js index a1f4bab03405..50e4229fa40e 100644 --- a/app/livechat/server/api/v1/config.js +++ b/app/livechat/server/api/v1/config.js @@ -1,6 +1,6 @@ import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findGuest, settings, online, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat'; API.v1.addRoute('livechat/config', { diff --git a/app/livechat/server/api/v1/customField.js b/app/livechat/server/api/v1/customField.js index f64266d3be6c..3b19e832bc65 100644 --- a/app/livechat/server/api/v1/customField.js +++ b/app/livechat/server/api/v1/customField.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findGuest } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; import { findLivechatCustomFields, findCustomFieldById } from '../lib/customFields'; diff --git a/app/livechat/server/api/v1/message.js b/app/livechat/server/api/v1/message.js index e2362baf6d32..0811bc8324fb 100644 --- a/app/livechat/server/api/v1/message.js +++ b/app/livechat/server/api/v1/message.js @@ -4,7 +4,7 @@ import { Random } from 'meteor/random'; import { Messages, LivechatRooms, LivechatVisitors } from '../../../../models'; import { hasPermission } from '../../../../authorization'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { loadMessageHistory } from '../../../../lib'; import { findGuest, findRoom, normalizeHttpHeaderData } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; diff --git a/app/livechat/server/api/v1/offlineMessage.js b/app/livechat/server/api/v1/offlineMessage.js index 6788c30e3d86..8264228c97e7 100644 --- a/app/livechat/server/api/v1/offlineMessage.js +++ b/app/livechat/server/api/v1/offlineMessage.js @@ -1,7 +1,7 @@ import { Match, check } from 'meteor/check'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { Livechat } from '../../lib/Livechat'; API.v1.addRoute('livechat/offline.message', { diff --git a/app/livechat/server/api/v1/pageVisited.js b/app/livechat/server/api/v1/pageVisited.js index e5ef7c42ba64..4f8c638e6146 100644 --- a/app/livechat/server/api/v1/pageVisited.js +++ b/app/livechat/server/api/v1/pageVisited.js @@ -1,7 +1,7 @@ import { Match, check } from 'meteor/check'; import _ from 'underscore'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { Livechat } from '../../lib/Livechat'; API.v1.addRoute('livechat/page.visited', { diff --git a/app/livechat/server/api/v1/room.js b/app/livechat/server/api/v1/room.js index 83af5ffe1b61..5ec9ef1cf06e 100644 --- a/app/livechat/server/api/v1/room.js +++ b/app/livechat/server/api/v1/room.js @@ -5,7 +5,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { settings as rcSettings } from '../../../../settings'; import { Messages, LivechatRooms } from '../../../../models'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findGuest, findRoom, getRoom, settings, findAgent, onCheckRoomParams } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; import { normalizeTransferredByData } from '../../lib/Helper'; diff --git a/app/livechat/server/api/v1/transcript.js b/app/livechat/server/api/v1/transcript.js index 02c0d9d27561..f8f3c923d25e 100644 --- a/app/livechat/server/api/v1/transcript.js +++ b/app/livechat/server/api/v1/transcript.js @@ -1,7 +1,7 @@ import { check } from 'meteor/check'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { Livechat } from '../../lib/Livechat'; API.v1.addRoute('livechat/transcript', { diff --git a/app/livechat/server/api/v1/transfer.js b/app/livechat/server/api/v1/transfer.js new file mode 100644 index 000000000000..aa3fb7facd0e --- /dev/null +++ b/app/livechat/server/api/v1/transfer.js @@ -0,0 +1,36 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; + +import { LivechatRooms } from '../../../../models'; +import { API } from '../../../../api/server'; +import { findLivechatTransferHistory } from '../lib/transfer'; + +API.v1.addRoute('livechat/transfer.history/:rid', { authRequired: true }, { + get() { + check(this.urlParams, { + rid: String, + }); + + const { rid } = this.urlParams; + + const room = LivechatRooms.findOneById(rid, { _id: 1 }); + if (!room) { + throw new Meteor.Error('invalid-room'); + } + + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + + const history = Promise.await(findLivechatTransferHistory({ + userId: this.userId, + rid, + pagination: { + offset, + count, + sort, + }, + })); + + return API.v1.success(history); + }, +}); diff --git a/app/livechat/server/api/v1/videoCall.js b/app/livechat/server/api/v1/videoCall.js index 0aaa231da654..56159d8c349c 100644 --- a/app/livechat/server/api/v1/videoCall.js +++ b/app/livechat/server/api/v1/videoCall.js @@ -4,7 +4,7 @@ import { Random } from 'meteor/random'; import { Messages } from '../../../../models'; import { settings as rcSettings } from '../../../../settings'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findGuest, getRoom, settings } from '../lib/livechat'; API.v1.addRoute('livechat/video.call/:token', { diff --git a/app/livechat/server/api/v1/visitor.js b/app/livechat/server/api/v1/visitor.js index f34930a1b9c3..98007540876c 100644 --- a/app/livechat/server/api/v1/visitor.js +++ b/app/livechat/server/api/v1/visitor.js @@ -3,7 +3,7 @@ import { Match, check } from 'meteor/check'; import { LivechatRooms, LivechatVisitors, LivechatCustomField } from '../../../../models'; import { hasPermission } from '../../../../authorization'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findGuest, normalizeHttpHeaderData } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; diff --git a/app/livechat/server/index.js b/app/livechat/server/index.js index 72632b50670e..527bc59802d6 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -84,5 +84,6 @@ import './sendMessageBySMS'; import './api'; import './api/rest'; import './externalFrame'; +import './lib/messageTypes'; export { Livechat } from './lib/Livechat'; diff --git a/app/livechat/server/lib/messageTypes.js b/app/livechat/server/lib/messageTypes.js new file mode 100644 index 000000000000..3d32da6f401f --- /dev/null +++ b/app/livechat/server/lib/messageTypes.js @@ -0,0 +1,26 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { actionLinks } from '../../../action-links/server'; +import { Notifications } from '../../../notifications/server'; +import { Messages, LivechatRooms } from '../../../models/server'; +import { settings } from '../../../settings/server'; +import { Livechat } from './Livechat'; + +actionLinks.register('denyLivechatCall', function(message/* , params*/) { + const user = Meteor.user(); + + Messages.createWithTypeRoomIdMessageAndUser('command', message.rid, 'endCall', user); + Notifications.notifyRoom(message.rid, 'deleteMessage', { _id: message._id }); + + const language = user.language || settings.get('Language') || 'en'; + + Livechat.closeRoom({ + user, + room: LivechatRooms.findOneById(message.rid), + comment: TAPi18n.__('Videocall_declined', { lng: language }), + }); + Meteor.defer(() => { + Messages.setHiddenById(message._id); + }); +}); diff --git a/app/livestream/server/routes.js b/app/livestream/server/routes.js index 8668217d19a5..3a52aec6031c 100644 --- a/app/livestream/server/routes.js +++ b/app/livestream/server/routes.js @@ -3,7 +3,7 @@ import google from 'googleapis'; import { settings } from '../../settings'; import { Users } from '../../models'; -import { API } from '../../api'; +import { API } from '../../api/server'; const { OAuth2 } = google.auth; diff --git a/app/models/server/models/Subscriptions.js b/app/models/server/models/Subscriptions.js index 3442cfcd5a46..e991c1d925a1 100644 --- a/app/models/server/models/Subscriptions.js +++ b/app/models/server/models/Subscriptions.js @@ -180,20 +180,6 @@ export class Subscriptions extends Base { return this.update(query, update); } - updateDesktopNotificationDurationById(_id, value) { - const query = { - _id, - }; - - const update = { - $set: { - desktopNotificationDuration: parseInt(value), - }, - }; - - return this.update(query, update); - } - updateMobilePushNotificationsById(_id, mobilePushNotifications) { const query = { _id, @@ -366,7 +352,6 @@ export class Subscriptions extends Base { ignored: 1, audioNotifications: 1, audioNotificationValue: 1, - desktopNotificationDuration: 1, desktopNotifications: 1, mobilePushNotifications: 1, emailNotifications: 1, @@ -393,7 +378,6 @@ export class Subscriptions extends Base { 'u._id': 1, audioNotifications: 1, audioNotificationValue: 1, - desktopNotificationDuration: 1, desktopNotifications: 1, mobilePushNotifications: 1, emailNotifications: 1, diff --git a/app/oauth2-server-config/server/oauth/oauth2-server.js b/app/oauth2-server-config/server/oauth/oauth2-server.js index ff813497bc5a..f1c51982c760 100644 --- a/app/oauth2-server-config/server/oauth/oauth2-server.js +++ b/app/oauth2-server-config/server/oauth/oauth2-server.js @@ -3,7 +3,7 @@ import { WebApp } from 'meteor/webapp'; import { OAuth2Server } from 'meteor/rocketchat:oauth2-server'; import { OAuthApps, Users } from '../../../models'; -import { API } from '../../../api'; +import { API } from '../../../api/server'; const oauth2server = new OAuth2Server({ accessTokensCollectionName: 'rocketchat_oauth_access_tokens', diff --git a/app/push-notifications/client/views/pushNotificationsFlexTab.html b/app/push-notifications/client/views/pushNotificationsFlexTab.html index a32f1b6e9713..f1e3a28162ad 100644 --- a/app/push-notifications/client/views/pushNotificationsFlexTab.html +++ b/app/push-notifications/client/views/pushNotificationsFlexTab.html @@ -101,20 +101,6 @@ {{/with}} -
- - {{# with "desktopNotificationDuration"}} - - {{/with}} -
diff --git a/app/push-notifications/client/views/pushNotificationsFlexTab.js b/app/push-notifications/client/views/pushNotificationsFlexTab.js index a71609be4145..43f026f2aaa9 100644 --- a/app/push-notifications/client/views/pushNotificationsFlexTab.js +++ b/app/push-notifications/client/views/pushNotificationsFlexTab.js @@ -74,9 +74,6 @@ Template.pushNotificationsFlexTab.helpers({ emailNotifications() { return Template.instance().form.emailNotifications.get(); }, - desktopNotificationDuration() { - return Template.instance().form.desktopNotificationDuration.get(); - }, subValue(field) { const { form } = Template.instance(); if (form[field]) { @@ -131,7 +128,6 @@ Template.pushNotificationsFlexTab.onCreated(function() { desktopNotifications: 1, mobilePushNotifications: 1, emailNotifications: 1, - desktopNotificationDuration: 1, audioNotificationValue: 1, muteGroupMentions: 1, }, @@ -144,7 +140,6 @@ Template.pushNotificationsFlexTab.onCreated(function() { desktopNotifications = 'default', mobilePushNotifications = 'default', emailNotifications = 'default', - desktopNotificationDuration = 0, muteGroupMentions = false, } = sub; @@ -157,7 +152,6 @@ Template.pushNotificationsFlexTab.onCreated(function() { desktopNotifications: new ReactiveVar(desktopNotifications), mobilePushNotifications: new ReactiveVar(mobilePushNotifications), emailNotifications: new ReactiveVar(emailNotifications), - desktopNotificationDuration: new ReactiveVar(desktopNotificationDuration), audioNotificationValue: new ReactiveVar(audioNotificationValue), muteGroupMentions: new ReactiveVar(muteGroupMentions), }; @@ -169,7 +163,6 @@ Template.pushNotificationsFlexTab.onCreated(function() { desktopNotifications: new ReactiveVar(desktopNotifications), mobilePushNotifications: new ReactiveVar(mobilePushNotifications), emailNotifications: new ReactiveVar(emailNotifications), - desktopNotificationDuration: new ReactiveVar(desktopNotificationDuration), audioNotificationValue: new ReactiveVar(audioNotificationValue), muteGroupMentions: new ReactiveVar(muteGroupMentions), }; @@ -186,9 +179,6 @@ Template.pushNotificationsFlexTab.onCreated(function() { } const rid = Session.get('openedRoom'); switch (field) { - case 'desktopNotificationDuration': - await call('saveDesktopNotificationDuration', rid, value); - break; case 'audioNotificationValue': await call('saveAudioNotificationValue', rid, value.split(' ')[0]); break; @@ -262,44 +252,6 @@ Template.pushNotificationsFlexTab.events({ ...audioAssetsArray, ]; break; - case 'desktopNotificationDuration': - options = [{ - id: 'desktopNotificationDuration', - name: 'desktopNotificationDuration', - label: 'Default', - value: 0, - }, - { - id: 'desktopNotificationDuration1s', - name: 'desktopNotificationDuration', - label: `1 ${ t('seconds') }`, - value: 1, - }, - { - id: 'desktopNotificationDuration2s', - name: 'desktopNotificationDuration', - label: `2 ${ t('seconds') }`, - value: 2, - }, - { - id: 'desktopNotificationDuration3s', - name: 'desktopNotificationDuration', - label: `3 ${ t('seconds') }`, - value: 3, - }, - { - id: 'desktopNotificationDuration4s', - name: 'desktopNotificationDuration', - label: `4 ${ t('seconds') }`, - value: 4, - }, - { - id: 'desktopNotificationDuration5s', - name: 'desktopNotificationDuration', - label: `5 ${ t('seconds') }`, - value: 5, - }]; - break; default: options = [{ id: 'desktopNotificationsDefault', @@ -331,7 +283,7 @@ Template.pushNotificationsFlexTab.events({ popoverClass: 'notifications-preferences', template: 'pushNotificationsPopover', data: { - change: (value) => instance.form[key].set(key === 'desktopNotificationDuration' ? parseInt(value) : value), + change: (value) => instance.form[key].set(value), value: instance.form[key].get(), options, }, diff --git a/app/push-notifications/server/methods/saveNotificationSettings.js b/app/push-notifications/server/methods/saveNotificationSettings.js index 3ddd80299e60..faf879b7ed94 100644 --- a/app/push-notifications/server/methods/saveNotificationSettings.js +++ b/app/push-notifications/server/methods/saveNotificationSettings.js @@ -59,9 +59,6 @@ Meteor.methods({ muteGroupMentions: { updateMethod: (subscription, value) => Subscriptions.updateMuteGroupMentions(subscription._id, value === '1'), }, - desktopNotificationDuration: { - updateMethod: (subscription, value) => Subscriptions.updateDesktopNotificationDurationById(subscription._id, value), - }, audioNotificationValue: { updateMethod: (subscription, value) => Subscriptions.updateAudioNotificationValueById(subscription._id, value), }, @@ -96,13 +93,4 @@ Meteor.methods({ Subscriptions.updateAudioNotificationValueById(subscription._id, value); return true; }, - - saveDesktopNotificationDuration(rid, value) { - const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, Meteor.userId()); - if (!subscription) { - throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { method: 'saveDesktopNotificationDuration' }); - } - Subscriptions.updateDesktopNotificationDurationById(subscription._id, value); - return true; - }, }); diff --git a/app/reactions/client/init.js b/app/reactions/client/init.js index 81d3c44ecb9f..81c48cb6aca0 100644 --- a/app/reactions/client/init.js +++ b/app/reactions/client/init.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Blaze } from 'meteor/blaze'; import { Template } from 'meteor/templating'; +import { roomTypes } from '../../utils/client'; import { Rooms } from '../../models'; import { MessageAction } from '../../ui-utils'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; @@ -17,13 +18,7 @@ Template.room.events({ const user = Meteor.user(); const room = Rooms.findOne({ _id: rid }); - if (room.ro && !room.reactWhenReadOnly) { - if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) { - return false; - } - } - - if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) { + if (roomTypes.readOnly(room._id, user._id)) { return false; } @@ -73,21 +68,15 @@ Meteor.startup(function() { return false; } - if (room.ro && !room.reactWhenReadOnly) { - if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) { - return false; - } - } - - if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) { + if (!subscription) { return false; } - if (!subscription) { + if (message.private) { return false; } - if (message.private) { + if (roomTypes.readOnly(room._id, user._id)) { return false; } diff --git a/app/reactions/client/methods/setReaction.js b/app/reactions/client/methods/setReaction.js index db58a8b4ea66..14ec5010f7f6 100644 --- a/app/reactions/client/methods/setReaction.js +++ b/app/reactions/client/methods/setReaction.js @@ -4,6 +4,7 @@ import _ from 'underscore'; import { Messages, Rooms, Subscriptions } from '../../../models'; import { callbacks } from '../../../callbacks'; import { emoji } from '../../../emoji'; +import { roomTypes } from '../../../utils/client'; Meteor.methods({ setReaction(reaction, messageId) { @@ -16,25 +17,19 @@ Meteor.methods({ const message = Messages.findOne({ _id: messageId }); const room = Rooms.findOne({ _id: message.rid }); - if (room.ro && !room.reactWhenReadOnly) { - if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) { - return false; - } - } - - if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) { + if (message.private) { return false; } - if (!Subscriptions.findOne({ rid: message.rid })) { + if (!emoji.list[reaction]) { return false; } - if (message.private) { + if (roomTypes.readOnly(room._id, user._id)) { return false; } - if (!emoji.list[reaction]) { + if (!Subscriptions.findOne({ rid: message.rid })) { return false; } diff --git a/app/reactions/server/setReaction.js b/app/reactions/server/setReaction.js index f9a6751e6134..52adec6d8c29 100644 --- a/app/reactions/server/setReaction.js +++ b/app/reactions/server/setReaction.js @@ -3,11 +3,12 @@ import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import _ from 'underscore'; -import { Messages, EmojiCustom, Subscriptions, Rooms } from '../../models'; +import { Messages, EmojiCustom, Rooms } from '../../models'; import { Notifications } from '../../notifications'; import { callbacks } from '../../callbacks'; import { emoji } from '../../emoji'; import { isTheLastMessage, msgStream } from '../../lib'; +import { hasPermission } from '../../authorization/server/functions/hasPermission'; const removeUserReaction = (message, reaction, username) => { message.reactions[reaction].usernames.splice(message.reactions[reaction].usernames.indexOf(username), 1); @@ -17,16 +18,17 @@ const removeUserReaction = (message, reaction, username) => { return message; }; -export function setReaction(room, user, message, reaction, shouldReact) { +async function setReaction(room, user, message, reaction, shouldReact) { reaction = `:${ reaction.replace(/:/g, '') }:`; if (!emoji.list[reaction] && EmojiCustom.findByNameOrAlias(reaction).count() === 0) { throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', { method: 'setReaction' }); } - if (room.ro && !room.reactWhenReadOnly) { - if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) { - return false; + if (room.ro === true && (!room.reactWhenReadOnly && !hasPermission(user._id, 'post-readonly', room._id))) { + // Unless the user was manually unmuted + if (!(room.unmuted || []).includes(user.username)) { + throw new Error('You can\'t send messages because the room is readonly.'); } } @@ -38,8 +40,6 @@ export function setReaction(room, user, message, reaction, shouldReact) { msg: TAPi18n.__('You_have_been_muted', {}, user.language), }); return false; - } if (!Subscriptions.findOne({ rid: message.rid })) { - return false; } const userAlreadyReacted = Boolean(message.reactions) && Boolean(message.reactions[reaction]) && message.reactions[reaction].usernames.indexOf(user.username) !== -1; @@ -92,18 +92,18 @@ Meteor.methods({ setReaction(reaction, messageId, shouldReact) { const user = Meteor.user(); - const message = Messages.findOneById(messageId); - - const room = Meteor.call('canAccessRoom', message.rid, Meteor.userId()); - if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setReaction' }); } + const message = Messages.findOneById(messageId); + if (!message) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' }); } + const room = Meteor.call('canAccessRoom', message.rid, Meteor.userId()); + if (!room) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' }); } diff --git a/app/theme/client/imports/general/base_old.css b/app/theme/client/imports/general/base_old.css index d2b2ace62a9b..0773f4350b3f 100644 --- a/app/theme/client/imports/general/base_old.css +++ b/app/theme/client/imports/general/base_old.css @@ -1314,6 +1314,15 @@ border-bottom: none; } + & .add-token { + display: flex; + + & .rc-select { + width: 40%; + margin: 0 0 0 10px; + } + } + &:first-child { padding-top: 0; } @@ -4147,6 +4156,15 @@ display: none; } } + + .add-token { + display: block !important; + + & .rc-select { + width: auto !important; + margin: 10px 0 !important; + } + } } @media (width <= 500px) { diff --git a/app/ui-account/client/accountPreferences.html b/app/ui-account/client/accountPreferences.html index ade63fece090..0da55e54fdf9 100644 --- a/app/ui-account/client/accountPreferences.html +++ b/app/ui-account/client/accountPreferences.html @@ -92,16 +92,6 @@

{{_ "Notifications"}}

{{/if}}
-
- -
- {{#if desktopNotificationDuration}} - - {{else}} - - {{/if}} -
-
diff --git a/app/ui-account/client/accountPreferences.js b/app/ui-account/client/accountPreferences.js index 099fab32f97a..2d7080102c3f 100644 --- a/app/ui-account/client/accountPreferences.js +++ b/app/ui-account/client/accountPreferences.js @@ -83,13 +83,6 @@ Template.accountPreferences.helpers({ desktopNotificationDisabled() { return KonchatNotification.notificationStatus.get() === 'denied' || (window.Notification && Notification.permission === 'denied'); }, - desktopNotificationDuration() { - const userPref = getUserPreference(Meteor.userId(), 'desktopNotificationDuration', 'undefined'); - return userPref !== 'undefined' ? userPref : undefined; - }, - defaultDesktopNotificationDuration() { - return settings.get('Accounts_Default_User_Preferences_desktopNotificationDuration'); - }, desktopNotificationRequireInteraction() { const userPref = getUserPreference(Meteor.userId(), 'desktopNotificationRequireInteraction', 'undefined'); return userPref !== 'undefined' ? userPref : undefined; @@ -178,7 +171,6 @@ Template.accountPreferences.onCreated(function() { data.sendOnEnter = $('#sendOnEnter').find('select').val(); data.autoImageLoad = JSON.parse($('input[name=autoImageLoad]:checked').val()); data.emailNotificationMode = $('select[name=emailNotificationMode]').val(); - data.desktopNotificationDuration = $('input[name=desktopNotificationDuration]').val() === '' ? settings.get('Accounts_Default_User_Preferences_desktopNotificationDuration') : parseInt($('input[name=desktopNotificationDuration]').val()); data.desktopNotifications = $('#desktopNotifications').find('select').val(); data.mobileNotifications = $('#mobileNotifications').find('select').val(); data.unreadAlert = JSON.parse($('#unreadAlert').find('input:checked').val()); @@ -330,7 +322,6 @@ Template.accountPreferences.events({ 'click .js-test-notifications'(e) { e.preventDefault(); KonchatNotification.notify({ - duration: $('input[name=desktopNotificationDuration]').val(), payload: { sender: { username: 'rocket.cat' }, }, title: TAPi18n.__('Desktop_Notification_Test'), diff --git a/app/ui-message/client/blocks/index.js b/app/ui-message/client/blocks/index.js index 5e332705c1d4..d081ff82163b 100644 --- a/app/ui-message/client/blocks/index.js +++ b/app/ui-message/client/blocks/index.js @@ -1,4 +1,10 @@ +import { HTML } from 'meteor/htmljs'; + import { createTemplateForComponent } from '../../../../client/reactAdapters'; -createTemplateForComponent('ModalBlock', () => import('./ModalBlock')); +createTemplateForComponent('ModalBlock', () => import('./ModalBlock'), { + // eslint-disable-next-line new-cap + renderContainerView: () => HTML.DIV({ class: 'rc-multiselect', style: 'display: flex; width:100%;' }), +}); + createTemplateForComponent('Blocks', () => import('./MessageBlock')); diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html index d2e0bef5ceb9..5269bcc5711e 100644 --- a/app/ui-message/client/message.html +++ b/app/ui-message/client/message.html @@ -176,11 +176,9 @@ {{/unless}} {{#if broadcast}} - {{#with msg.u}} - - {{/with}} + {{/if}} {{#unless hideReactions}}
    diff --git a/app/ui-sidenav/client/toolbar.js b/app/ui-sidenav/client/toolbar.js index 97b11cfa24f6..3c59a62a7496 100644 --- a/app/ui-sidenav/client/toolbar.js +++ b/app/ui-sidenav/client/toolbar.js @@ -35,19 +35,33 @@ const getFromServer = (cb, type) => { return false; } + let exactUser = null; + let exactRoom = null; + if (results.users[0] && results.users[0].username === currentFilter) { + exactUser = results.users.shift(); + } + if (results.rooms[0] && results.rooms[0].username === currentFilter) { + exactRoom = results.rooms.shift(); + } + const resultsFromServer = []; - resultsFromServer.push(...results.users.map((user) => ({ + const roomFilter = (room) => !resultsFromClient.find((item) => [item.rid, item._id].includes(room._id)); + const userMap = (user) => ({ _id: user._id, t: 'd', name: user.username, fname: user.name, - }))); + }); - resultsFromServer.push(...results.rooms.filter((room) => !resultsFromClient.find((item) => [item.rid, item._id].includes(room._id)))); + resultsFromServer.push(...results.users.map(userMap)); + resultsFromServer.push(...results.rooms.filter(roomFilter)); - if (resultsFromServer.length) { - cb(resultsFromClient.concat(resultsFromServer)); + if (resultsFromServer.length || exactUser || exactRoom) { + exactRoom = exactRoom ? [roomFilter(exactRoom)] : []; + exactUser = exactUser ? [userMap(exactUser)] : []; + const combinedResults = exactUser.concat(exactRoom, resultsFromClient, resultsFromServer); + cb(combinedResults); } }); }; diff --git a/app/ui/client/lib/notification.js b/app/ui/client/lib/notification.js index 2ad74e0a7dd1..98dd113ab002 100644 --- a/app/ui/client/lib/notification.js +++ b/app/ui/client/lib/notification.js @@ -44,7 +44,7 @@ export const KonchatNotification = { requireInteraction: getUserPreference(Meteor.userId(), 'desktopNotificationRequireInteraction'), }); - const notificationDuration = notification.duration - 0 || getUserPreference(Meteor.userId(), 'desktopNotificationDuration') - 0; + const notificationDuration = notification.duration - 0 || 10; if (notificationDuration > 0) { setTimeout(() => n.close(), notificationDuration * 1000); } diff --git a/app/videobridge/client/actionLink.js b/app/videobridge/client/actionLink.js index 666059649d32..c1955690ddfb 100644 --- a/app/videobridge/client/actionLink.js +++ b/app/videobridge/client/actionLink.js @@ -2,7 +2,7 @@ import { Session } from 'meteor/session'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import toastr from 'toastr'; -import { actionLinks } from '../../action-links'; +import { actionLinks } from '../../action-links/client'; import { Rooms } from '../../models'; actionLinks.register('joinJitsiCall', function(message, params, instance) { diff --git a/app/videobridge/server/actionLink.js b/app/videobridge/server/actionLink.js index 01d5baa2ff4f..e3e42bee4c33 100644 --- a/app/videobridge/server/actionLink.js +++ b/app/videobridge/server/actionLink.js @@ -1,4 +1,4 @@ -import { actionLinks } from '../../action-links'; +import { actionLinks } from '../../action-links/server'; actionLinks.register('joinJitsiCall', function(/* message, params*/) { diff --git a/app/videobridge/server/methods/bbb.js b/app/videobridge/server/methods/bbb.js index 04b1d6030ed1..b06045271d54 100644 --- a/app/videobridge/server/methods/bbb.js +++ b/app/videobridge/server/methods/bbb.js @@ -2,11 +2,11 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; import xml2js from 'xml2js'; -import BigBlueButtonApi from '../../../bigbluebutton'; +import BigBlueButtonApi from '../../../bigbluebutton/server'; import { settings } from '../../../settings'; import { Rooms, Users } from '../../../models'; import { saveStreamingOptions } from '../../../channel-settings'; -import { API } from '../../../api'; +import { API } from '../../../api/server'; const parser = new xml2js.Parser({ explicitRoot: true, diff --git a/client/admin/integrations/edit/EditIncomingWebhook.js b/client/admin/integrations/edit/EditIncomingWebhook.js index 2a6363d9f224..7fa24155c299 100644 --- a/client/admin/integrations/edit/EditIncomingWebhook.js +++ b/client/admin/integrations/edit/EditIncomingWebhook.js @@ -1,5 +1,5 @@ import React, { useMemo, useState } from 'react'; -import { Field, Box, Headline, Skeleton, Margins, Button } from '@rocket.chat/fuselage'; +import { Field, Box, Skeleton, Margins, Button } from '@rocket.chat/fuselage'; import { SuccessModal, DeleteWarningModal } from './EditIntegrationsPage'; import { useTranslation } from '../../../contexts/TranslationContext'; @@ -21,11 +21,11 @@ export default function EditIncomingWebhookWithData({ integrationId, ...props }) if (state === ENDPOINT_STATES.LOADING) { return - + - + - + ; } diff --git a/client/admin/integrations/edit/EditOutgoingWebhook.js b/client/admin/integrations/edit/EditOutgoingWebhook.js index 7c20820b2a82..ea03cd5fdeab 100644 --- a/client/admin/integrations/edit/EditOutgoingWebhook.js +++ b/client/admin/integrations/edit/EditOutgoingWebhook.js @@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react'; import { Field, Box, - Headline, Skeleton, Margins, Button, @@ -28,11 +27,11 @@ export default function EditOutgoingWebhookWithData({ integrationId, ...props }) if (state === ENDPOINT_STATES.LOADING) { return - + - + - + ; } diff --git a/client/admin/integrations/edit/OutgoingWebhookHistoryPage.js b/client/admin/integrations/edit/OutgoingWebhookHistoryPage.js index e931ade0b90d..63752ce098d8 100644 --- a/client/admin/integrations/edit/OutgoingWebhookHistoryPage.js +++ b/client/admin/integrations/edit/OutgoingWebhookHistoryPage.js @@ -1,4 +1,4 @@ -import { Button, ButtonGroup, Icon, Headline, Skeleton, Box, Accordion, Field, FieldGroup, Pagination } from '@rocket.chat/fuselage'; +import { Button, ButtonGroup, Icon, Skeleton, Box, Accordion, Field, FieldGroup, Pagination } from '@rocket.chat/fuselage'; import React, { useMemo, useCallback, useState, useEffect } from 'react'; import Page from '../../../components/basic/Page'; @@ -174,11 +174,11 @@ function HistoryContent({ data, state, onChange, ...props }) { if (!loadedData || state === ENDPOINT_STATES.LOADING) { return - + - + - + ; } diff --git a/client/admin/rooms/edit/EditRoom.js b/client/admin/rooms/edit/EditRoom.js deleted file mode 100644 index 3ba4b679a911..000000000000 --- a/client/admin/rooms/edit/EditRoom.js +++ /dev/null @@ -1,194 +0,0 @@ -import React, { useCallback, useState, useMemo } from 'react'; -import { Box, Headline, Button, Margins, TextInput, Skeleton, Field, ToggleSwitch, Divider, Icon, Callout } from '@rocket.chat/fuselage'; - -import { useTranslation } from '../../../contexts/TranslationContext'; -import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; -import { roomTypes } from '../../../../app/utils/client'; -import { useMethod } from '../../../contexts/ServerContext'; -import { usePermission } from '../../../contexts/AuthorizationContext'; -import NotAuthorizedPage from '../../NotAuthorizedPage'; -import { useEndpointAction } from '../../../hooks/useEndpointAction'; -import Page from '../../../components/basic/Page'; - -export function EditRoomContextBar({ rid }) { - const canViewRoomAdministration = usePermission('view-room-administration'); - return canViewRoomAdministration ? : ; -} - -function EditRoomWithData({ rid }) { - const [cache, setState] = useState(); - - const { data = {}, state, error } = useEndpointDataExperimental('rooms.adminRooms.getRoom', useMemo(() => ({ rid }), [rid, cache])); - - if (state === ENDPOINT_STATES.LOADING) { - return - - - - - - - ; - } - - if (state === ENDPOINT_STATES.ERROR) { - return error.message; - } - - return setState(new Date())}/>; -} - -function EditRoom({ room, onChange }) { - const t = useTranslation(); - - const [deleted, setDeleted] = useState(false); - const [newData, setNewData] = useState({}); - const [changeArchivation, setChangeArchivation] = useState(false); - - const canDelete = usePermission(`delete-${ room.t }`); - - const hasUnsavedChanges = useMemo(() => Object.values(newData).filter((current) => current === null).length < Object.keys(newData).length, [JSON.stringify(newData)]); - const saveQuery = useMemo(() => ({ rid: room._id, ...Object.fromEntries(Object.entries(newData).filter(([, value]) => value !== null)) }), [room._id, JSON.stringify(newData)]); - - const archiveSelector = room.archived ? 'unarchive' : 'archive'; - const archiveMessage = archiveSelector === 'archive' ? 'Room_has_been_archived' : 'Room_has_been_archived'; - const archiveQuery = useMemo(() => ({ rid: room._id, action: room.archived ? 'unarchive' : 'archive' }), [room.rid, changeArchivation]); - - const saveAction = useEndpointAction('POST', 'rooms.saveRoomSettings', saveQuery, t('Room_updated_successfully')); - const archiveAction = useEndpointAction('POST', 'rooms.changeArchivationState', archiveQuery, t(archiveMessage)); - - const updateType = (type) => () => (type === 'p' ? 'c' : 'p'); - const areEqual = (a, b) => a === b || !(a || b); - - const handleChange = (field, currentValue, getValue = (e) => e.currentTarget.value) => (e) => setNewData({ ...newData, [field]: areEqual(getValue(e), currentValue) ? null : getValue(e) }); - const handleSave = async () => { - await Promise.all([hasUnsavedChanges && saveAction(), changeArchivation && archiveAction()].filter(Boolean)); - onChange('update'); - }; - - const deleteRoom = useMethod('eraseRoom'); - - const handleDelete = useCallback(async () => { - await deleteRoom(room._id); - setDeleted(true); - }, [room]); - - const roomName = room.t === 'd' ? room.usernames.join(' x ') : roomTypes.getRoomName(room.t, { type: room.t, ...room }); - const roomType = newData.roomType ?? room.t; - const readOnly = newData.readOnly ?? !!room.ro; - const isArchived = changeArchivation ? !room.archived : !!room.archived; - const isDefault = newData.default ?? !!room.default; - const isFavorite = newData.favorite ?? !!room.favorite; - const isFeatured = newData.featured ?? !!room.featured; - - return e.preventDefault(), [])}> - - - {deleted && } - - - {t('Name')} - - - - - { room.t !== 'd' && <> - - {t('Owner')} - - {room.u?.username} - - - - {t('Topic')} - - - - - - - - - {t('Public')} - {t('All_users_in_the_channel_can_write_new_messages')} - - - - - - {t('Private')} - {t('Just_invited_people_can_access_this_channel')} - - - - - - - - - - {t('Collaborative')} - {t('All_users_in_the_channel_can_write_new_messages')} - - - !readOnly)}/> - - - {t('Read_only')} - {t('Only_authorized_users_can_write_new_messages')} - - - - - - - - - {t('Archived')} - setChangeArchivation(!changeArchivation)}/> - - - - - - - {t('Default')} - !isDefault)}/> - - - - - - - {t('Favorite')} - !isFavorite)}/> - - - - - - - {t('Featured')} - !isFeatured)}/> - - - - - - - - - - - - - - } - - - - - - - ; -} diff --git a/client/admin/users/EditUser.js b/client/admin/users/EditUser.js index 522f355928ae..14e8e699d6ac 100644 --- a/client/admin/users/EditUser.js +++ b/client/admin/users/EditUser.js @@ -1,5 +1,5 @@ import React, { useMemo, useState, useCallback } from 'react'; -import { Box, Skeleton, Field, Margins, Button } from '@rocket.chat/fuselage'; +import { Box, Field, Margins, Button } from '@rocket.chat/fuselage'; import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; @@ -9,21 +9,15 @@ import { useRoute } from '../../contexts/RouterContext'; import UserAvatarEditor from '../../components/basic/avatar/UserAvatarEditor'; import { useForm } from '../../hooks/useForm'; import UserForm from './UserForm'; +import { FormSkeleton } from './Skeleton'; -export function EditUserWithData({ userId, ...props }) { +export function EditUserWithData({ uid, ...props }) { const t = useTranslation(); const { data: roleData, state: roleState, error: roleError } = useEndpointDataExperimental('roles.list', '') || {}; - const { data, state, error } = useEndpointDataExperimental('users.info', useMemo(() => ({ userId }), [userId])); + const { data, state, error } = useEndpointDataExperimental('users.info', useMemo(() => ({ userId: uid }), [uid])); if ([state, roleState].includes(ENDPOINT_STATES.LOADING)) { - return - - - - - - - ; + return ; } if (error || roleError) { @@ -36,11 +30,12 @@ export function EditUserWithData({ userId, ...props }) { const getInitialValue = (data) => ({ roles: data.roles, name: data.name ?? '', + password: '', username: data.username, status: data.status, bio: data.bio ?? '', email: (data.emails && data.emails[0].address) || '', - emailVerified: (data.emails && data.emails[0].verified) || false, + verified: (data.emails && data.emails[0].verified) || false, setRandomPassword: false, requirePasswordChange: data.setRandomPassword || false, customFields: data.customFields ?? {}, diff --git a/client/admin/users/Skeleton.js b/client/admin/users/Skeleton.js new file mode 100644 index 000000000000..2fc5e9097c14 --- /dev/null +++ b/client/admin/users/Skeleton.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { Box, Skeleton } from '@rocket.chat/fuselage'; + +export const FormSkeleton = (props) => + + + + + + +; diff --git a/client/admin/users/UserInfo.js b/client/admin/users/UserInfo.js index bc1dd867be2c..32d570b18634 100644 --- a/client/admin/users/UserInfo.js +++ b/client/admin/users/UserInfo.js @@ -1,5 +1,5 @@ import React, { useMemo, useState, useEffect, useCallback } from 'react'; -import { Box, Avatar, Margins, Skeleton, Chip, Tag } from '@rocket.chat/fuselage'; +import { Box, Avatar, Margins, Chip, Tag } from '@rocket.chat/fuselage'; import moment from 'moment'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; @@ -9,6 +9,7 @@ import { DateFormat } from '../../../app/lib'; import { UserInfoActions } from './UserInfoActions'; import MarkdownText from '../../components/basic/MarkdownText'; import VerticalBar from '../../components/basic/VerticalBar'; +import { FormSkeleton } from './Skeleton'; const useTimezoneClock = (utcOffset = 0, updateInterval) => { const [time, setTime] = useState(); @@ -28,23 +29,16 @@ const UTCClock = ({ utcOffset, ...props }) => { return {time} UTC {utcOffset}; }; -export function UserInfoWithData({ userId, ...props }) { +export function UserInfoWithData({ uid, ...props }) { const t = useTranslation(); const [cache, setCache] = useState(); const onChange = () => setCache(new Date()); - const { data, state, error } = useEndpointDataExperimental('users.info', useMemo(() => ({ userId }), [userId, cache])); + const { data, state, error } = useEndpointDataExperimental('users.info', useMemo(() => ({ userId: uid }), [uid, cache])); if (state === ENDPOINT_STATES.LOADING) { - return - - - - - - - ; + return ; } if (error) { @@ -65,17 +59,17 @@ export function UserInfo({ data, onChange, ...props }) { const avatarUrl = roomTypes.getConfig('d').getAvatarPath({ name: data.username || data.name, type: 'd', _id: data._id }); return e.preventDefault(), [])} {...props}> - - + + - {data.name || data.username} - {!!data.name && @{data.username}} - {data.status} + {data.name || data.username} + {!!data.name && @{data.username}} + {data.status} - + {data.bio && data.bio.trim().length > 0 && {data.bio}} {!!data.roles.length && <> @@ -89,7 +83,7 @@ export function UserInfo({ data, onChange, ...props }) { {data.emails && <> {t('Email')} - {data.emails[0].address} + {data.emails[0].address} {data.emails[0].verified && {t('Verified')}} {data.emails[0].verified || {t('Not_verified')}} diff --git a/client/admin/users/UsersPage.js b/client/admin/users/UsersPage.js index f9060964b630..02904be6dc2a 100644 --- a/client/admin/users/UsersPage.js +++ b/client/admin/users/UsersPage.js @@ -56,8 +56,8 @@ function UsersPage() { - {context === 'info' && } - {context === 'edit' && } + {context === 'info' && } + {context === 'edit' && } {context === 'new' && } {context === 'invite' && } diff --git a/client/components/basic/VerticalBar.js b/client/components/basic/VerticalBar.js index 52e0dae7677b..f9ce374bcd9a 100644 --- a/client/components/basic/VerticalBar.js +++ b/client/components/basic/VerticalBar.js @@ -10,6 +10,7 @@ function VerticalBar({ children, ...props }) { return {children} diff --git a/client/importPackages.js b/client/importPackages.js index 183ed71b41fc..7d95bc6f83e7 100644 --- a/client/importPackages.js +++ b/client/importPackages.js @@ -102,7 +102,7 @@ import '../app/notifications'; import '../app/promises/client'; import '../app/ui-utils'; import '../app/ui-cached-collection'; -import '../app/action-links'; +import '../app/action-links/client'; import '../app/reactions/client'; import '../app/livechat/client'; import '../app/meteor-autocomplete/client'; diff --git a/ee/app/api-enterprise/server/canned-responses.js b/ee/app/api-enterprise/server/canned-responses.js index 36b1175406a1..99f41f2709c6 100644 --- a/ee/app/api-enterprise/server/canned-responses.js +++ b/ee/app/api-enterprise/server/canned-responses.js @@ -1,4 +1,4 @@ -import { API } from '../../../../app/api'; +import { API } from '../../../../app/api/server'; import { findAllCannedResponses } from './lib/canned-responses'; API.v1.addRoute('canned-responses.get', { authRequired: true }, { diff --git a/ee/app/engagement-dashboard/server/api/channels.js b/ee/app/engagement-dashboard/server/api/channels.js index 405161d3e0c6..e97dd3995df0 100644 --- a/ee/app/engagement-dashboard/server/api/channels.js +++ b/ee/app/engagement-dashboard/server/api/channels.js @@ -1,6 +1,6 @@ import { check } from 'meteor/check'; -import { API } from '../../../../../app/api'; +import { API } from '../../../../../app/api/server'; import { findAllChannelsWithNumberOfMessages } from '../lib/channels'; import { transformDatesForAPI } from './helpers/date'; diff --git a/ee/app/engagement-dashboard/server/api/messages.js b/ee/app/engagement-dashboard/server/api/messages.js index b51f393e0091..94b6428c011e 100644 --- a/ee/app/engagement-dashboard/server/api/messages.js +++ b/ee/app/engagement-dashboard/server/api/messages.js @@ -1,6 +1,6 @@ import { check } from 'meteor/check'; -import { API } from '../../../../../app/api'; +import { API } from '../../../../../app/api/server'; import { findWeeklyMessagesSentData, findMessagesSentOrigin, findTopFivePopularChannelsByMessageSentQuantity } from '../lib/messages'; import { transformDatesForAPI } from './helpers/date'; diff --git a/ee/app/engagement-dashboard/server/api/users.js b/ee/app/engagement-dashboard/server/api/users.js index aaf33b4c0f28..8a675146237a 100644 --- a/ee/app/engagement-dashboard/server/api/users.js +++ b/ee/app/engagement-dashboard/server/api/users.js @@ -1,6 +1,6 @@ import { check } from 'meteor/check'; -import { API } from '../../../../../app/api'; +import { API } from '../../../../../app/api/server'; import { findWeeklyUsersRegisteredData, findActiveUsersMonthlyData, diff --git a/ee/app/livechat-enterprise/server/lib/Helper.js b/ee/app/livechat-enterprise/server/lib/Helper.js index 00964271e230..d3c525e85066 100644 --- a/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/ee/app/livechat-enterprise/server/lib/Helper.js @@ -128,7 +128,7 @@ export const checkWaitingQueue = async (department) => { return processWaitingQueue(); } - return Promise.all(departments.forEach((department) => processWaitingQueue(department))); + return Promise.all(departments.map(async (department) => processWaitingQueue(department))); }; export const allowAgentSkipQueue = (agent) => { diff --git a/ee/app/livechat/imports/server/rest/departments.js b/ee/app/livechat/imports/server/rest/departments.js index 4ec594c9051e..1521dc7656a9 100644 --- a/ee/app/livechat/imports/server/rest/departments.js +++ b/ee/app/livechat/imports/server/rest/departments.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/inquiries.js b/ee/app/livechat/imports/server/rest/inquiries.js index 3c9e6c314414..cab34ca9b355 100644 --- a/ee/app/livechat/imports/server/rest/inquiries.js +++ b/ee/app/livechat/imports/server/rest/inquiries.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/rooms.js b/ee/app/livechat/imports/server/rest/rooms.js index 7d15bf85e2f9..76578268b288 100644 --- a/ee/app/livechat/imports/server/rest/rooms.js +++ b/ee/app/livechat/imports/server/rest/rooms.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/sms.js b/ee/app/livechat/imports/server/rest/sms.js index 982ddd7b2438..211f5e444a40 100644 --- a/ee/app/livechat/imports/server/rest/sms.js +++ b/ee/app/livechat/imports/server/rest/sms.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/upload.js b/ee/app/livechat/imports/server/rest/upload.js index 8f87096af200..e8c4ad4e81ba 100644 --- a/ee/app/livechat/imports/server/rest/upload.js +++ b/ee/app/livechat/imports/server/rest/upload.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/imports/personal-access-tokens/client/personalAccessTokens.html b/imports/personal-access-tokens/client/personalAccessTokens.html index b0e790ecd1c5..9234ee6b4f53 100644 --- a/imports/personal-access-tokens/client/personalAccessTokens.html +++ b/imports/personal-access-tokens/client/personalAccessTokens.html @@ -8,7 +8,7 @@
    -
    +
    @@ -20,8 +20,8 @@ {{> icon block="rc-select__arrow" icon="arrow-down"}}
    +
    -
    diff --git a/mocha_apps.opts b/mocha_apps.opts deleted file mode 100644 index 7a631df0013d..000000000000 --- a/mocha_apps.opts +++ /dev/null @@ -1,8 +0,0 @@ ---require babel-mocha-es6-compiler ---require babel-polyfill ---reporter spec ---ui bdd ---timeout 10000 ---bail ---file tests/end-to-end/teardown.js -tests/end-to-end/apps/*.js diff --git a/mocha_api.opts b/mocha_end_to_end.opts similarity index 73% rename from mocha_api.opts rename to mocha_end_to_end.opts index a30078e5ccfe..a48288c76568 100644 --- a/mocha_api.opts +++ b/mocha_end_to_end.opts @@ -5,4 +5,4 @@ --timeout 10000 --bail --file tests/end-to-end/teardown.js -tests/end-to-end/api/*.js +tests/end-to-end/api/*.js tests/end-to-end/apps/*.js diff --git a/package-lock.json b/package-lock.json index 7d72f3451d7f..9ec9af78bac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2797,9 +2797,9 @@ } }, "@rocket.chat/apps-engine": { - "version": "1.15.0-alpha.3394", - "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.15.0-alpha.3394.tgz", - "integrity": "sha512-f1ZrVHqxQS4C/jaM4ES8JIzVU1zgHNGEMuxXVMT1wbk1NnMt40Uw2fEWYV7Ijy3ttEJ35ekMSSV+3ctunsyq1A==", + "version": "1.15.0-beta.3411", + "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.15.0-beta.3411.tgz", + "integrity": "sha512-e1ddaAfjWXWGyb2tlW8eZHgg6sBHN73n52i8b62GfqSJtf1cIM9VhLA4igq8Anaai5UtcxmmhdAQgmt0xhnNuw==", "requires": { "adm-zip": "^0.4.9", "cryptiles": "^4.1.3", diff --git a/package.json b/package.json index fb1eaeeb38ac..55f6d62ff5cd 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,12 @@ "deploy": "npm run build && pm2 startOrRestart pm2.json", "postinstall": "node .scripts/npm-postinstall.js", "coverage": "nyc -r html mocha --opts ./mocha.opts", - "test": "node .scripts/start.js", + "testci": "node .scripts/start.js", "testui": "cypress run --project tests", + "testapi": "mocha --opts ./mocha_end_to_end.opts", "testunit": "mocha --opts ./mocha.opts", "testunit-watch": "mocha --watch --opts ./mocha.opts", - "testapi": "mocha --opts ./mocha_api.opts", - "testapps": "mocha --opts ./mocha_apps.opts", - "testci": "npm run testapi && npm run testapps && npm run testui", + "test": "npm run testapi && npm run testui", "translation-diff": "node .scripts/translationDiff.js", "translation-fix-order": "node .scripts/fix-i18n.js", "version": "node .scripts/version.js", @@ -126,7 +125,7 @@ "@nivo/heatmap": "^0.61.0", "@nivo/line": "^0.61.1", "@nivo/pie": "^0.61.1", - "@rocket.chat/apps-engine": "1.15.0-alpha.3394", + "@rocket.chat/apps-engine": "1.15.0-beta.3411", "@rocket.chat/fuselage": "^0.6.3-dev.45", "@rocket.chat/fuselage-hooks": "^0.6.3-dev.35", "@rocket.chat/fuselage-polyfills": "^0.6.3-dev.45", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 359c65494b51..66795f3b3dd2 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1045,6 +1045,7 @@ "CRM_Integration": "CRM Integration", "CROWD_Allow_Custom_Username": "Allow custom username in Rocket.Chat", "CROWD_Reject_Unauthorized": "Reject Unauthorized", + "Crowd_Remove_Orphaned_Users": "Remove Orphaned Users", "Crowd_sync_interval_Description": "The interval between synchronizations. Example `every 24 hours` or `on the first day of the week`, more examples at [Cron Text Parser](http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Current Chats", "Current_File": "Current File", @@ -1238,7 +1239,6 @@ "Duplicated_Email_address_will_be_ignored": "Duplicated email address will be ignored.", "Duplicate_file_name_found": "Duplicate file name found.", "Duplicate_private_group_name": "A Private Group with name '%s' exists", - "Duration": "Duration", "E2E Encryption": "E2E Encryption", "E2E_Enabled": "E2E Enabled", "E2E_Enable_alert": "This feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues and be aware of:
    - Encrypted messages of encrypted rooms will not be found by search operations.
    - The mobile apps may not support the encypted messages (they are implementing it).
    - Bots may not be able to see encrypted messages until they implement support for it.
    - Uploads will not be encrypted in this version.", @@ -2499,7 +2499,7 @@ "No_Limit": "No Limit", "No_available_agents_to_transfer": "No available agents to transfer", "No_channel_with_name_%s_was_found": "No channel with name \"%s\" was found!", - "No_channels_yet": "You aren't part of any channel yet", + "No_channels_yet": "You aren't part of any channels yet", "No_direct_messages_yet": "No Direct Messages.", "No_emojis_found": "No emojis found", "No_Encryption": "No Encryption", @@ -2536,12 +2536,10 @@ "Nothing_found": "Nothing found", "Not_Imported_Messages_Title": "The following messages were not imported successfully", "Notification_Desktop_Default_For": "Show Desktop Notifications For", - "Notification_Duration": "Notification Duration", "Notification_RequireInteraction": "Require Interaction to Dismiss Desktop Notification", "Notification_RequireInteraction_Description": "Works only with Chrome browser versions > 50. Utilizes the parameter requireInteraction to show the desktop notification to indefinite until the user interacts with it.", "Notification_Mobile_Default_For": "Push Mobile Notifications For", "Notifications": "Notifications", - "Notifications_Duration": "Notifications Duration", "Notifications_Max_Room_Members": "Max Room Members Before Disabling All Message Notifications", "Notifications_Max_Room_Members_Description": "Max number of members in room when notifications for all messages gets disabled. Users can still change per room setting to receive all notifications on an individual basis. (0 to disable)", "Notifications_Muted_Description": "If you choose to mute everything, you won't see the room highlight in the list when there are new messages, except for mentions. Muting notifications will override notifications settings.", @@ -3479,6 +3477,7 @@ "Unarchive": "Unarchive", "unarchive-room": "Unarchive Room", "unarchive-room_description": "Permission to unarchive channels", + "Unavailable": "Unavailable", "Unblock_User": "Unblock User", "Uncheck_All": "Uncheck All", "Undefined": "Undefined", @@ -3813,4 +3812,4 @@ "Your_server_link": "Your server link", "Your_temporary_password_is_password": "Your temporary password is [password].", "Your_workspace_is_ready": "Your workspace is ready to use 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index cf93796c15a2..7c96af8a2699 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -831,6 +831,7 @@ "Created_at_s_by_s_triggered_by_s": "Creato alle %s da %s scatenato da %s", "CRM_Integration": "Integrazione CRM", "CROWD_Reject_Unauthorized": "Rifiuta non autorizzati", + "Crowd_Remove_Orphaned_Users": "Rimuovi utenti orfani", "Crowd_sync_interval_Description": "L'intervallo tra le sincronizzazioni. Esempio \"ogni 24 ore\" o \"il primo giorno della settimana\", altri esempi su [Cron Text Parser] (http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Chat attuali", "Current_Status": "Stato attuale", @@ -2888,4 +2889,4 @@ "Your_push_was_sent_to_s_devices": "La tua richiesta è stata inviata ai %s dispositivi.", "Your_server_link": "Il tuo collegamento al server", "Your_workspace_is_ready": "Il tuo spazio di lavoro è pronto per l'uso 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 81d4439d78a9..f4bdd6bf0d33 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -1000,6 +1000,7 @@ "CRM_Integration": "Integração de CRM", "CROWD_Allow_Custom_Username": "Permitir nome de usuário personalizado no Rocket.Chat", "CROWD_Reject_Unauthorized": "Rejeitar não autorizado", + "Crowd_Remove_Orphaned_Users": "Remover usuários órfãos", "Crowd_sync_interval_Description": "O intervalo entre as sincronizações. Exemplo de \"todas as 24 horas\" ou \"no primeiro dia da semana\", mais exemplos em [Cron Text Parser] (http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Bate-papos atuais", "Current_File": "Arquivo atual", @@ -3122,6 +3123,7 @@ "Unarchive": "Desarquivar", "unarchive-room": "Desarquivar Sala", "unarchive-room_description": "Permissão para desarchivar canais", + "Unavailable": "Indisponível", "Unblock_User": "Desbloquear Usuário", "Undefined": "Não definido", "Unfavorite": "Remover dos Favoritos", diff --git a/server/importPackages.js b/server/importPackages.js index 96073ce4fa3f..aa56233f2bf5 100644 --- a/server/importPackages.js +++ b/server/importPackages.js @@ -1,14 +1,14 @@ import '../app/cors/server'; import '../app/sms'; import '../app/2fa/server'; -import '../app/accounts'; +import '../app/accounts/server'; import '../app/analytics/server'; -import '../app/api'; -import '../app/assets'; +import '../app/api/server'; +import '../app/assets/server'; import '../app/authorization'; import '../app/autolinker/server'; import '../app/autotranslate/server'; -import '../app/bot-helpers'; +import '../app/bot-helpers/server'; import '../app/cas/server'; import '../app/channel-settings'; import '../app/channel-settings-mail-messages/server'; @@ -98,7 +98,7 @@ import '../app/version-check/server'; import '../app/search/server'; import '../app/chatpal-search/server'; import '../app/discussion/server'; -import '../app/bigbluebutton'; +import '../app/bigbluebutton/server'; import '../app/mail-messages/server'; import '../app/user-status'; import '../app/utils'; @@ -109,6 +109,6 @@ import '../app/callbacks'; import '../app/notifications'; import '../app/promises/server'; import '../app/ui-utils'; -import '../app/action-links'; +import '../app/action-links/server'; import '../app/reactions/server'; import '../app/livechat/server'; diff --git a/server/methods/saveUserPreferences.js b/server/methods/saveUserPreferences.js index 6d24205a6666..4feee329219a 100644 --- a/server/methods/saveUserPreferences.js +++ b/server/methods/saveUserPreferences.js @@ -22,7 +22,6 @@ Meteor.methods({ mobileNotifications: Match.Optional(String), enableAutoAway: Match.Optional(Boolean), highlights: Match.Optional([String]), - desktopNotificationDuration: Match.Optional(Number), messageViewMode: Match.Optional(Number), hideUsernames: Match.Optional(Boolean), hideRoles: Match.Optional(Boolean), diff --git a/server/publications/spotlight.js b/server/publications/spotlight.js index 0da321b49dfd..702ed843d139 100644 --- a/server/publications/spotlight.js +++ b/server/publications/spotlight.js @@ -19,7 +19,7 @@ function fetchRooms(userId, rooms) { } Meteor.methods({ - spotlight(text, usernames, type = { users: true, rooms: true }, rid) { + spotlight(text, usernames = [], type = { users: true, rooms: true }, rid) { const searchForChannels = text[0] === '#'; const searchForDMs = text[0] === '@'; if (searchForChannels) { @@ -72,7 +72,12 @@ Meteor.methods({ if (hasPermission(userId, 'view-outside-room')) { if (type.users === true && hasPermission(userId, 'view-d-room')) { - result.users = Users.findByActiveUsersExcept(text, usernames, userOptions).fetch(); + const exactUser = Users.findOneByUsernameIgnoringCase(text, userOptions); + if (exactUser && !usernames.includes(exactUser.username)) { + result.users.push(exactUser); + usernames.push(exactUser.username); + } + result.users = result.users.concat(Users.findByActiveUsersExcept(text, usernames, userOptions).fetch()); } if (type.rooms === true && hasPermission(userId, 'view-c-room')) { @@ -81,7 +86,13 @@ Meteor.methods({ .map((roomType) => roomType[0]); const roomIds = Subscriptions.findByUserIdAndTypes(userId, searchableRoomTypes, { fields: { rid: 1 } }).fetch().map((s) => s.rid); - result.rooms = fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch()); + const exactRoom = Rooms.findOneByNameAndType(text, searchableRoomTypes, roomOptions); + if (exactRoom) { + result.exactRoom.push(exactRoom); + roomIds.push(exactRoom.rid); + } + + result.rooms = result.rooms.concat(fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch())); } } else if (type.users === true && rid) { const subscriptions = Subscriptions.find({ diff --git a/server/publications/subscription/index.js b/server/publications/subscription/index.js index 8001eb21eeaf..59a080202fdc 100644 --- a/server/publications/subscription/index.js +++ b/server/publications/subscription/index.js @@ -24,7 +24,6 @@ export const fields = { audioNotifications: 1, audioNotificationValue: 1, desktopNotifications: 1, - desktopNotificationDuration: 1, mobilePushNotifications: 1, emailNotifications: 1, unreadAlert: 1, diff --git a/server/startup/migrations/index.js b/server/startup/migrations/index.js index c32aef2646df..d4353ab9d79c 100644 --- a/server/startup/migrations/index.js +++ b/server/startup/migrations/index.js @@ -187,4 +187,5 @@ import './v187'; import './v188'; import './v189'; import './v190'; +import './v191'; import './xrun'; diff --git a/server/startup/migrations/v036.js b/server/startup/migrations/v036.js index a4f97e7bf80f..ba22dbdfa8db 100644 --- a/server/startup/migrations/v036.js +++ b/server/startup/migrations/v036.js @@ -5,7 +5,7 @@ import { HTTP } from 'meteor/http'; import { Migrations } from '../../../app/migrations'; import { Settings } from '../../../app/models'; -import { RocketChatAssets } from '../../../app/assets'; +import { RocketChatAssets } from '../../../app/assets/server'; Migrations.add({ version: 36, diff --git a/server/startup/migrations/v042.js b/server/startup/migrations/v042.js index dc95a49cbae2..d17a90026692 100644 --- a/server/startup/migrations/v042.js +++ b/server/startup/migrations/v042.js @@ -2,7 +2,7 @@ import { Mongo } from 'meteor/mongo'; import { Migrations } from '../../../app/migrations'; import { settings } from '../../../app/settings'; -import { RocketChatAssets } from '../../../app/assets'; +import { RocketChatAssets } from '../../../app/assets/server'; Migrations.add({ version: 42, diff --git a/server/startup/migrations/v105.js b/server/startup/migrations/v105.js index d68b1bdda5e4..39615b89bf79 100644 --- a/server/startup/migrations/v105.js +++ b/server/startup/migrations/v105.js @@ -15,7 +15,6 @@ Migrations.add({ Desktop_Notifications_Default_Alert: 'Accounts_Default_User_Preferences_desktopNotifications', Mobile_Notifications_Default_Alert: 'Accounts_Default_User_Preferences_mobileNotifications', Audio_Notifications_Default_Alert: 'Accounts_Default_User_Preferences_audioNotifications', - Desktop_Notifications_Duration: 'Accounts_Default_User_Preferences_desktopNotificationDuration', Audio_Notifications_Value: undefined, }; Settings.find({ _id: { $in: Object.keys(settingsMap) } }).forEach((oldSetting) => { diff --git a/server/startup/migrations/v190.js b/server/startup/migrations/v190.js index 33fe26570579..8c4da9b206b2 100644 --- a/server/startup/migrations/v190.js +++ b/server/startup/migrations/v190.js @@ -1,9 +1,19 @@ -import { Migrations } from '../../../app/migrations/server'; -import { Settings } from '../../../app/models/server'; +import { Migrations } from '../../../app/migrations'; +import { Settings, Subscriptions } from '../../../app/models/server/raw'; Migrations.add({ version: 190, up() { - Settings.remove({ _id: /theme-color-status/ }, { multi: true }); + // Remove unused settings + Promise.await(Settings.col.deleteOne({ _id: 'Accounts_Default_User_Preferences_desktopNotificationDuration' })); + Promise.await(Subscriptions.col.updateMany({ + desktopNotificationDuration: { + $exists: true, + }, + }, { + $unset: { + desktopNotificationDuration: 1, + }, + })); }, }); diff --git a/server/startup/migrations/v191.js b/server/startup/migrations/v191.js new file mode 100644 index 000000000000..10f5af6a62ce --- /dev/null +++ b/server/startup/migrations/v191.js @@ -0,0 +1,9 @@ +import { Migrations } from '../../../app/migrations/server'; +import { Settings } from '../../../app/models/server'; + +Migrations.add({ + version: 191, + up() { + Settings.remove({ _id: /theme-color-status/ }, { multi: true }); + }, +}); diff --git a/tests/cypress/integration/11-admin.js b/tests/cypress/integration/11-admin.js index 333d54514ed1..51fc46af08e1 100644 --- a/tests/cypress/integration/11-admin.js +++ b/tests/cypress/integration/11-admin.js @@ -683,15 +683,6 @@ describe('[Administration]', () => { admin.accountsidleTimeLimit.should('have.value', '300'); }); - it('it should show the notifications durations field', () => { - admin.accountsNotificationDuration.click(); - admin.accountsNotificationDuration.should('be.visible'); - }); - - it('the notification duration field value should be 0', () => { - admin.accountsNotificationDuration.should('have.value', '0'); - }); - it('it should show the audio notifications select field', () => { admin.accountsAudioNotifications.scrollIntoView(); admin.accountsAudioNotifications.should('be.visible'); diff --git a/tests/cypress/pageobjects/administration.page.js b/tests/cypress/pageobjects/administration.page.js index d2d30530be67..b34f3e83f2b2 100644 --- a/tests/cypress/pageobjects/administration.page.js +++ b/tests/cypress/pageobjects/administration.page.js @@ -231,10 +231,6 @@ class Administration extends Page { get accountsidleTimeLimitReset() { return browser.element('[data-qa-reset-setting-id="Accounts_Default_User_Preferences_idleTimeLimit"]'); } - get accountsNotificationDuration() { return browser.element('[data-qa-setting-id="Accounts_Default_User_Preferences_desktopNotificationDuration"]'); } - - get accountsNotificationDurationReset() { return browser.element('[data-qa-reset-setting-id="Accounts_Default_User_Preferences_desktopNotificationDuration"]'); } - get accountsAudioNotifications() { return browser.element('[data-qa-setting-id="Accounts_Default_User_Preferences_audioNotifications"]'); } get accountsAudioNotificationsReset() { return browser.element('[data-qa-reset-setting-id="Accounts_Default_User_Preferences_audioNotifications"]'); } diff --git a/tests/data/user.js b/tests/data/user.js index 02d805b138d8..6fb74b9862f7 100644 --- a/tests/data/user.js +++ b/tests/data/user.js @@ -23,7 +23,6 @@ export const preferences = { mobileNotifications: 'default', enableAutoAway: true, highlights: [], - desktopNotificationDuration: 0, desktopNotificationRequireInteraction: false, messageViewMode: 0, hideUsernames: false, diff --git a/tests/end-to-end/api/00-miscellaneous.js b/tests/end-to-end/api/00-miscellaneous.js index a514b3cd7b7c..9ddb510d9704 100644 --- a/tests/end-to-end/api/00-miscellaneous.js +++ b/tests/end-to-end/api/00-miscellaneous.js @@ -126,7 +126,6 @@ describe('miscellaneous', function() { 'mobileNotifications', 'enableAutoAway', // 'highlights', - 'desktopNotificationDuration', 'desktopNotificationRequireInteraction', 'messageViewMode', 'hideUsernames', diff --git a/tests/end-to-end/api/09-rooms.js b/tests/end-to-end/api/09-rooms.js index 841b9b0a4b88..38fa9a246146 100644 --- a/tests/end-to-end/api/09-rooms.js +++ b/tests/end-to-end/api/09-rooms.js @@ -58,7 +58,6 @@ describe('[Rooms]', function() { emailNotifications: 'nothing', audioNotificationValue: 'beep', desktopNotifications: 'nothing', - desktopNotificationDuration: '2', audioNotifications: 'all', mobilePushNotifications: 'mentions', },