diff --git a/app/api/server/v1/channels.js b/app/api/server/v1/channels.js index 76af348d9a6c3..baf85dac47813 100644 --- a/app/api/server/v1/channels.js +++ b/app/api/server/v1/channels.js @@ -188,7 +188,7 @@ function createChannelValidator(params) { function createChannel(userId, params) { const readOnly = typeof params.readOnly !== 'undefined' ? params.readOnly : false; - const id = Meteor.runAsUser(userId, () => Meteor.call('createChannel', params.name, params.members ? params.members : [], readOnly, params.customFields)); + const id = Meteor.runAsUser(userId, () => Meteor.call('createChannel', params.name, params.members ? params.members : [], readOnly, params.customFields, params.extraData)); return { channel: findChannelByIdOrName({ params: { roomId: id.rid }, userId: this.userId }), diff --git a/app/api/server/v1/groups.js b/app/api/server/v1/groups.js index ba5002f8bbb42..c3f41db768f77 100644 --- a/app/api/server/v1/groups.js +++ b/app/api/server/v1/groups.js @@ -223,12 +223,16 @@ API.v1.addRoute('groups.create', { authRequired: true }, { if (this.bodyParams.customFields && !(typeof this.bodyParams.customFields === 'object')) { return API.v1.failure('Body param "customFields" must be an object if provided'); } + if (this.bodyParams.extraData && !(typeof this.bodyParams.extraData === 'object')) { + return API.v1.failure('Body param "extraData" must be an object if provided'); + } const readOnly = typeof this.bodyParams.readOnly !== 'undefined' ? this.bodyParams.readOnly : false; let id; + Meteor.runAsUser(this.userId, () => { - id = Meteor.call('createPrivateGroup', this.bodyParams.name, this.bodyParams.members ? this.bodyParams.members : [], readOnly, this.bodyParams.customFields); + id = Meteor.call('createPrivateGroup', this.bodyParams.name, this.bodyParams.members ? this.bodyParams.members : [], readOnly, this.bodyParams.customFields, this.bodyParams.extraData); }); return API.v1.success({ diff --git a/app/lib/server/functions/createRoom.js b/app/lib/server/functions/createRoom.js index 3d5d4ca886eef..8bc4c170b67f2 100644 --- a/app/lib/server/functions/createRoom.js +++ b/app/lib/server/functions/createRoom.js @@ -91,7 +91,6 @@ export const createRoom = function(type, name, owner, members = [], readOnly, ex if (type === 'c') { callbacks.run('beforeCreateChannel', owner, room); } - room = Rooms.createWithFullRoomData(room); for (const username of members) { diff --git a/app/ui/client/index.js b/app/ui/client/index.js index 2e23faa91ac24..8a08c5cdcbf8a 100644 --- a/app/ui/client/index.js +++ b/app/ui/client/index.js @@ -12,7 +12,6 @@ import './views/404/roomNotFound.html'; import './views/404/invalidSecretURL.html'; import './views/404/invalidInvite.html'; import './views/app/burger.html'; -import './views/app/createChannel.html'; import './views/app/editStatus.html'; import './views/app/editStatus.css'; import './views/app/home.html'; @@ -28,7 +27,6 @@ import './views/app/photoswipe.html'; import './views/cmsPage'; import './views/404/roomNotFound'; import './views/app/burger'; -import './views/app/createChannel'; import './views/app/CreateDirectMessage'; import './views/app/editStatus'; import './views/app/home'; diff --git a/app/ui/client/views/app/createChannel.html b/app/ui/client/views/app/createChannel.html deleted file mode 100644 index 752d3a98e3b59..0000000000000 --- a/app/ui/client/views/app/createChannel.html +++ /dev/null @@ -1,217 +0,0 @@ - - - {{#each roomType in roomTypesBeforeStandard}} - - {{> Template.dynamic template=roomType.creationTemplate }} - - {{/each}} - - {{_ "Channels_are_where_your_team_communicate"}} - - - - - - - - - - {{typeLabel}} - - - {{typeDescription}} - - - - - - - - - {{_ "Read_only_channel"}} - - - {{readOnlyDescription}} - - {{#if e2eEnabled}} - - - - - - - - {{_"Encrypted"}} - - - - {{_"Encrypted_channel_Description"}} - - - {{/if}} - - - - - - - - {{_"Broadcast_channel"}} - - - - {{_"Broadcast_channel_Description"}} - - - - - - - {{_ "Channel_name"}} - - - {{> icon block="rc-input__icon-svg" icon=iconType }} - - - - - {{#if inUse}} - - - {{> icon block="rc-input__error-icon" icon="warning" classes="rc-input__error-icon-svg"}} - - {{_ "Channel_already_exist_static"}} - - {{/if}} - - - - {{_ "Invite_Users"}} - - - {{> icon block="rc-input__icon-svg" icon="at"}} - - - {{#each user in selectedUsers}} - {{> tag user}} - {{/each}} - - - - {{#with config}} - {{#if autocomplete 'isShowing'}} - {{> popupList data=config items=items ready=(autocomplete 'isLoaded')}} - {{/if}} - {{/with}} - - - - {{#if tokenAccessEnabled}} - - - - - - - - - {{_ "Token_Controlled_Access"}} - - - - - {{#if tokensRequired}} - {{> tokenpass extensionsConfig}} - {{/if}} - {{/if}} - - - - - - {{#each roomType in roomTypesAfterStandard}} - - {{> Template.dynamic template=roomType.creationTemplate }} - - {{/each}} - - - - - - Tokenpass - - - - - - - - - - {{tokenRequiment}} - - - {{tokenRequimentDescription}} - - - - - - {{_ "Tokens_Required"}} - - - - - {{_ "Tokens_Required_Input_Description"}} - - - - {{_ "Tokens_Minimum_Needed_Balance"}} - - - - - {{_ "Tokens_Minimum_Needed_Balance_Description"}} - - {{#if selectedTokens}} - - - Tokens - - - {{#each token in selectedTokens}} - {{> tagToken token}} - {{/each}} - - - - - {{/if}} - - - - - - - - - {{#if username}} - - {{> avatar username=username}} - - {{username}} - {{else}} - {{text}} - {{/if}} - {{> icon block="rc-tags__tag-icon" icon="cross"}} - - - - - - {{token}} - {{> icon block="rc-tags__tag-icon" icon="cross"}} - - diff --git a/app/ui/client/views/app/createChannel.js b/app/ui/client/views/app/createChannel.js deleted file mode 100644 index fa3def7e50f73..0000000000000 --- a/app/ui/client/views/app/createChannel.js +++ /dev/null @@ -1,437 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Tracker } from 'meteor/tracker'; -import { Blaze } from 'meteor/blaze'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { Template } from 'meteor/templating'; -import toastr from 'toastr'; -import _ from 'underscore'; - -import { settings } from '../../../../settings'; -import { callbacks } from '../../../../callbacks'; -import { t, roomTypes } from '../../../../utils'; -import { hasAllPermission } from '../../../../authorization'; -import { AutoComplete } from '../../../../meteor-autocomplete/client'; - -const acEvents = { - 'click .rc-popup-list__item'(e, t) { - t.ac.onItemClick(this, e); - }, - 'keydown [name="users"]'(e, t) { - if ([8, 46].includes(e.keyCode) && e.target.value === '') { - const users = t.selectedUsers; - const usersArr = users.get(); - usersArr.pop(); - return users.set(usersArr); - } - - t.ac.onKeyDown(e); - }, - 'keyup [name="users"]'(e, t) { - t.ac.onKeyUp(e); - }, - 'focus [name="users"]'(e, t) { - t.ac.onFocus(e); - }, - 'blur [name="users"]'(e, t) { - t.ac.onBlur(e); - }, -}; - -const validateChannelName = (name) => { - if (settings.get('UI_Allow_room_names_with_special_chars')) { - return true; - } - - const reg = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`); - return name.length === 0 || reg.test(name); -}; - -const filterNames = (old) => { - if (settings.get('UI_Allow_room_names_with_special_chars')) { - return old; - } - - const reg = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`); - return [...old.replace(' ', '').toLocaleLowerCase()].filter((f) => reg.test(f)).join(''); -}; - -Template.createChannel.helpers({ - autocomplete(key) { - const instance = Template.instance(); - const param = instance.ac[key]; - return typeof param === 'function' ? param.apply(instance.ac) : param; - }, - items() { - return Template.instance().ac.filteredList(); - }, - config() { - const filter = Template.instance().userFilter; - return { - filter: filter.get(), - noMatchTemplate: 'userSearchEmpty', - modifier(text) { - const f = filter.get(); - return `@${ f.length === 0 ? text : text.replace(new RegExp(filter.get()), function(part) { - return `${ part }`; - }) }`; - }, - }; - }, - selectedUsers() { - return Template.instance().selectedUsers.get(); - }, - inUse() { - return Template.instance().inUse.get(); - }, - invalidChannel() { - const instance = Template.instance(); - const invalid = instance.invalid.get(); - const inUse = instance.inUse.get(); - return invalid || inUse; - }, - typeLabel() { - return t(Template.instance().type.get() === 'p' ? t('Private_Channel') : t('Public_Channel')); - }, - typeDescription() { - return t(Template.instance().type.get() === 'p' ? t('Just_invited_people_can_access_this_channel') : t('Everyone_can_access_this_channel')); - }, - broadcast() { - return Template.instance().broadcast.get(); - }, - encrypted() { - return Template.instance().encrypted.get(); - }, - encryptedDisabled() { - return Template.instance().type.get() !== 'p' || Template.instance().broadcast.get(); - }, - e2eEnabled() { - return settings.get('E2E_Enable'); - }, - readOnly() { - return Template.instance().readOnly.get(); - }, - readOnlyDescription() { - return t(Template.instance().readOnly.get() ? t('Only_authorized_users_can_write_new_messages') : t('All_users_in_the_channel_can_write_new_messages')); - }, - cantCreateBothTypes() { - return !hasAllPermission(['create-c', 'create-p']); - }, - roomTypeIsP() { - return Template.instance().type.get() === 'p'; - }, - createIsDisabled() { - const instance = Template.instance(); - const invalid = instance.invalid.get(); - const extensions_invalid = instance.extensions_invalid.get(); - const inUse = instance.inUse.get(); - const name = instance.name.get(); - - if (name.length === 0 || invalid || inUse === true || inUse === undefined || extensions_invalid) { - return 'disabled'; - } - return ''; - }, - iconType() { - return Template.instance().type.get() === 'p' ? 'lock' : 'hashtag'; - }, - tokenAccessEnabled() { - return settings.get('API_Tokenpass_URL') !== ''; - }, - tokenIsDisabled() { - return Template.instance().type.get() !== 'p' ? 'disabled' : null; - }, - tokensRequired() { - return Template.instance().tokensRequired.get() && Template.instance().type.get() === 'p'; - }, - extensionsConfig() { - const instance = Template.instance(); - return { - validations: instance.extensions_validations, - submits: instance.extensions_submits, - change: instance.change, - }; - }, - roomTypesBeforeStandard() { - const orderLow = roomTypes.roomTypesOrder.filter((roomTypeOrder) => roomTypeOrder.identifier === 'c')[0].order; - return roomTypes.roomTypesOrder.filter( - (roomTypeOrder) => roomTypeOrder.order < orderLow, - ).map( - (roomTypeOrder) => roomTypes.getConfig(roomTypeOrder.identifier), - ).filter((roomType) => roomType.creationTemplate); - }, - roomTypesAfterStandard() { - const orderHigh = roomTypes.roomTypesOrder.filter((roomTypeOrder) => roomTypeOrder.identifier === 'd')[0].order; - return roomTypes.roomTypesOrder.filter( - (roomTypeOrder) => roomTypeOrder.order > orderHigh, - ).map( - (roomTypeOrder) => roomTypes.getConfig(roomTypeOrder.identifier), - ).filter((roomType) => roomType.creationTemplate); - }, -}); - -Template.createChannel.events({ - ...acEvents, - 'click .rc-tags__tag'({ target }, t) { - const { username } = Blaze.getData(target); - t.selectedUsers.set(t.selectedUsers.get().filter((user) => user.username !== username)); - }, - 'change [name=setTokensRequired]'(e, t) { - t.tokensRequired.set(e.currentTarget.checked); - t.change(); - }, - 'change [name="type"]'(e, t) { - t.type.set(e.target.checked ? e.target.value : 'c'); - t.change(); - }, - 'change [name="broadcast"]'(e, t) { - t.broadcast.set(e.target.checked); - t.change(); - }, - 'change [name="encrypted"]'(e, t) { - t.encrypted.set(e.target.checked); - t.change(); - }, - 'change [name="readOnly"]'(e, t) { - t.readOnly.set(e.target.checked); - }, - 'input [name="users"]'(e, t) { - const input = e.target; - const position = input.selectionEnd || input.selectionStart; - const { length } = input.value; - const modified = filterNames(input.value); - input.value = modified; - document.activeElement === input && e && /input/i.test(e.type) && (input.selectionEnd = position + input.value.length - length); - - t.userFilter.set(modified); - }, - 'input [name="name"]'(e, t) { - const input = e.target; - const position = input.selectionEnd || input.selectionStart; - const { length } = input.value; - const modified = filterNames(input.value); - - input.value = modified; - document.activeElement === input && e && /input/i.test(e.type) && (input.selectionEnd = position + input.value.length - length); - t.invalid.set(!validateChannelName(input.value)); - if (input.value !== t.name.get()) { - t.inUse.set(undefined); - t.checkChannel(input.value); - t.name.set(modified); - } - }, - 'submit .create-channel__content'(e, instance) { - e.preventDefault(); - e.stopPropagation(); - const name = e.target.name.value; - const type = instance.type.get(); - const readOnly = instance.readOnly.get(); - const broadcast = instance.broadcast.get(); - const encrypted = instance.encrypted.get(); - const isPrivate = type === 'p'; - - if (instance.invalid.get() || instance.inUse.get()) { - return e.target.name.focus(); - } - if (!Object.keys(instance.extensions_validations).map((key) => instance.extensions_validations[key]).reduce((valid, fn) => fn(instance) && valid, true)) { - return instance.extensions_invalid.set(true); - } - - const extraData = Object.keys(instance.extensions_submits) - .reduce((result, key) => ({ ...result, ...instance.extensions_submits[key](instance) }), { broadcast, encrypted }); - - Meteor.call(isPrivate ? 'createPrivateGroup' : 'createChannel', name, instance.selectedUsers.get().map((user) => user.username), readOnly, {}, extraData, function(err, result) { - if (err) { - if (err.error === 'error-invalid-name') { - instance.invalid.set(true); - return; - } - if (err.error === 'error-duplicate-channel-name') { - instance.inUse.set(true); - return; - } - if (err.error === 'error-invalid-room-name') { - toastr.error(t('error-invalid-room-name', { room_name: name })); - return; - } - toastr.error(err.message); - return; - } - - if (!isPrivate) { - callbacks.run('aftercreateCombined', { _id: result.rid, name: result.name }); - } - if (instance.data.onCreate) { - instance.data.onCreate(result); - } - - return FlowRouter.go(isPrivate ? 'group' : 'channel', { ...result }, FlowRouter.current().queryParams); - }); - return false; - }, -}); - -Template.createChannel.onRendered(function() { - const users = this.selectedUsers; - - this.firstNode.querySelector('[name="name"]').focus(); - this.ac.element = this.firstNode.querySelector('[name="users"]'); - this.ac.$element = $(this.ac.element); - this.ac.$element.on('autocompleteselect', function(e, { item }) { - const usersArr = users.get(); - usersArr.push(item); - users.set(usersArr); - }); -}); - -Template.createChannel.onCreated(function() { - this.selectedUsers = new ReactiveVar([]); - - const filter = { exceptions: [Meteor.user().username].concat(this.selectedUsers.get().map((u) => u.username)) }; - // this.onViewRead:??y(function() { - Tracker.autorun(() => { - filter.exceptions = [Meteor.user().username].concat(this.selectedUsers.get().map((u) => u.username)); - }); - this.extensions_validations = {}; - this.extensions_submits = {}; - this.name = new ReactiveVar(''); - this.type = new ReactiveVar(hasAllPermission(['create-p']) ? 'p' : 'c'); - this.readOnly = new ReactiveVar(false); - this.broadcast = new ReactiveVar(false); - this.encrypted = new ReactiveVar(settings.get('E2E_Enabled_Default_PrivateRooms')); - this.inUse = new ReactiveVar(undefined); - this.invalid = new ReactiveVar(false); - this.extensions_invalid = new ReactiveVar(false); - this.change = _.debounce(() => { - let valid = true; - Object.keys(this.extensions_validations).map((key) => this.extensions_validations[key]).forEach((f) => { valid = f(this) && valid; }); - this.extensions_invalid.set(!valid); - }, 300); - - Tracker.autorun(() => { - const broadcast = this.broadcast.get(); - if (broadcast) { - this.readOnly.set(true); - this.encrypted.set(false); - } - - const type = this.type.get(); - if (type !== 'p') { - this.encrypted.set(false); - } - }); - - this.userFilter = new ReactiveVar(''); - this.tokensRequired = new ReactiveVar(false); - this.checkChannel = _.debounce((name) => { - if (validateChannelName(name)) { - return Meteor.call('roomNameExists', name, (error, result) => { - if (error) { - return; - } - this.inUse.set(result); - }); - } - this.inUse.set(undefined); - }, 1000); - - this.ac = new AutoComplete( - { - selector: { - anchor: '.rc-input__label', - item: '.rc-popup-list__item', - container: '.rc-popup-list__list', - }, - position: 'fixed', - limit: 10, - inputDelay: 300, - rules: [ - { - // @TODO maybe change this 'collection' and/or template - - collection: 'UserAndRoom', - endpoint: 'users.autocomplete', - field: 'username', - matchAll: true, - filter, - doNotChangeWidth: false, - selector(match) { - return { term: match }; - }, - sort: 'username', - }, - ], - - }); - - // this.firstNode.querySelector('[name=name]').focus(); - // this.ac.element = this.firstNode.querySelector('[name=users]'); - // this.ac.$element = $(this.ac.element); - this.ac.tmplInst = this; -}); - -Template.tokenpass.onCreated(function() { - this.data.validations.tokenpass = (instance) => { - const result = (settings.get('API_Tokenpass_URL') !== '' && instance.tokensRequired.get() && instance.type.get() === 'p') && this.selectedTokens.get().length === 0; - this.invalid.set(result); - return !result; - }; - this.data.submits.tokenpass = () => ({ - tokenpass: { - require: this.requireAll.get() ? 'all' : 'any', - tokens: this.selectedTokens.get(), - }, - }); - this.balance = new ReactiveVar(''); - this.token = new ReactiveVar(''); - this.selectedTokens = new ReactiveVar([]); - this.invalid = new ReactiveVar(false); - this.requireAll = new ReactiveVar(true); -}); - -Template.tokenpass.helpers({ - selectedTokens() { - return Template.instance().selectedTokens.get(); - }, - invalid() { - return Template.instance().invalid.get(); - }, - addIsDisabled() { - const { balance, token } = Template.instance(); - return balance.get().length && token.get().length ? '' : 'disabled'; - }, - tokenRequiment() { - return Template.instance().requireAll.get() ? t('Require_all_tokens') : t('Require_any_token'); - }, - tokenRequimentDescription() { - return Template.instance().requireAll.get() ? t('All_added_tokens_will_be_required_by_the_user') : t('At_least_one_added_token_is_required_by_the_user'); - }, -}); - -Template.tokenpass.events({ - 'click [data-button=add]'(e, instance) { - const { balance, token, selectedTokens } = instance; - const text = token.get(); - const arr = selectedTokens.get(); - selectedTokens.set([...arr.filter((token) => token.token !== text), { token: text, balance: balance.get() }]); - balance.set(''); - token.set(''); - [...instance.findAll('input[type=text],input[type=number]')].forEach((el) => { el.value = ''; }); - instance.data.change(); - return false; - }, - 'click .rc-tags__tag'({ target }, t) { - const { token } = Blaze.getData(target); - t.selectedTokens.set(t.selectedTokens.get().filter((t) => t.token !== token)); - t.data.change(); - }, - 'input [name=tokenMinimumNeededBalance]'(e, i) { - i.balance.set(e.target.value); - }, - 'input [name=tokensRequired]'(e, i) { - i.token.set(e.target.value); - }, - 'change [name=tokenRequireAll]'(e, i) { - i.requireAll.set(e.currentTarget.checked); - }, -}); diff --git a/client/sidebar/header/CreateChannel.js b/client/sidebar/header/CreateChannel.js new file mode 100644 index 0000000000000..932b0b96c65c1 --- /dev/null +++ b/client/sidebar/header/CreateChannel.js @@ -0,0 +1,262 @@ +import { FlowRouter } from 'meteor/kadira:flow-router'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { useMutableCallback, useDebouncedCallback } from '@rocket.chat/fuselage-hooks'; +import { Box, Modal, ButtonGroup, Button, TextInput, Icon, Field, ToggleSwitch } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import { useForm } from '../../hooks/useForm'; +import { useEndpointActionExperimental } from '../../hooks/useEndpointAction'; +import UserAutoCompleteMultiple from '../../../ee/client/audit/UserAutoCompleteMultiple'; +import { useSetting } from '../../contexts/SettingsContext'; +import { usePermission } from '../../contexts/AuthorizationContext'; +import { useMethod } from '../../contexts/ServerContext'; + +export const CreateChannel = ({ + values, + handlers, + hasUnsavedChanges, + onChangeUsers, + onChangeType, + onChangeBroadcast, + canOnlyCreateOneType, + e2eEnabledForPrivateByDefault, + onCreate, + onClose, +}) => { + const t = useTranslation(); + const e2eEnabled = useSetting('E2E_Enable'); + const namesValidation = useSetting('UTF8_Names_Validation'); + const allowSpecialNames = useSetting('UI_Allow_room_names_with_special_chars'); + const channelNameExists = useMethod('roomNameExists'); + const channelNameRegex = useMemo(() => { + if (allowSpecialNames) { + return ''; + } + const regex = new RegExp(`^${ namesValidation }$`); + + return regex; + }, [allowSpecialNames, namesValidation]); + + const [nameError, setNameError] = useState(); + + const checkName = useDebouncedCallback(async (name) => { + setNameError(false); + if (hasUnsavedChanges) { return; } + if (!name || name.length === 0) { return setNameError(t('Field_required')); } + if (!channelNameRegex.test(name)) { return setNameError(t('error-invalid-name')); } + const isNotAvailable = await channelNameExists(name); + if (isNotAvailable) { return setNameError(t('Channel_already_exist', name)); } + }, 100, [name]); + + useEffect(() => { + checkName(values.name); + }, [checkName, values.name]); + + const e2edisabled = useMemo(() => !values.type || values.broadcast || !e2eEnabled || e2eEnabledForPrivateByDefault, [e2eEnabled, e2eEnabledForPrivateByDefault, values.broadcast, values.type]); + + const canSave = useMemo(() => hasUnsavedChanges && !nameError, [hasUnsavedChanges, nameError]); + + return + + {t('Create_channel')} + + + + + {t('Name')} + + } placeholder={t('Channel_name')} onChange={handlers.handleName}/> + + {hasUnsavedChanges && nameError && + {nameError} + } + + + {t('Topic')} ({t('optional')}) + + + + + + + + {t('Private')} + {values.type ? t('Only_invited_users_can_acess_this_channel') : t('Everyone_can_access_this_channel')} + + + + + + + + {t('Read_only')} + {t('All_users_in_the_channel_can_write_new_messages')} + + + + + + + + {t('Encrypted')} + {values.type ? t('Encrypted_channel_Description') : t('Encrypted_not_available')} + + + + + + + + {t('Broadcast')} + {t('Broadcast_channel_Description')} + + + + + + {`${ t('Add_members') } (${ t('optional') })`} + + + + + + {t('Cancel')} + {t('Create')} + + + ; +}; + +export default memo(({ + onClose, +}) => { + const createChannel = useEndpointActionExperimental('POST', 'channels.create'); + const createPrivateChannel = useEndpointActionExperimental('POST', 'groups.create'); + const setChannelDescription = useEndpointActionExperimental('POST', 'channels.setDescription'); + const setPrivateChannelDescription = useEndpointActionExperimental('POST', 'groups.setDescription'); + const canCreateChannel = usePermission('create-c'); + const canCreatePrivateChannel = usePermission('create-p'); + const e2eEnabledForPrivateByDefault = useSetting('E2E_Enabled_Default_PrivateRooms'); + const canOnlyCreateOneType = useMemo(() => { + if (!canCreateChannel && canCreatePrivateChannel) { + return 'p'; + } + if (canCreateChannel && !canCreatePrivateChannel) { + return 'c'; + } + return false; + }, [canCreateChannel, canCreatePrivateChannel]); + + + const initialValues = { + users: [], + name: '', + description: '', + type: canOnlyCreateOneType ? canOnlyCreateOneType === 'p' : true, + readOnly: false, + encrypted: e2eEnabledForPrivateByDefault, + broadcast: false, + }; + const { values, handlers, hasUnsavedChanges } = useForm(initialValues); + + const { + users, + name, + description, + type, + readOnly, + broadcast, + encrypted, + } = values; + const { + handleUsers, + handleEncrypted, + handleType, + handleBroadcast, + handleReadOnly, + } = handlers; + + const onChangeUsers = useMutableCallback((value, action) => { + if (!action) { + if (users.includes(value)) { + return; + } + return handleUsers([...users, value]); + } + handleUsers(users.filter((current) => current !== value)); + }); + + const onChangeType = useMutableCallback((value) => { + if (value) { + handleEncrypted(false); + } + return handleType(value); + }); + + const onChangeBroadcast = useMutableCallback((value) => { + if (value) { + handleEncrypted(false); + handleReadOnly(true); + } + handleReadOnly(value); + return handleBroadcast(value); + }); + + const onCreate = useCallback(async () => { + const goToRoom = (rid) => { + FlowRouter.goToRoomById(rid); + }; + + const params = { + name, + members: users, + readOnly, + extraData: { + broadcast, + encrypted, + }, + }; + let roomData; + + if (type) { + roomData = await createPrivateChannel(params); + goToRoom(roomData.group._id); + } else { + roomData = await createChannel(params); + goToRoom(roomData.channel._id); + } + + if (roomData.success && roomData.group && description) { + setPrivateChannelDescription({ description, roomName: roomData.group.name }); + } else if (roomData.success && roomData.channel && description) { + setChannelDescription({ description, roomName: roomData.channel.name }); + } + + onClose(); + }, [broadcast, + createChannel, + createPrivateChannel, + description, + encrypted, + name, + onClose, + readOnly, + setChannelDescription, + setPrivateChannelDescription, + type, + users, + ]); + + return ; +}); diff --git a/client/sidebar/header/actions/CreateRoom.js b/client/sidebar/header/actions/CreateRoom.js index 4e47e0d4058af..b07afa6b7b1c0 100644 --- a/client/sidebar/header/actions/CreateRoom.js +++ b/client/sidebar/header/actions/CreateRoom.js @@ -6,6 +6,8 @@ import { popover, modal } from '../../../../app/ui-utils'; import { useAtLeastOnePermission, usePermission } from '../../../contexts/AuthorizationContext'; import { useSetting } from '../../../contexts/SettingsContext'; import { useTranslation } from '../../../contexts/TranslationContext'; +import { useSetModal } from '../../../contexts/ModalContext'; +import CreateChannel from '../CreateChannel'; const CREATE_ROOM_PERMISSIONS = ['create-c', 'create-p', 'create-d', 'start-discussion', 'start-discussion-other-user']; @@ -27,6 +29,18 @@ const openPopover = (e, items) => popover.open({ offsetVertical: e.currentTarget.clientHeight + 10, }); +const useReactModal = (setModal, Component) => useMutableCallback((e) => { + e.preventDefault(); + + const handleClose = () => { + setModal(null); + }; + + setModal(() => ); +}); + const useAction = (title, content) => useMutableCallback((e) => { e.preventDefault(); modal.open({ @@ -46,13 +60,15 @@ const useAction = (title, content) => useMutableCallback((e) => { const CreateRoom = (props) => { const t = useTranslation(); + const setModal = useSetModal(); + const showCreate = useAtLeastOnePermission(CREATE_ROOM_PERMISSIONS); const canCreateChannel = useAtLeastOnePermission(CREATE_CHANNEL_PERMISSIONS); const canCreateDirectMessages = usePermission('create-d'); const canCreateDiscussion = useAtLeastOnePermission(CREATE_DISCUSSION_PERMISSIONS); - const createChannel = useAction(t('Create_A_New_Channel'), 'createChannel'); + const createChannel = useReactModal(setModal, CreateChannel); const createDirectMessage = useAction(t('Direct_Messages'), 'CreateDirectMessage'); const createDiscussion = useAction(t('Discussion_title'), 'CreateDiscussion'); diff --git a/client/views/room/contextualBar/RoomMembers/List/RoomMembers.js b/client/views/room/contextualBar/RoomMembers/List/RoomMembers.js index 7fefda2682056..dcf35b166da37 100644 --- a/client/views/room/contextualBar/RoomMembers/List/RoomMembers.js +++ b/client/views/room/contextualBar/RoomMembers/List/RoomMembers.js @@ -177,7 +177,7 @@ export default ({ const params = useMemo(() => [rid, type === 'all', { limit: 50 }, debouncedText], [rid, type, debouncedText]); const { value, phase, more, error } = useGetUsersOfRoom(params); - console.log(value); + const canAddUsers = useAtLeastOnePermission(useMemo(() => [room.t === 'p' ? 'add-user-to-any-p-room' : 'add-user-to-any-c-room', 'add-user-to-joined-room'], [room.t]), rid); const handleTextChange = useCallback((event) => { diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 2235b6ad19424..673babfb0516e 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -259,6 +259,7 @@ "Add_user": "Add user", "Add_User": "Add User", "Add_users": "Add users", + "Add_members": "Add Members", "add-livechat-department-agents": "Add Omnichannel Agents to Departments", "add-oauth-service": "Add Oauth Service", "add-oauth-service_description": "Permission to add a new Oauth service", @@ -618,6 +619,7 @@ "BotHelpers_userFields_Description": "CSV of user fields that can be accessed by bots helper methods.", "Bots": "Bots", "Branch": "Branch", + "Broadcast": "Broadcast", "Broadcast_channel": "Broadcast Channel", "Broadcast_channel_Description": "Only authorized users can write new messages, but the other users will be able to reply", "Broadcast_Connected_Instances": "Broadcast Connected Instances", @@ -706,6 +708,7 @@ "Channels": "Channels", "Channels_are_where_your_team_communicate": "Channels are where your team communicate", "Channels_list": "List of public channels", + "Channel_what_is_this_channel_about": "What is this channel about?", "Chart": "Chart", "Chat_button": "Chat button", "Chat_close": "Chat Close", @@ -1159,6 +1162,7 @@ "Country_Zimbabwe": "Zimbabwe", "Cozy": "Cozy", "Create": "Create", + "Create_channel": "Create Channel", "Create_A_New_Channel": "Create a New Channel", "Create_new": "Create new", "Create_unique_rules_for_this_channel": "Create unique rules for this channel", @@ -1486,6 +1490,7 @@ "Encrypted_channel_Description": "End to end encrypted channel. Search will not work with encrypted channels and notifications may not show the messages content.", "Encrypted_message": "Encrypted message", "Encrypted_setting_changed_successfully": "Encrypted setting changed successfully", + "Encrypted_not_available": "Not available for Public Channels", "Encryption_key_saved_successfully": "Your encryption key was saved successfully.", "EncryptionKey_Change_Disabled": "You can't set a password for your encryption key because your private key is not present on this client. In order to set a new password you need load your private key using your existing password or use a client where the key is already loaded.", "End": "End", @@ -2924,6 +2929,7 @@ "Only_On_Desktop": "Desktop mode (only sends with enter on desktop)", "Only_works_with_chrome_version_greater_50": "Only works with Chrome browser versions > 50", "Only_you_can_see_this_message": "Only you can see this message", + "Only_invited_users_can_acess_this_channel": "Only invited users can access this Channel", "Oops_page_not_found": "Oops, page not found", "Oops!": "Oops", "Open": "Open",
{{_ "Channels_are_where_your_team_communicate"}}