diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js
index e25d023b88d4..5398a697c9e0 100644
--- a/app/api/server/v1/users.js
+++ b/app/api/server/v1/users.js
@@ -244,6 +244,25 @@ API.v1.addRoute('users.list', { authRequired: true }, {
},
});
+API.v1.addRoute('users.all', { authRequired: true }, {
+ get() {
+ if (!hasPermission(this.userId, 'view-d-room')) {
+ return API.v1.unauthorized();
+ }
+
+ const { sort, fields, query } = this.parseJsonQuery();
+
+ const users = Users.find(query, {
+ sort: sort || { username: 1 },
+ fields,
+ }).fetch();
+
+ return API.v1.success({
+ users
+ });
+ },
+});
+
API.v1.addRoute('users.register', { authRequired: false }, {
post() {
if (this.userId) {
diff --git a/app/manual-mail-sender/client/views/MailForm.js b/app/manual-mail-sender/client/views/MailForm.js
index 974432181f39..5810a4cf6ffc 100644
--- a/app/manual-mail-sender/client/views/MailForm.js
+++ b/app/manual-mail-sender/client/views/MailForm.js
@@ -1,10 +1,14 @@
-import { Box, Button, ButtonGroup, Field, Margins, TextInput, InputBox, Chip } from '@rocket.chat/fuselage';
-import React, { useMemo, useState } from 'react';
+import { Box, Button, ButtonGroup, Chip, Field, InputBox, Margins, Scrollable, TextInput } from '@rocket.chat/fuselage';
+import React, { Component, useMemo, useState } from 'react';
+import 'react-dropdown-tree-select/dist/styles.css'
+import '../../public/stylesheets/mail-sender.css'
import CKEditor from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import '@ckeditor/ckeditor5-build-classic/build/translations/ru';
-import { useUniqueId } from '@rocket.chat/fuselage-hooks';
+import DropdownTreeSelect from 'react-dropdown-tree-select';
+import isEqual from 'lodash.isequal';
+import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useMethod } from '../../../../client/contexts/ServerContext';
import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
@@ -19,7 +23,44 @@ function packData(data, files) {
return dataToSend;
}
-function MailForm() {
+class DropdownTreeSelectContainer extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { data: props.data }
+ }
+
+ componentWillReceiveProps = (nextProps) => {
+ if (!isEqual(nextProps.data, this.state.data)) {
+ this.setState({data: nextProps.data})
+ }
+ }
+
+ shouldComponentUpdate = (nextProps) => {
+ return !isEqual(nextProps.data, this.state.data)
+ }
+
+ render() {
+ const { data, ...rest } = this.props;
+ return (
+
+ )
+ }
+}
+
+const getEmails = (obj, path) => {
+ let emails = '';
+ obj.forEach(node => {
+ if (!node.children && node.path.startsWith(path)) {
+ emails += node.value + ',';
+ }
+ if (node.children) {
+ emails += getEmails(node.children, path)
+ }
+ })
+ return emails;
+}
+
+function MailForm({ recipients }) {
const [newData, setNewData] = useState({
email: { value: '', required: true },
topic: { value: '', required: true },
@@ -76,8 +117,6 @@ function MailForm() {
dispatchToastMessage({ type: 'success', message: t('Integrations_Outgoing_Type_SendMessage') });
} catch (e) {
- console.error('[MailForm.js].handleSubmit e:', e);
-
const { invalidMails } = e.details;
dispatchToastMessage({ type: 'error', message: `${ t('Mail_Message_Invalid_emails') }: ${ invalidMails }` });
@@ -86,59 +125,73 @@ function MailForm() {
}
};
- return
-
-
-
-
- {t('Email_receivers')} *
-
-
-
-
-
- {t('Email_subject')} *
-
-
-
-
-
- {t('Email_body')} *
-
- {
- const data = editor.getData();
- setNewData({ ...newData, message: { value: data, required: newData.message.required } });
- } }
- />
-
-
-
- {t('Add_files')}
-
-
-
- {files?.length > 0 &&
-
- {files.map((file, i) => {file.filename})}
-
- }
-
+ $('.main-content').removeClass('rc-old');
+
+ const handleDropdownTreeSelectChange = (currentNode, selectedNodes) => {
+ let emails = '';
+ selectedNodes.forEach(item => {
+ emails += getEmails(recipients, item.path)
+ })
+ setNewData( { ...newData, email: { value: emails, required: newData.email.required } });
+ };
+
+ return <>
+
+ {t('Email_receivers')} *
+
+
+
+
+
+ {t('Email_subject')} *
+
+
+
+
+
+ {t('Email_body')} *
+
+ {
+ const data = editor.getData();
+ setNewData({ ...newData, message: { value: data, required: newData.message.required } });
+ } }
+ />
+
+
+
+ {t('Add_files')}
+
+
+
+ {files?.length > 0 &&
+
+ {files.map((file, i) => {file.filename})}
-
-
-
-
-
- ;
+ }
+
+
+
+
+ >;
}
export default MailForm;
diff --git a/app/manual-mail-sender/client/views/index.js b/app/manual-mail-sender/client/views/index.js
index 817bdab5b107..26647fa9a188 100644
--- a/app/manual-mail-sender/client/views/index.js
+++ b/app/manual-mail-sender/client/views/index.js
@@ -1,72 +1,75 @@
-import React, { useCallback, useMemo, useState } from 'react';
-import { Button, Icon } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useMediaQuery } from '@rocket.chat/fuselage-hooks';
+import React, {useMemo, useState, useEffect} from 'react';
import '../../public/stylesheets/mail-sender.css';
import Page from '../../../../client/components/basic/Page';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
-import MailForm from './MailForm';
-import { useRoute, useRouteParameter } from '../../../../client/contexts/RouterContext';
+import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { useEndpointData } from '../../../../client/hooks/useEndpointData';
+import MailForm from './MailForm';
const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1);
-export const useQuery = (params, sort, cache) => useMemo(() => ({
- query: JSON.stringify({ desc: { $regex: params.text || '', $options: 'i' } }),
- sort: JSON.stringify({ [sort[0]]: sortDir(sort[1]) }),
- ...params.itemsPerPage && { count: params.itemsPerPage },
- ...params.current && { offset: params.current },
-}), [JSON.stringify(params), JSON.stringify(sort), cache]);
+const useQuery = (params, sort) => useMemo(() => ({
+ fields: JSON.stringify({ name: 1, username: 1, emails: 1 }),
+ query: JSON.stringify({
+ $or: [
+ { 'emails.address': { $regex: params.text || '', $options: 'i' } },
+ { username: { $regex: params.text || '', $options: 'i' } },
+ { name: { $regex: params.text || '', $options: 'i' } },
+ ],
+ }),
+ sort: JSON.stringify({ [sort[0]]: sortDir(sort[1]), usernames: sort[0] === 'name' ? sortDir(sort[1]) : undefined }),
+}), [JSON.stringify(params), JSON.stringify(sort)]);
+
+const assignObjectPaths = (obj, stack) => {
+ const isArray = Array.isArray(obj);
+ Object.keys(obj).forEach(k => {
+ const node = obj[k];
+ const key = isArray ? `[${k}]` : k;
+ if (typeof node === 'object') {
+ node.path = stack ? `${stack}.${key}` : key;
+ assignObjectPaths(node, node.path);
+ }
+ })
+}
export function MailSenderPage() {
const t = useTranslation();
- const routeName = 'councils';
-
- const [params, setParams] = useState({ current: 0, itemsPerPage: 25 });
- const [sort, setSort] = useState(['d', 'desc']);
- const [cache, setCache] = useState();
+ const [params, setParams] = useState({ text: '' });
+ const [sort, setSort] = useState(['name', 'asc']);
const debouncedParams = useDebouncedValue(params, 500);
const debouncedSort = useDebouncedValue(sort, 500);
-
- const query = useQuery(debouncedParams, debouncedSort, cache);
-
- const data = useEndpointData('councils.list', query) || { result: [] };
-
- const router = useRoute(routeName);
-
- const mobile = useMediaQuery('(max-width: 420px)');
- const small = useMediaQuery('(max-width: 780px)');
-
- const onEditClick = (_id) => () => {
- router.push({
- context: 'edit',
- id: _id,
- });
- };
-
- const onHeaderClick = (id) => {
- const [sortBy, sortDirection] = sort;
-
- if (sortBy === id) {
- setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']);
- return;
- }
- setSort([id, 'asc']);
- };
-
- const handleHeaderButtonClick = useCallback((context) => () => {
- router.push({ context });
- }, [router]);
-
- return
-
-
-
-
-
-
-
+ const query = useQuery(debouncedParams, debouncedSort);
+
+ let data = useEndpointData('users.all', query) || {};
+
+ const [recipients, setRecipients] = useState([]);
+
+ useEffect(() => {
+ const users = data.users || [];
+ const recipients = [{
+ label: 'Все пользователи',
+ value: 'all_users',
+ children: users.map((value) => {
+ return {
+ label: value.name,
+ value: value.emails ? value.emails[0].address : '',
+ }
+ })
+ }]
+
+ assignObjectPaths(recipients);
+
+ setRecipients(recipients);
+ }, [data])
+
+ return
+
+
+
+
+
;
}
diff --git a/app/manual-mail-sender/client/views/lib.js b/app/manual-mail-sender/client/views/lib.js
deleted file mode 100644
index 98c0b28a2646..000000000000
--- a/app/manual-mail-sender/client/views/lib.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Here previousData will define if it is an update or a new entry
-export function validate(councilData) {
- const errors = [];
-
- if (!councilData.d) {
- errors.push('Date');
- }
-
- if (!councilData.desc) {
- errors.push('Description');
- }
-
- return errors;
-}
-
-export function createCouncilData(date, description = '', previousData) {
- const councilData = {
- };
-
- if (previousData) {
- councilData._id = previousData._id;
- }
- councilData.d = date;
- councilData.desc = description;
-
- return councilData;
-}
diff --git a/package-lock.json b/package-lock.json
index e06333a4fd5a..bc37a21684b5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9752,6 +9752,11 @@
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
"dev": true
},
+ "array.partial": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/array.partial/-/array.partial-1.0.5.tgz",
+ "integrity": "sha512-nkHH1dU6JXrwppCqdUD5M1R85vihgBqhk9miq+3WFwwRayNY1ggpOT6l99PppqYQ1Hcjv2amFfUzhe25eAcYfA=="
+ },
"array.prototype.flat": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
@@ -28884,6 +28889,15 @@
"prop-types": "^15.6.0"
}
},
+ "react-dropdown-tree-select": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/react-dropdown-tree-select/-/react-dropdown-tree-select-2.4.1.tgz",
+ "integrity": "sha512-LTC4D3hzUP3h7aj4Gs8WPXzVFCJIkfA544Mr+YHRO1IVtes5FX4Dkm6gK5tHlACt2Nq4cw9bseY7a9VLI/Ru3A==",
+ "requires": {
+ "array.partial": "^1.0.5",
+ "react-infinite-scroll-component": "^4.0.2"
+ }
+ },
"react-error-overlay": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
@@ -28932,6 +28946,11 @@
"prop-types": "^15.6.1"
}
},
+ "react-infinite-scroll-component": {
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-4.5.3.tgz",
+ "integrity": "sha512-8O0PIeYZx0xFVS1ChLlLl/1obn64vylzXeheLsm+t0qUibmet7U6kDaKFg6jVRQJwDikWBTcyqEFFsxrbFCO5w=="
+ },
"react-input-autosize": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz",
diff --git a/package.json b/package.json
index 33672df2c757..81a50c77a764 100644
--- a/package.json
+++ b/package.json
@@ -233,6 +233,7 @@
"react-keyed-flatten-children": "^1.3.0",
"react-window": "^1.8.5",
"react-window-infinite-loader": "^1.0.5",
+ "react-dropdown-tree-select": "^2.4.1",
"redis": "^2.8.0",
"semver": "^5.7.1",
"sharp": "^0.22.1",
diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json
index a95c7ccef137..06a09c70eeb7 100755
--- a/packages/rocketchat-i18n/i18n/ru.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ru.i18n.json
@@ -1266,7 +1266,7 @@
"Email": "Электронная почта",
"Email_address_to_send_offline_messages": "Адрес Email для отправки сообщения в режиме офлайн",
"Email_already_exists": "Такой адрес электронной почты уже используется",
- "Email_body": "Тело письма",
+ "Email_body": "Текст письма",
"Email_receivers": "Получатели",
"Email_Change_Disabled": "Администратор вашего Rocket.Chat запретил изменение адреса электронной почты",
"Email_Changed_Description": "Вы можете использовать следующие подстановки:
- [email] для электронной почты получателя.
- [Site_Name] и [Site_URL] для названия приложения и его URL.
",