[NEW][ENTERPRISE] Support for custom Livechat registration form fields (#17581)

Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
pull/17715/head
Renato Becker 5 years ago committed by GitHub
parent bf5af0da0b
commit 4aed76deba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      app/livechat/client/views/app/livechatCustomFieldForm.html
  2. 29
      app/livechat/client/views/app/livechatCustomFieldForm.js
  3. 23
      app/livechat/client/views/app/tabbar/visitorEdit.html
  4. 34
      app/livechat/client/views/app/tabbar/visitorEdit.js
  5. 22
      app/livechat/client/views/app/tabbar/visitorEditCustomField.html
  6. 17
      app/livechat/client/views/app/tabbar/visitorEditCustomField.js
  7. 1
      app/livechat/client/views/regular.js
  8. 2
      app/livechat/server/api/lib/livechat.js
  9. 5
      app/livechat/server/api/v1/config.js
  10. 6
      app/livechat/server/methods/saveCustomField.js
  11. 42
      ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.html
  12. 37
      ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.js
  13. 4
      ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js
  14. 17
      ee/app/livechat-enterprise/server/hooks/index.js
  15. 39
      ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js
  16. 8
      ee/app/livechat-enterprise/server/index.js
  17. 43
      ee/app/livechat-enterprise/server/lib/Helper.js
  18. 3
      ee/i18n/en.i18n.json
  19. 3
      ee/i18n/pt-BR.i18n.json
  20. 4
      packages/rocketchat-i18n/i18n/en.i18n.json
  21. 2
      packages/rocketchat-i18n/i18n/pt-BR.i18n.json
  22. 1
      packages/rocketchat-i18n/i18n/pt.i18n.json

@ -7,19 +7,19 @@
<div class="input-line">
<label>{{_ "Field"}}</label>
<div>
<input type="text" class="rc-input__element" name="field" value="{{customField._id}}" readonly="{{$exists customField._id}}" placeholder="{{_ "Field"}}" />
<input type="text" class="rc-input__element custom-field-input" name="field" value="{{customField._id}}" readonly="{{$exists customField._id}}" placeholder="{{_ "Field"}}" />
</div>
</div>
<div class="input-line">
<label>{{_ "Label"}}</label>
<div>
<input type="text" class="rc-input__element" name="label" value="{{customField.label}}" placeholder="{{_ "Label"}}" />
<input type="text" class="rc-input__element custom-field-input" name="label" value="{{customField.label}}" placeholder="{{_ "Label"}}" />
</div>
</div>
<div class="input-line">
<label>{{_ "Scope"}}</label>
<div>
<select name="scope" class="rc-input__element">
<select name="scope" class="rc-input__element custom-field-input">
<option value="visitor" selected="{{$eq customField.scope 'visitor'}}">{{_ "Visitor"}}</option>
<option value="room" selected="{{$eq customField.scope 'room'}}">{{_ "Room"}}</option>
</select>
@ -28,18 +28,21 @@
<div class="input-line">
<label>{{_ "Visibility"}}</label>
<div>
<select name="visibility" class="rc-input__element">
<select name="visibility" class="rc-input__element custom-field-input">
<option value="visible" selected="{{$eq customField.visibility 'visible'}}">{{_ "Visible"}}</option>
<option value="hidden" selected="{{$eq customField.visibility 'hidden'}}">{{_ "Hidden"}}</option>
</select>
</div>
</div>
<div class="input-line">
<label>{{_ "Regexp_validation"}}</label>
<label>{{_ "Validation"}}</label>
<div>
<input type="text" class="rc-input__element" name="regexp" value="{{customField.regexp}}" placeholder="{{_ "Regexp_validation"}}" />
<input type="text" class="rc-input__element custom-field-input" name="regexp" value="{{customField.regexp}}" placeholder="{{_ "Regexp_validation"}}" />
</div>
</div>
{{#if customFieldsTemplate}}
{{> Template.dynamic template=customFieldsTemplate data=dataContext }}
{{/if}}
</fieldset>
<div class="rc-button__group submit">
<button class="rc-button back" type="button"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button>

@ -5,6 +5,7 @@ import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { t, handleError } from '../../../../utils';
import { getCustomFormTemplate } from './customTemplates/register';
import './livechatCustomFieldForm.html';
import { APIClient } from '../../../../utils/client';
@ -12,6 +13,16 @@ Template.livechatCustomFieldForm.helpers({
customField() {
return Template.instance().customField.get();
},
customFieldsTemplate() {
return getCustomFormTemplate('livechatCustomFieldsAdditionalForm');
},
dataContext() {
// To make the dynamic template reactive we need to pass a ReactiveVar through the data property
// because only the dynamic template data will be reloaded
return Template.instance().localFields;
},
});
Template.livechatCustomFieldForm.events({
@ -45,6 +56,16 @@ Template.livechatCustomFieldForm.events({
regexp: regexp.trim(),
};
instance.$('.additional-field').each((i, el) => {
const elField = instance.$(el);
const name = elField.attr('name');
let value = elField.val();
if (['true', 'false'].includes(value) && el.tagName === 'SELECT') {
value = value === 'true';
}
customFieldData[name] = value;
});
Meteor.call('livechat:saveCustomField', _id, customFieldData, function(error) {
$btn.html(oldBtnValue);
if (error) {
@ -60,12 +81,20 @@ Template.livechatCustomFieldForm.events({
e.preventDefault();
FlowRouter.go('livechat-customfields');
},
'change .custom-field-input'(e, instance) {
const { target: { name, value } } = e;
instance.localFields.set({ ...instance.localFields.get(), [name]: value });
},
});
Template.livechatCustomFieldForm.onCreated(async function() {
this.customField = new ReactiveVar({});
this.localFields = new ReactiveVar({});
const { customField } = await APIClient.v1.get(`livechat/custom-fields/${ FlowRouter.getParam('_id') }`);
if (customField) {
this.customField.set(customField);
this.localFields.set({ ...customField });
}
});

@ -30,17 +30,9 @@
</div>
</label>
</div>
{{#if canViewCustomFields }}
{{#each visitorCustomFields}}
<div class="rc-input rc-form-group rc-form-group--small">
<label class="rc-input__label">
<div class="rc-input__title">{{label}}</div>
<div class="rc-input__wrapper">
<input class="rc-input__element" type="text" name="{{name}}" autocomplete="off" data-visitorLivechatData="true" value="{{value}}" disabled="{{canOnlyViewCustomFields}}">
</div>
</label>
</div>
{{#each field in visitorCustomFields}}
{{> visitorEditCustomField field }}
{{/each}}
{{/if}}
{{/with}}
@ -94,15 +86,8 @@
</ul>
</div>
{{#if canViewCustomFields }}
{{#each roomCustomFields}}
<div class="rc-input rc-form-group rc-form-group--small">
<label class="rc-input__label">
<div class="rc-input__title">{{label}}</div>
<div class="rc-input__wrapper">
<input class="rc-input__element" type="text" name="{{name}}" autocomplete="off" data-roomLivechatData="true" value="{{value}}" disabled="{{canOnlyViewCustomFields}}">
</div>
</label>
</div>
{{#each field in roomCustomFields}}
{{> visitorEditCustomField field }}
{{/each}}
{{/if}}
{{/with}}

@ -11,6 +11,16 @@ import { getCustomFormTemplate } from '../customTemplates/register';
const CUSTOM_FIELDS_COUNT = 100;
const getCustomFieldsByScope = (customFields = [], data = {}, filter, disabled) =>
customFields
.filter(({ visibility, scope }) => visibility !== 'hidden' && scope === filter)
.map(({ _id: name, scope, label, ...extraData }) => {
const value = data[name] ? data[name] : '';
return { name, label, scope, value, disabled, ...extraData };
});
const isCustomFieldDisabled = () => !hasPermission('edit-livechat-room-customfields');
Template.visitorEdit.helpers({
visitor() {
return Template.instance().visitor.get();
@ -20,28 +30,16 @@ Template.visitorEdit.helpers({
return hasAtLeastOnePermission(['view-livechat-room-customfields', 'edit-livechat-room-customfields']);
},
canOnlyViewCustomFields() {
return hasPermission('view-livechat-room-customfields') && !hasPermission('edit-livechat-room-customfields');
},
visitorCustomFields() {
const customFields = Template.instance().customFields.get();
if (!customFields || customFields.length === 0) {
return [];
}
const fields = [];
const visitor = Template.instance().visitor.get();
const { livechatData = {} } = visitor || {};
customFields.forEach((field) => {
if (field.visibility !== 'hidden' && field.scope === 'visitor') {
const value = livechatData[field._id] ? livechatData[field._id] : '';
fields.push({ name: field._id, label: field.label, value });
}
});
return fields;
return getCustomFieldsByScope(customFields, livechatData, 'visitor', isCustomFieldDisabled());
},
room() {
@ -54,18 +52,10 @@ Template.visitorEdit.helpers({
return [];
}
const fields = [];
const room = Template.instance().room.get();
const { livechatData = {} } = room || {};
customFields.forEach((field) => {
if (field.visibility !== 'hidden' && field.scope === 'room') {
const value = livechatData[field._id] ? livechatData[field._id] : '';
fields.push({ name: field._id, label: field.label, value });
}
});
return fields;
return getCustomFieldsByScope(customFields, livechatData, 'room', isCustomFieldDisabled());
},
email() {

@ -0,0 +1,22 @@
<template name="visitorEditCustomField">
<div class="rc-input rc-form-group rc-form-group--small">
<label class="rc-input__label">
<div class="rc-input__title">{{label}}</div>
{{#if $eq type 'select'}}
<div class="rc-select">
<select name="{{name}}" class="rc-select__element" data-visitorLivechatData="{{$eq scope 'visitor'}}" data-roomLivechatData="{{$eq scope 'room'}}" disabled="{{disabled}}">
<option value=""></option>
{{#each optionsList}}
<option value="{{.}}" selected="{{selectedField . ..}}">{{.}}</option>
{{/each}}
</select>
{{> icon block="rc-select__arrow" icon="arrow-down" }}
</div>
{{else}}
<div class="rc-input__wrapper">
<input class="rc-input__element" type="text" name="{{name}}" autocomplete="off" data-visitorLivechatData="{{$eq scope 'visitor'}}" data-roomLivechatData="{{$eq scope 'room'}}" disabled="{{disabled}}" value="{{value}}">
</div>
{{/if}}
</label>
</div>
</template>

@ -0,0 +1,17 @@
import { Template } from 'meteor/templating';
import './visitorEditCustomField.html';
Template.visitorEditCustomField.helpers({
optionsList() {
if (!this.options) {
return [];
}
return this.options.split(',');
},
selectedField(current) {
const { fieldData: { value } } = Template.currentData();
return value.trim() === current.trim();
},
});

@ -6,6 +6,7 @@ import './app/livechatRoomTagSelector';
import './app/tabbar/agentEdit';
import './app/tabbar/agentInfo';
import './app/tabbar/visitorEdit';
import './app/tabbar/visitorEditCustomField';
import './app/tabbar/visitorForward';
import './app/tabbar/visitorHistory';
import './app/tabbar/visitorInfo';

@ -146,7 +146,7 @@ export function settings() {
}
export async function getExtraConfigInfo(room) {
return callbacks.run('livechat.onLoadConfigApi', room);
return callbacks.run('livechat.onLoadConfigApi', { room });
}
export function onCheckRoomParams(params) {

@ -27,8 +27,9 @@ API.v1.addRoute('livechat/config', {
room = findOpenRoom(token);
agent = room && room.servedBy && findAgent(room.servedBy._id);
}
const extraConfig = room && Promise.await(getExtraConfigInfo(room));
Object.assign(config, { online: status, guest, room, agent }, extraConfig);
const extra = Promise.await(getExtraConfigInfo(room));
const { config: extraConfig = {} } = extra || {};
Object.assign(config, { online: status, guest, room, agent }, { ...extraConfig });
return API.v1.success({ config });
} catch (e) {

@ -17,7 +17,7 @@ Meteor.methods({
check(customFieldData, Match.ObjectIncluding({ field: String, label: String, scope: String, visibility: String, regexp: String }));
if (!/^[0-9a-zA-Z-_]+$/.test(customFieldData.field)) {
throw new Meteor.Error('error-invalid-custom-field-nmae', 'Invalid custom field name. Use only letters, numbers, hyphens and underscores.', { method: 'livechat:saveCustomField' });
throw new Meteor.Error('error-invalid-custom-field-name', 'Invalid custom field name. Use only letters, numbers, hyphens and underscores.', { method: 'livechat:saveCustomField' });
}
if (_id) {
@ -26,6 +26,8 @@ Meteor.methods({
throw new Meteor.Error('error-invalid-custom-field', 'Custom Field Not found', { method: 'livechat:saveCustomField' });
}
}
return LivechatCustomField.createOrUpdateCustomField(_id, customFieldData.field, customFieldData.label, customFieldData.scope, customFieldData.visibility, { regexp: customFieldData.regexp });
const { field, label, scope, visibility, ...extraData } = customFieldData;
return LivechatCustomField.createOrUpdateCustomField(_id, field, label, scope, visibility, { ...extraData });
},
});

@ -0,0 +1,42 @@
<template name="livechatCustomFieldsAdditionalForm">
<div class="input-line">
<label>{{_ "Required"}}</label>
<div>
<select name="required" class="rc-input__element additional-field">
<option value="false" selected="{{$eq customField.required false}}">{{_ "No"}}</option>
<option value="true" selected="{{$eq customField.required true}}">{{_ "Yes"}}</option>
</select>
</div>
</div>
<div class="input-line">
<label>{{_ "Type"}}</label>
<div>
<select name="type" class="rc-input__element additional-field">
<option value="input" selected="{{$eq customField.type 'input'}}">{{_ "Input"}}</option>
<option value="select" selected="{{$eq customField.type 'select'}}">{{_ "Select"}}</option>
</select>
</div>
</div>
<div class="input-line">
<label>{{_ "Default_value"}}</label>
<div>
<input type="text" class="rc-input__element additional-field" name="defaultValue" value="{{customField.defaultValue}}" placeholder="{{_ "Default_value"}}" />
</div>
</div>
<div class="input-line">
<label>{{_ "Options"}}</label>
<div>
<input type="text" class="rc-input__element additional-field" name="options" disabled="{{$neq customField.type 'select'}}" value="{{customField.options}}" placeholder="{{_ "Livechat_custom_fields_options_placeholder"}}" />
</div>
</div>
<div class="input-line">
<label>{{_ "Public"}}</label>
<div>
<select name="public" class="rc-input__element additional-field" disabled="{{$eq customField.visibility 'hidden'}}">
<option value="false" selected="{{$eq customField.public false}}">{{_ "No"}}</option>
<option value="true" selected="{{$eq customField.public true}}">{{_ "Yes"}}</option>
</select>
</div>
<div class="settings-description secondary-font-color">{{{_ "Livechat_custom_fields_public_description"}}}</div>
</div>
</template>

@ -0,0 +1,37 @@
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import './livechatCustomFieldsAdditionalForm.html';
import { t } from '../../../../../../../app/utils/client';
Template.livechatCustomFieldsAdditionalForm.helpers({
customField() {
return Template.instance().customField.get();
},
});
Template.livechatCustomFieldsAdditionalForm.onCreated(function() {
this.customField = new ReactiveVar({});
this.autorun(() => {
// To make this template reactive we expect a ReactiveVar through the data property,
// because the parent form may not be rerender, only the dynamic template data
this.customField.set({ ...this.data.get() });
});
});
Template.livechatCustomFieldsAdditionalForm.events({
'change .additional-field'(e, instance) {
const { target: { name, value } } = e;
instance.customField.set({ ...instance.customField.get(), [name]: value });
},
'blur [name="options"]'(e) {
const { currentTarget: { value } } = e;
if (value.trim() !== '' && !/^([a-zA-Z0-9-_ ]+)(,\s*[a-zA-Z0-9-_ ]+)*$/i.test(value)) {
toastr.error(t('error-invalid-value'));
e.currentTarget.focus();
}
},
});

@ -1,12 +1,14 @@
import { addCustomFormTemplate } from '../../../../../../app/livechat/client/views/app/customTemplates/register';
import './customTemplates/livechatCustomFieldsAdditionalForm';
import './customTemplates/livechatDepartmentCustomFieldsForm';
import './customTemplates/livechatAgentEditCustomFieldsForm';
import './customTemplates/livechatAgentInfoCustomFieldsForm';
import './customTemplates/visitorEditCustomFieldsForm';
import './customTemplates/visitorInfoCustomForm';
addCustomFormTemplate('livechatDepartmentForm', 'livechatDepartmentCustomFieldsForm');
addCustomFormTemplate('livechatAgentEditForm', 'livechatAgentEditCustomFieldsForm');
addCustomFormTemplate('livechatAgentInfoForm', 'livechatAgentInfoCustomFieldsForm');
addCustomFormTemplate('livechatCustomFieldsAdditionalForm', 'livechatCustomFieldsAdditionalForm');
addCustomFormTemplate('livechatDepartmentForm', 'livechatDepartmentCustomFieldsForm');
addCustomFormTemplate('livechatVisitorEditForm', 'visitorEditCustomFieldsForm');
addCustomFormTemplate('livechatVisitorInfo', 'visitorInfoCustomForm');

@ -0,0 +1,17 @@
import './addDepartmentAncestors';
import './afterForwardChatToDepartment';
import './beforeListTags';
import './setPredictedVisitorAbandonmentTime';
import './beforeForwardRoomToDepartment';
import './afterRemoveDepartment';
import './onLoadForwardDepartmentRestrictions';
import './afterTakeInquiry';
import './beforeNewInquiry';
import './beforeNewRoom';
import './beforeRoutingChat';
import './checkAgentBeforeTakeInquiry';
import './onCheckRoomParamsApi';
import './onLoadConfigApi';
import './onSetUserStatusLivechat';
import './onCloseLivechat';
import './onSaveVisitorInfo';

@ -1,35 +1,16 @@
import { callbacks } from '../../../../../app/callbacks';
import { settings } from '../../../../../app/settings';
import { LivechatInquiry } from '../../../../../app/models/server';
import { normalizeQueueInfo } from '../lib/Helper';
import { getLivechatQueueInfo, getLivechatCustomFields } from '../lib/Helper';
callbacks.add('livechat.onLoadConfigApi', async (room) => {
if (!room) {
return null;
}
callbacks.add('livechat.onLoadConfigApi', async (options = {}) => {
const { room } = options;
if (!settings.get('Livechat_waiting_queue')) {
return null;
}
const queueInfo = await getLivechatQueueInfo(room);
const customFields = getLivechatCustomFields();
const { _id: rid } = room;
const inquiry = LivechatInquiry.findOneByRoomId(rid);
if (!inquiry) {
return null;
}
const config = {
...queueInfo && { queueInfo },
...customFields && { customFields },
};
const { _id, status } = inquiry;
if (status !== 'queued') {
return null;
}
const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ _id });
let queueInfo;
if (inq) {
const { position, department } = inq;
queueInfo = await normalizeQueueInfo({ position, department });
}
return { queueInfo };
return Object.assign({ config }, options);
}, callbacks.priority.MEDIUM, 'livechat-on-load-config-api');

@ -1,13 +1,6 @@
import { Meteor } from 'meteor/meteor';
import '../lib/messageTypes';
import './hooks/addDepartmentAncestors';
import './hooks/afterForwardChatToDepartment';
import './hooks/beforeListTags';
import './hooks/setPredictedVisitorAbandonmentTime';
import './hooks/beforeForwardRoomToDepartment';
import './hooks/afterRemoveDepartment';
import './hooks/onLoadForwardDepartmentRestrictions';
import './methods/addMonitor';
import './methods/getUnitsFromUserRoles';
import './methods/removeMonitor';
@ -38,6 +31,7 @@ import { onLicense } from '../../license/server';
onLicense('livechat-enterprise', () => {
require('./api');
require('./hooks');
const { createPermissions } = require('./permissions');
const { createSettings } = require('./settings');

@ -3,7 +3,14 @@ import { Match, check } from 'meteor/check';
import moment from 'moment';
import { hasRole } from '../../../../../app/authorization';
import { LivechatDepartment, Users, LivechatInquiry, LivechatRooms, Messages } from '../../../../../app/models/server';
import {
LivechatDepartment,
Users,
LivechatInquiry,
LivechatRooms,
Messages,
LivechatCustomField,
} from '../../../../../app/models/server';
import { Rooms as RoomRaw } from '../../../../../app/models/server/raw';
import { settings } from '../../../../../app/settings';
import { Livechat } from '../../../../../app/livechat/server/lib/Livechat';
@ -217,3 +224,37 @@ export const updatePriorityInquiries = (priority) => {
updateInquiryQueuePriority(room._id, priority);
});
};
export const getLivechatCustomFields = () => {
const customFields = LivechatCustomField.find({ visibility: 'visible', scope: 'visitor', public: true }).fetch();
return customFields.map(({ _id, label, regexp, required = false, type, defaultValue = null, options }) => ({ _id, label, regexp, required, type, defaultValue, ...options && options !== '' && { options: options.split(',') } }));
};
export const getLivechatQueueInfo = async (room) => {
if (!room) {
return null;
}
if (!settings.get('Livechat_waiting_queue')) {
return null;
}
const { _id: rid } = room;
const inquiry = LivechatInquiry.findOneByRoomId(rid, { fields: { _id: 1, status: 1 } });
if (!inquiry) {
return null;
}
const { _id, status } = inquiry;
if (status !== 'queued') {
return null;
}
const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ _id });
if (!inq) {
return null;
}
return normalizeQueueInfo(inq);
};

@ -11,6 +11,7 @@
"Canned_Response_Removed": "Canned Response Removed",
"Canned_Responses_Enable": "Enable Canned Responses",
"Closed_automatically": "Closed automatically by the system",
"Default_value": "Default value",
"Edit_Tag": "Edit Tag",
"Edit_Unit": "Edit Unit",
"Edit_Priority": "Edit Priority",
@ -34,6 +35,8 @@
"List_of_departments_for_forward": "List of departments allowed for forwarding (Optional)",
"List_of_departments_for_forward_description": "Allow to set a restricted list of departments that can receive chats from this department",
"Livechat_abandoned_rooms_closed_custom_message": "Custom message when room is automatically closed by visitor inactivity",
"Livechat_custom_fields_options_placeholder": "Comma-separated list used to select a pre-configured value. Spaces between elements are not accepted.",
"Livechat_custom_fields_public_description": "Public custom fields will be displayed in external applications, such as Livechat, etc.",
"Livechat_last_chatted_agent_routing": "Last-Chatted Agent Preferred",
"Livechat_last_chatted_agent_routing_Description": "The Last-Chatted Agent setting allocates chats to the agent who previously interacted with the same visitor if the agent is available when the chat starts.",
"Livechat_Monitors": "Monitors",

@ -5,6 +5,7 @@
"Add_monitor": "Adicionar Monitor",
"Available_departments": "Departamentos disponíveis",
"Closed_automatically": "Fechado automaticamente pelo sistema",
"Default_value": "Default value",
"Edit_Tag": "Editar Tag",
"Edit_Unit": "Editar Unidade",
"Edit_Priority": "Editar Prioridade",
@ -26,6 +27,8 @@
"List_of_departments_for_forward": "Lista de departamentos permitidos para o encaminhamento(Opcional).",
"List_of_departments_for_forward_description": "Permite definir uma lista restrita de departamentos que podem receber conversas desse departamento.",
"Livechat_abandoned_rooms_closed_custom_message": "Mensagem customizada para usar quando a sala for automaticamente fechada por abandono do visitante",
"Livechat_custom_fields_options_placeholder": "Lista separada por vírgula usada para selecionar um valor pré-configurado. Espaços entre elementos não são aceitos.",
"Livechat_custom_fields_public_description": "Os custom fields públicos serão exibidos nos aplicativos externos, como o Livechat etc.",
"Livechat_last_chatted_agent_routing": "Agente preferido pela última conversa",
"Livechat_last_chatted_agent_routing_Description": "Agente preferido pela última conversa aloca bate-papos para o agente que interagiu anteriormente com o mesmo visitante, caso o agente esteja disponível quando o bate-papo for iniciado.",
"Livechat_Monitors": "Monitores",

@ -1406,6 +1406,7 @@
"error-invalid-urls": "Invalid URLs",
"error-invalid-user": "Invalid user",
"error-invalid-username": "Invalid username",
"error-invalid-value": "Invalid valid",
"error-invalid-webhook-response": "The webhook URL responded with a status other than 200",
"error-message-deleting-blocked": "Message deleting is blocked",
"error-message-editing-blocked": "Message editing is blocked",
@ -2802,7 +2803,7 @@
"Refresh_oauth_services": "Refresh OAuth Services",
"Refresh_your_page_after_install_to_enable_screen_sharing": "Refresh your page after install to enable screen sharing",
"Regenerate_codes": "Regenerate codes",
"Regexp_validation": "Validation by RegExp",
"Regexp_validation": "Validation by regular expression",
"Register": "Register a new account",
"Register_Server": "Register Server",
"Register_Server_Info": "Use the preconfigured gateways and proxies provided by Rocket.Chat Technologies Corp.",
@ -3634,6 +3635,7 @@
"UTF8_Names_Slugify": "UTF8 Names Slugify",
"UTF8_Names_Validation": "UTF8 Names Validation",
"UTF8_Names_Validation_Description": "RegExp that will be used to validate usernames and channel names",
"Validation": "Validation",
"Value_messages": "__value__ messages",
"Value_users": "__value__ users",
"Validate_email_address": "Validate Email Address",

@ -2541,6 +2541,7 @@
"Refresh_oauth_services": "Atualize os Serviços OAuth",
"Refresh_your_page_after_install_to_enable_screen_sharing": "Atualize sua página após a instalação para permitir o compartilhamento de tela",
"Regenerate_codes": "Regenerar códigos",
"Regexp_validation": "Validação por expressão regular",
"Register": "Registrar-se",
"Register_Server": "Registrar Servidor",
"Register_Server_Info": "Use os gateways e proxies pré-configurados fornecidos pela Rocket.Chat Technologies Corp.",
@ -3272,6 +3273,7 @@
"UTF8_Names_Validation": "Validação de Nomes UTF8",
"UTF8_Names_Validation_Description": "Não permitir caracteres especiais e espaços. Você pode usar - _ e. mas não no final do nome",
"Validate_email_address": "Validar endereço de e-mail",
"Validation": "Validação",
"Verification_email_body": "Você criou uma conta com sucesso em [Site_Name]. Por favor, clique no botão abaixo para confirmar seu endereço de e-mail e finalizar o registro.",
"Verification": "Verificação",
"Verification_Description": "Você pode usar os seguintes placeholders:<br/><ul><li>[Verification_Url] para o URL de verificação.</li><li>[nome], [fname], [lname] para o nome completo, primeiro nome ou sobrenome do usuário, respectivamente.</li><li>[email] para o email do usuário.</li><li>[Site_Name] e [Site_URL] para o Nome da Aplicação e o URL, respectivamente.</li></ul>",

@ -1256,6 +1256,7 @@
"error-invalid-triggerWords": "Gerador de palavras inválido",
"error-invalid-urls": "URLs inválidas",
"error-invalid-user": "Utilizador inválido",
"error-invalid-value": "Valor inválido",
"error-invalid-username": "Nome de utilizador Inválido",
"error-invalid-webhook-response": "O URL do webhook respondeu com um status diferente de 200",
"error-message-deleting-blocked": "Exclusão de mensagens está bloqueada",

Loading…
Cancel
Save