Rewrite: CreateChannel modal component (#20617)
Co-authored-by: Martin <martin.schoeler@rocket.chat>pull/20661/head
parent
374281c83a
commit
2ad2d0bd7e
@ -1,217 +0,0 @@ |
||||
<template name="createChannel"> |
||||
<section class="create-channel"> |
||||
{{#each roomType in roomTypesBeforeStandard}} |
||||
<div class="room-type-creation"> |
||||
{{> Template.dynamic template=roomType.creationTemplate }} |
||||
</div> |
||||
{{/each}} |
||||
<div class="create-channel__wrapper"> |
||||
<p class="create-channel__description">{{_ "Channels_are_where_your_team_communicate"}}</p> |
||||
<form id="create-channel" name="create-channel" class="create-channel__content"> |
||||
<div class="create-channel__switches"> |
||||
<div class="rc-switch"> |
||||
<label class="rc-switch__label" tabindex="-1"> |
||||
<input type="checkbox" class="rc-switch__input" name="type" value="p" checked={{roomTypeIsP}} disabled="{{cantCreateBothTypes}}"> |
||||
<span class="rc-switch__button"> |
||||
<span class="rc-switch__button-inside"></span> |
||||
</span> |
||||
<span class="rc-switch__text"> |
||||
{{typeLabel}} |
||||
</span> |
||||
</label> |
||||
<span class="rc-switch__description">{{typeDescription}}</span> |
||||
</div> |
||||
<div class="rc-switch"> |
||||
<label class="rc-switch__label"> |
||||
<input type="checkbox" class="rc-switch__input" name="readOnly" checked={{readOnly}} disabled="{{broadcast}}"> |
||||
<span class="rc-switch__button"> |
||||
<span class="rc-switch__button-inside"></span> |
||||
</span> |
||||
<span class="rc-switch__text"> |
||||
{{_ "Read_only_channel"}} |
||||
</span> |
||||
</label> |
||||
<span class="rc-switch__description">{{readOnlyDescription}}</span> |
||||
</div> |
||||
{{#if e2eEnabled}} |
||||
<div class="rc-switch"> |
||||
<label class="rc-switch__label"> |
||||
<input type="checkbox" class="rc-switch__input" name="encrypted" checked={{encrypted}} disabled="{{encryptedDisabled}}"> |
||||
<span class="rc-switch__button"> |
||||
<span class="rc-switch__button-inside"></span> |
||||
</span> |
||||
<span class="rc-switch__text"> |
||||
{{_"Encrypted"}} |
||||
</span> |
||||
</label> |
||||
<span class="rc-switch__description"> |
||||
{{_"Encrypted_channel_Description"}} |
||||
</span> |
||||
</div> |
||||
{{/if}} |
||||
<div class="rc-switch"> |
||||
<label class="rc-switch__label"> |
||||
<input type="checkbox" class="rc-switch__input" name="broadcast"> |
||||
<span class="rc-switch__button"> |
||||
<span class="rc-switch__button-inside"></span> |
||||
</span> |
||||
<span class="rc-switch__text"> |
||||
{{_"Broadcast_channel"}} |
||||
</span> |
||||
</label> |
||||
<span class="rc-switch__description"> |
||||
{{_"Broadcast_channel_Description"}} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<div class="create-channel__inputs"> |
||||
<div class="rc-input {{#if invalidChannel}}rc-input--error{{/if}}"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Channel_name"}}</div> |
||||
<div class="rc-input__wrapper"> |
||||
<div class="rc-input__icon"> |
||||
{{> icon block="rc-input__icon-svg" icon=iconType }} |
||||
</div> |
||||
<input name="name" type="text" class="rc-input__element" |
||||
placeholder="{{_ "Channel_Name_Placeholder"}}" autofocus> |
||||
</div> |
||||
</label> |
||||
{{#if inUse}} |
||||
<div class="rc-input__error"> |
||||
<div class="rc-input__error-icon"> |
||||
{{> icon block="rc-input__error-icon" icon="warning" classes="rc-input__error-icon-svg"}} |
||||
</div> |
||||
<div class="rc-input__error-message">{{_ "Channel_already_exist_static"}}</div> |
||||
</div> |
||||
{{/if}} |
||||
</div> |
||||
<div class="rc-input"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Invite_Users"}}</div> |
||||
<div class="rc-input__wrapper"> |
||||
<div class="rc-input__icon"> |
||||
{{> icon block="rc-input__icon-svg" icon="at"}} |
||||
</div> |
||||
<div class="rc-tags"> |
||||
{{#each user in selectedUsers}} |
||||
{{> tag user}} |
||||
{{/each}} |
||||
<input type="text" class="rc-tags__input" placeholder="{{_ "Username_Placeholder"}}" name="users" autocomplete="off"/> |
||||
</div> |
||||
</div> |
||||
{{#with config}} |
||||
{{#if autocomplete 'isShowing'}} |
||||
{{> popupList data=config items=items ready=(autocomplete 'isLoaded')}} |
||||
{{/if}} |
||||
{{/with}} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
{{#if tokenAccessEnabled}} |
||||
<div class="create-channel__switches"> |
||||
<div class="rc-switch"> |
||||
<label class="rc-switch__label" tabindex="-1"> |
||||
<input type="checkbox" class="rc-switch__input" name="setTokensRequired" {{tokenIsDisabled}}> |
||||
<span class="rc-switch__button"> |
||||
<span class="rc-switch__button-inside"></span> |
||||
</span> |
||||
<span class="rc-switch__text"> |
||||
{{_ "Token_Controlled_Access"}} |
||||
</span> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
{{#if tokensRequired}} |
||||
{{> tokenpass extensionsConfig}} |
||||
{{/if}} |
||||
{{/if}} |
||||
<div class="create-channel__inputs"> |
||||
<input form='create-channel' class="rc-button rc-button--primary" type='submit' data-button="create" {{createIsDisabled}} value="{{_ "Create"}}" /> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
{{#each roomType in roomTypesAfterStandard}} |
||||
<div class="room-type-creation"> |
||||
{{> Template.dynamic template=roomType.creationTemplate }} |
||||
</div> |
||||
{{/each}} |
||||
</section> |
||||
</template> |
||||
|
||||
<template name="tokenpass"> |
||||
<fieldset class="rc-form-fieldset {{#if invalid}}rc-form-fieldset--error{{/if}}"> |
||||
<legend class="rc-form-legend">Tokenpass</legend> |
||||
|
||||
<div class="create-channel__switches"> |
||||
<div class="rc-switch"> |
||||
<label class="rc-switch__label" tabindex="-1"> |
||||
<input type="checkbox" class="rc-switch__input" name="tokenRequireAll" value="all" checked> |
||||
<span class="rc-switch__button"> |
||||
<span class="rc-switch__button-inside"></span> |
||||
</span> |
||||
<span class="rc-switch__text"> |
||||
{{tokenRequiment}} |
||||
</span> |
||||
</label> |
||||
<span class="rc-switch__description">{{tokenRequimentDescription}}</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="rc-input {{#if invalidTokens}}rc-input--error{{/if}}"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Tokens_Required"}}</div> |
||||
<div class="rc-input__wrapper"> |
||||
<input name="tokensRequired" type="text" class="rc-input__element" placeholder="{{_ "Tokens_Required_Input_Placeholder"}}"> |
||||
</div> |
||||
</label> |
||||
<div class="rc-input__description">{{_ "Tokens_Required_Input_Description"}}</div> |
||||
</div> |
||||
<div class="rc-input"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Tokens_Minimum_Needed_Balance"}}</div> |
||||
<div class="rc-input__wrapper"> |
||||
<input name="tokenMinimumNeededBalance" type="number" class="rc-input__element" placeholder="{{_ "Tokens_Minimum_Needed_Balance_Placeholder"}}" min="0"> |
||||
</div> |
||||
</label> |
||||
<div class="rc-input__description">{{_ "Tokens_Minimum_Needed_Balance_Description"}}</div> |
||||
</div> |
||||
{{#if selectedTokens}} |
||||
<div class="rc-input"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">Tokens</div> |
||||
<div class="rc-input__wrapper"> |
||||
<div class="rc-tags rc-tags--no-icon"> |
||||
{{#each token in selectedTokens}} |
||||
{{> tagToken token}} |
||||
{{/each}} |
||||
</div> |
||||
</div> |
||||
</label> |
||||
</div> |
||||
{{/if}} |
||||
|
||||
<input class="rc-button rc-button--primary rc-button--full" type="submit" data-button="add" {{addIsDisabled}} value="{{_ "Add"}}"> |
||||
|
||||
</fieldset> |
||||
</template> |
||||
|
||||
<template name='tag'> |
||||
<span class="rc-tags__tag"> |
||||
{{#if username}} |
||||
<span class="rc-tags__tag-image"> |
||||
{{> avatar username=username}} |
||||
</span> |
||||
<span class="rc-tags__tag-text">{{username}}</span> |
||||
{{else}} |
||||
<span class="rc-tags__tag-text">{{text}}</span> |
||||
{{/if}} |
||||
{{> icon block="rc-tags__tag-icon" icon="cross"}} |
||||
</span> |
||||
</template> |
||||
|
||||
<template name='tagToken'> |
||||
<span class="rc-tags__tag"> |
||||
<span class="rc-tags__tag-text" title="balance: {{balance}}">{{token}}</span> |
||||
{{> icon block="rc-tags__tag-icon" icon="cross"}} |
||||
</span> |
||||
</template> |
||||
@ -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 `<strong>${ part }</strong>`; |
||||
}) }`;
|
||||
}, |
||||
}; |
||||
}, |
||||
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); |
||||
}, |
||||
}); |
||||
@ -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 <Modal> |
||||
<Modal.Header> |
||||
<Modal.Title>{t('Create_channel')}</Modal.Title> |
||||
<Modal.Close onClick={onClose}/> |
||||
</Modal.Header> |
||||
<Modal.Content> |
||||
<Field mbe='x24'> |
||||
<Field.Label>{t('Name')}</Field.Label> |
||||
<Field.Row> |
||||
<TextInput error={hasUnsavedChanges && nameError} addon={<Icon name={values.type ? 'lock' : 'hash'} size='x20' />} placeholder={t('Channel_name')} onChange={handlers.handleName}/> |
||||
</Field.Row> |
||||
{hasUnsavedChanges && nameError && <Field.Error> |
||||
{nameError} |
||||
</Field.Error>} |
||||
</Field> |
||||
<Field mbe='x24'> |
||||
<Field.Label>{t('Topic')} <Box is='span' color='neutral-600'>({t('optional')})</Box></Field.Label> |
||||
<Field.Row> |
||||
<TextInput placeholder={t('Channel_what_is_this_channel_about')} onChange={handlers.handleDescription}/> |
||||
</Field.Row> |
||||
</Field> |
||||
<Field mbe='x24'> |
||||
<Box display='flex' justifyContent='space-between' alignItems='start'> |
||||
<Box display='flex' flexDirection='column'> |
||||
<Field.Label>{t('Private')}</Field.Label> |
||||
<Field.Description>{values.type ? t('Only_invited_users_can_acess_this_channel') : t('Everyone_can_access_this_channel')}</Field.Description> |
||||
</Box> |
||||
<ToggleSwitch checked={values.type} disabled={!!canOnlyCreateOneType} onChange={onChangeType}/> |
||||
</Box> |
||||
</Field> |
||||
<Field mbe='x24' disabled={values.broadcast}> |
||||
<Box display='flex' justifyContent='space-between' alignItems='start'> |
||||
<Box display='flex' flexDirection='column'> |
||||
<Field.Label>{t('Read_only')}</Field.Label> |
||||
<Field.Description>{t('All_users_in_the_channel_can_write_new_messages')}</Field.Description> |
||||
</Box> |
||||
<ToggleSwitch checked={values.readOnly} disabled={values.broadcast} onChange={handlers.handleReadOnly}/> |
||||
</Box> |
||||
</Field> |
||||
<Field disabled={e2edisabled} mbe='x24'> |
||||
<Box display='flex' justifyContent='space-between' alignItems='start'> |
||||
<Box display='flex' flexDirection='column'> |
||||
<Field.Label>{t('Encrypted')}</Field.Label> |
||||
<Field.Description>{values.type ? t('Encrypted_channel_Description') : t('Encrypted_not_available')}</Field.Description> |
||||
</Box> |
||||
<ToggleSwitch checked={values.encrypted} disabled={e2edisabled} onChange={handlers.handleEncrypted} /> |
||||
</Box> |
||||
</Field> |
||||
<Field mbe='x24'> |
||||
<Box display='flex' justifyContent='space-between' alignItems='start'> |
||||
<Box display='flex' flexDirection='column'> |
||||
<Field.Label>{t('Broadcast')}</Field.Label> |
||||
<Field.Description>{t('Broadcast_channel_Description')}</Field.Description> |
||||
</Box> |
||||
<ToggleSwitch onChange={onChangeBroadcast} /> |
||||
</Box> |
||||
</Field> |
||||
<Field mbe='x24'> |
||||
<Field.Label>{`${ t('Add_members') } (${ t('optional') })`}</Field.Label> |
||||
<UserAutoCompleteMultiple value={values.users} onChange={onChangeUsers}/> |
||||
</Field> |
||||
</Modal.Content> |
||||
<Modal.Footer> |
||||
<ButtonGroup align='end'> |
||||
<Button onClick={onClose}>{t('Cancel')}</Button> |
||||
<Button disabled={!canSave} onClick={onCreate} primary>{t('Create')}</Button> |
||||
</ButtonGroup> |
||||
</Modal.Footer> |
||||
</Modal>; |
||||
}; |
||||
|
||||
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 <CreateChannel |
||||
values={values} |
||||
handlers={handlers} |
||||
hasUnsavedChanges={hasUnsavedChanges} |
||||
onChangeUsers={onChangeUsers} |
||||
onChangeType={onChangeType} |
||||
onChangeBroadcast={onChangeBroadcast} |
||||
canOnlyCreateOneType={canOnlyCreateOneType} |
||||
e2eEnabledForPrivateByDefault={e2eEnabledForPrivateByDefault} |
||||
onClose={onClose} |
||||
onCreate={onCreate} |
||||
/>; |
||||
}); |
||||
Loading…
Reference in new issue