From c9a715410ed55a8d26082f7e792ee524b948e5b5 Mon Sep 17 00:00:00 2001 From: shedoev Date: Thu, 22 Oct 2020 02:54:19 +0800 Subject: [PATCH] =?UTF-8?q?#703=20=D0=92=D1=8B=D0=B1=D0=BE=D1=80=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D1=82=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/server/v1/users.js | 19 ++ .../client/views/MailForm.js | 169 ++++++++++++------ app/manual-mail-sender/client/views/index.js | 113 ++++++------ app/manual-mail-sender/client/views/lib.js | 27 --- package-lock.json | 19 ++ package.json | 1 + packages/rocketchat-i18n/i18n/ru.i18n.json | 2 +- 7 files changed, 209 insertions(+), 141 deletions(-) delete mode 100644 app/manual-mail-sender/client/views/lib.js 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.
",