diff --git a/app/livechat/client/views/app/livechatDepartmentForm.js b/app/livechat/client/views/app/livechatDepartmentForm.js
index 59bbcec1228..203450b9d40 100644
--- a/app/livechat/client/views/app/livechatDepartmentForm.js
+++ b/app/livechat/client/views/app/livechatDepartmentForm.js
@@ -10,7 +10,7 @@ import { t, handleError } from '../../../../utils';
import { hasPermission } from '../../../../authorization';
import { getCustomFormTemplate } from './customTemplates/register';
import './livechatDepartmentForm.html';
-import { APIClient } from '../../../../utils/client';
+import { APIClient, roomTypes } from '../../../../utils/client';
Template.livechatDepartmentForm.helpers({
department() {
@@ -91,6 +91,21 @@ Template.livechatDepartmentForm.helpers({
hasChatClosingTags() {
return [...Template.instance().chatClosingTags.get()].length > 0;
},
+ onClickTagOfflineMessageChannel() {
+ return Template.instance().onClickTagOfflineMessageChannel;
+ },
+ selectedOfflineMessageChannel() {
+ return Template.instance().offlineMessageChannel.get();
+ },
+ onSelectOfflineMessageChannel() {
+ return Template.instance().onSelectOfflineMessageChannel;
+ },
+ offlineMessageChannelModifier() {
+ return (filter, text = '') => {
+ const f = filter.get();
+ return `#${ f.length === 0 ? text : text.replace(new RegExp(filter.get()), (part) => `
`) }`;
+ };
+ },
});
Template.livechatDepartmentForm.events({
@@ -111,6 +126,9 @@ Template.livechatDepartmentForm.events({
const showOnOfflineForm = instance.$('input[name=showOnOfflineForm]:checked').val();
const requestTagBeforeClosingChat = instance.$('input[name=requestTagBeforeClosingChat]:checked').val();
const chatClosingTags = instance.chatClosingTags.get();
+ const [offlineMessageChannel] = instance.offlineMessageChannel.get();
+ const offlineMessageChannelName = (offlineMessageChannel && roomTypes.getRoomName(offlineMessageChannel.t, offlineMessageChannel)) || '';
+
if (enabled !== '1' && enabled !== '0') {
return toastr.error(t('Please_select_enabled_yes_or_no'));
}
@@ -132,6 +150,7 @@ Template.livechatDepartmentForm.events({
requestTagBeforeClosingChat: requestTagBeforeClosingChat === '1',
email: email.trim(),
chatClosingTags,
+ offlineMessageChannelName,
};
}
@@ -242,7 +261,17 @@ Template.livechatDepartmentForm.onCreated(async function() {
this.chatClosingTags = new ReactiveVar([]);
this.availableTags = new ReactiveVar([]);
this.availableDepartmentTags = new ReactiveVar([]);
+ this.offlineMessageChannel = new ReactiveVar([]);
+ this.onClickTagOfflineMessageChannel = () => {
+ this.offlineMessageChannel.set([]);
+ };
+
+ this.onSelectOfflineMessageChannel = async ({ item }) => {
+ const { room } = await APIClient.v1.get(`rooms.info?roomId=${ item._id }`);
+ room.text = room.name;
+ this.offlineMessageChannel.set([room]);
+ };
this.onSelectAgents = ({ item: agent }) => {
this.selectedAgents.set([agent]);
};
@@ -272,4 +301,17 @@ Template.livechatDepartmentForm.onCreated(async function() {
this.loadAvailableTags(id);
}
});
+
+ this.autorun(async () => {
+ const department = this.department.get();
+ let offlineChannel = [];
+ if (department?.offlineMessageChannelName) {
+ const { room } = await APIClient.v1.get(`rooms.info?roomName=${ department?.offlineMessageChannelName }`);
+ if (room) {
+ room.text = room.name;
+ offlineChannel = [{ ...room }];
+ }
+ }
+ this.offlineMessageChannel.set(offlineChannel);
+ });
});
diff --git a/app/livechat/client/views/app/livechatManagers.html b/app/livechat/client/views/app/livechatManagers.html
index ec1468cd13b..d35e2d33be4 100644
--- a/app/livechat/client/views/app/livechatManagers.html
+++ b/app/livechat/client/views/app/livechatManagers.html
@@ -57,7 +57,7 @@
-
{{> avatar username=username}}
+
{{> avatar username=username}}
{{name}}
diff --git a/app/livechat/client/views/app/livechatQueue.html b/app/livechat/client/views/app/livechatQueue.html
index e666828d5c3..2a3f926611e 100644
--- a/app/livechat/client/views/app/livechatQueue.html
+++ b/app/livechat/client/views/app/livechatQueue.html
@@ -84,7 +84,7 @@
-
{{> avatar username=user.username}}
+
{{> avatar username=user.username}}
{{user.username}}
diff --git a/app/livechat/client/views/app/tabbar/externalSearch.html b/app/livechat/client/views/app/tabbar/externalSearch.html
deleted file mode 100644
index a08f388c9d8..00000000000
--- a/app/livechat/client/views/app/tabbar/externalSearch.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
{{_ "Knowledge_Base"}}
-
-
- {{#each messages}}
-
-
- {{msg}}
-
- {{/each}}
- {{#if hasMore}}
-
{{_ "Show_more"}}
- {{/if}}
-
-
-
\ No newline at end of file
diff --git a/app/livechat/client/views/app/tabbar/externalSearch.js b/app/livechat/client/views/app/tabbar/externalSearch.js
deleted file mode 100644
index 2bea37e8c91..00000000000
--- a/app/livechat/client/views/app/tabbar/externalSearch.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Template } from 'meteor/templating';
-import { ReactiveVar } from 'meteor/reactive-var';
-
-import './externalSearch.html';
-import { APIClient } from '../../../../../utils/client';
-
-const MESSAGES_COUNT = 50;
-
-Template.externalSearch.helpers({
- messages() {
- return Template.instance().externalMessages.get();
- },
- hasMore() {
- const instance = Template.instance();
- const externalMessages = instance.externalMessages.get();
- return instance.total.get() > externalMessages.length;
- },
-});
-
-Template.externalSearch.events({
- 'click button.pick-message'(event, instance) {
- event.preventDefault();
- $(`#chat-window-${ instance.roomId } .js-input-message`).val(this.msg).focus();
- },
- 'click .load-more'(e, t) {
- e.preventDefault();
- e.stopPropagation();
- t.offset.set(t.offset.get() + MESSAGES_COUNT);
- },
- 'click .load-more-livechat-external-messages'(event, instance) {
- return instance.offset.set(instance.offset.get() + MESSAGES_COUNT);
- },
-});
-
-Template.externalSearch.onCreated(function() {
- this.roomId = null;
- this.externalMessages = new ReactiveVar([]);
- this.offset = new ReactiveVar(0);
- this.ready = new ReactiveVar(true);
- this.total = new ReactiveVar(0);
-
- this.autorun(async () => {
- this.ready.set(false);
- const offset = this.offset.get();
- this.roomId = Template.currentData().rid;
- if (this.roomId) {
- const { messages, total } = await APIClient.v1.get(`livechat/messages.external/${ this.roomId }?count=${ MESSAGES_COUNT }&offset=${ offset }`);
- this.total.set(total);
- if (offset === 0) {
- this.externalMessages.set(messages);
- } else {
- this.externalMessages.set(this.externalMessages.get().concat(messages));
- }
- }
- this.ready.set(true);
- });
-});
diff --git a/app/livechat/client/views/app/tabbar/visitorEdit.html b/app/livechat/client/views/app/tabbar/visitorEdit.html
index 306d1c31024..778b28a7e1f 100644
--- a/app/livechat/client/views/app/tabbar/visitorEdit.html
+++ b/app/livechat/client/views/app/tabbar/visitorEdit.html
@@ -30,17 +30,11 @@
-
- {{#each visitorCustomFields}}
-
- {{/each}}
+ {{#if canViewCustomFields }}
+ {{#each field in visitorCustomFields}}
+ {{> visitorEditCustomField field }}
+ {{/each}}
+ {{/if}}
{{/with}}
{{#with room}}
@@ -91,17 +85,11 @@
{{/each}}
-
- {{#each roomCustomFields}}
-
- {{/each}}
+ {{#if canViewCustomFields }}
+ {{#each field in roomCustomFields}}
+ {{> visitorEditCustomField field }}
+ {{/each}}
+ {{/if}}
{{/with}}
diff --git a/app/livechat/client/views/app/tabbar/visitorEdit.js b/app/livechat/client/views/app/tabbar/visitorEdit.js
index 21065ec5eae..536d0b77e27 100644
--- a/app/livechat/client/views/app/tabbar/visitorEdit.js
+++ b/app/livechat/client/views/app/tabbar/visitorEdit.js
@@ -4,36 +4,42 @@ import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { t } from '../../../../../utils';
-import { hasRole } from '../../../../../authorization';
+import { hasAtLeastOnePermission, hasPermission, hasRole } from '../../../../../authorization';
import './visitorEdit.html';
import { APIClient } from '../../../../../utils/client';
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();
},
+ canViewCustomFields() {
+ return hasAtLeastOnePermission(['view-livechat-room-customfields', '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() {
@@ -46,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() {
diff --git a/app/livechat/client/views/app/tabbar/visitorEditCustomField.html b/app/livechat/client/views/app/tabbar/visitorEditCustomField.html
new file mode 100644
index 00000000000..bf85704e4b2
--- /dev/null
+++ b/app/livechat/client/views/app/tabbar/visitorEditCustomField.html
@@ -0,0 +1,22 @@
+
+
+
diff --git a/app/livechat/client/views/app/tabbar/visitorEditCustomField.js b/app/livechat/client/views/app/tabbar/visitorEditCustomField.js
new file mode 100644
index 00000000000..f0024c0725d
--- /dev/null
+++ b/app/livechat/client/views/app/tabbar/visitorEditCustomField.js
@@ -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();
+ },
+});
diff --git a/app/livechat/client/views/app/tabbar/visitorInfo.html b/app/livechat/client/views/app/tabbar/visitorInfo.html
index 0ba4aa7d6c9..b802b596261 100644
--- a/app/livechat/client/views/app/tabbar/visitorInfo.html
+++ b/app/livechat/client/views/app/tabbar/visitorInfo.html
@@ -64,10 +64,10 @@
{{_ "Close"}}
{{/if}}
{{#if canForwardGuest}}
-
{{_ "Forward"}}
+
{{_ "Forward"}}
{{/if}}
{{#if canReturnQueue}}
-
{{_ "Return"}}
+
{{_ "Return"}}
{{/if}}
{{/if}}
diff --git a/app/livechat/client/views/app/tabbar/visitorInfo.js b/app/livechat/client/views/app/tabbar/visitorInfo.js
index 6c975b4ee80..a1bdb160024 100644
--- a/app/livechat/client/views/app/tabbar/visitorInfo.js
+++ b/app/livechat/client/views/app/tabbar/visitorInfo.js
@@ -93,6 +93,9 @@ Template.visitorInfo.helpers({
customVisitorFields() {
const customFields = Template.instance().customFields.get();
+ if (!hasAtLeastOnePermission(['view-livechat-room-customfields', 'edit-livechat-room-customfields'])) {
+ return;
+ }
if (!customFields || customFields.length === 0) {
return [];
}
diff --git a/app/livechat/client/views/regular.js b/app/livechat/client/views/regular.js
index 1fc7ddf9441..e2a3f3b8b6f 100644
--- a/app/livechat/client/views/regular.js
+++ b/app/livechat/client/views/regular.js
@@ -5,8 +5,8 @@ import './app/livechatNotSubscribed.html';
import './app/livechatRoomTagSelector';
import './app/tabbar/agentEdit';
import './app/tabbar/agentInfo';
-import './app/tabbar/externalSearch';
import './app/tabbar/visitorEdit';
+import './app/tabbar/visitorEditCustomField';
import './app/tabbar/visitorForward';
import './app/tabbar/visitorHistory';
import './app/tabbar/visitorInfo';
diff --git a/app/livechat/imports/server/rest/agent.js b/app/livechat/imports/server/rest/agent.js
index b928501dc2c..170af7636ff 100644
--- a/app/livechat/imports/server/rest/agent.js
+++ b/app/livechat/imports/server/rest/agent.js
@@ -1,6 +1,6 @@
import { Match, check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findAgentDepartments } from '../../../server/api/lib/agents';
API.v1.addRoute('livechat/agents/:agentId/departments', { authRequired: true }, {
diff --git a/app/livechat/imports/server/rest/appearance.js b/app/livechat/imports/server/rest/appearance.js
index f8345f7d31d..c7fe16243a7 100644
--- a/app/livechat/imports/server/rest/appearance.js
+++ b/app/livechat/imports/server/rest/appearance.js
@@ -1,4 +1,4 @@
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findAppearance } from '../../../server/api/lib/appearance';
API.v1.addRoute('livechat/appearance', { authRequired: true }, {
diff --git a/app/livechat/imports/server/rest/dashboards.js b/app/livechat/imports/server/rest/dashboards.js
index 0e1f8cedb27..af8972296b2 100644
--- a/app/livechat/imports/server/rest/dashboards.js
+++ b/app/livechat/imports/server/rest/dashboards.js
@@ -1,6 +1,6 @@
-import { check } from 'meteor/check';
+import { Match, check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { hasPermission } from '../../../../authorization/server';
import {
findAllChatsStatus,
@@ -20,8 +20,11 @@ API.v1.addRoute('livechat/analytics/dashboards/conversation-totalizers', { authR
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -33,7 +36,7 @@ API.v1.addRoute('livechat/analytics/dashboards/conversation-totalizers', { authR
}
end = new Date(end);
- const totalizers = getConversationsMetrics({ start, end });
+ const totalizers = getConversationsMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
});
@@ -44,8 +47,11 @@ API.v1.addRoute('livechat/analytics/dashboards/agents-productivity-totalizers',
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -57,7 +63,7 @@ API.v1.addRoute('livechat/analytics/dashboards/agents-productivity-totalizers',
}
end = new Date(end);
- const totalizers = getAgentsProductivityMetrics({ start, end });
+ const totalizers = getAgentsProductivityMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
});
@@ -68,8 +74,11 @@ API.v1.addRoute('livechat/analytics/dashboards/chats-totalizers', { authRequired
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -81,7 +90,7 @@ API.v1.addRoute('livechat/analytics/dashboards/chats-totalizers', { authRequired
}
end = new Date(end);
- const totalizers = getChatsMetrics({ start, end });
+ const totalizers = getChatsMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
});
@@ -92,8 +101,11 @@ API.v1.addRoute('livechat/analytics/dashboards/productivity-totalizers', { authR
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -105,7 +117,7 @@ API.v1.addRoute('livechat/analytics/dashboards/productivity-totalizers', { authR
}
end = new Date(end);
- const totalizers = getProductivityMetrics({ start, end });
+ const totalizers = getProductivityMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
@@ -117,8 +129,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats', { authRequired: tr
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -129,7 +144,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats', { authRequired: tr
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
- const result = findAllChatsStatus({ start, end });
+ const result = findAllChatsStatus({ start, end, departmentId });
return API.v1.success(result);
},
@@ -141,8 +156,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-agent', { authRe
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -153,7 +171,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-agent', { authRe
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
- const result = findAllChatMetricsByAgent({ start, end });
+ const result = findAllChatMetricsByAgent({ start, end, departmentId });
return API.v1.success(result);
},
@@ -164,7 +182,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/agents-status', { authRequ
if (!hasPermission(this.userId, 'view-livechat-manager')) {
return API.v1.unauthorized();
}
- const result = findAllAgentsStatus({});
+
+ const { departmentId } = this.requestParams();
+ check(departmentId, Match.Maybe(String));
+
+ const result = findAllAgentsStatus({ departmentId });
return API.v1.success(result);
},
@@ -176,8 +198,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-department', { a
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -188,7 +213,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-department', { a
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
- const result = findAllChatMetricsByDepartment({ start, end });
+ const result = findAllChatMetricsByDepartment({ start, end, departmentId });
return API.v1.success(result);
},
@@ -200,8 +225,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/timings', { authRequired:
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
+ const { departmentId } = this.requestParams();
+
check(start, String);
check(end, String);
+ check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@@ -212,7 +240,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/timings', { authRequired:
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
- const result = findAllResponseTimeMetrics({ start, end });
+ const result = findAllResponseTimeMetrics({ start, end, departmentId });
return API.v1.success(result);
},
diff --git a/app/livechat/imports/server/rest/departments.js b/app/livechat/imports/server/rest/departments.js
index 8a255185f86..9ec4113ba66 100644
--- a/app/livechat/imports/server/rest/departments.js
+++ b/app/livechat/imports/server/rest/departments.js
@@ -1,6 +1,6 @@
import { Match, check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { hasPermission } from '../../../../authorization';
import { LivechatDepartment, LivechatDepartmentAgents } from '../../../../models';
import { Livechat } from '../../../server/lib/Livechat';
diff --git a/app/livechat/imports/server/rest/facebook.js b/app/livechat/imports/server/rest/facebook.js
index cce8c53a716..b4b8efa5503 100644
--- a/app/livechat/imports/server/rest/facebook.js
+++ b/app/livechat/imports/server/rest/facebook.js
@@ -2,7 +2,7 @@ import crypto from 'crypto';
import { Random } from 'meteor/random';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { LivechatRooms, LivechatVisitors } from '../../../../models';
import { settings } from '../../../../settings';
import { Livechat } from '../../../server/lib/Livechat';
diff --git a/app/livechat/imports/server/rest/inquiries.js b/app/livechat/imports/server/rest/inquiries.js
index a553c875fe8..3abfc5eee73 100644
--- a/app/livechat/imports/server/rest/inquiries.js
+++ b/app/livechat/imports/server/rest/inquiries.js
@@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { hasPermission } from '../../../../authorization';
import { Users, LivechatDepartment, LivechatInquiry } from '../../../../models';
import { findInquiries, findOneInquiryByRoomId } from '../../../server/api/lib/inquiries';
diff --git a/app/livechat/imports/server/rest/integrations.js b/app/livechat/imports/server/rest/integrations.js
index 08d9d064892..6b7aed33d89 100644
--- a/app/livechat/imports/server/rest/integrations.js
+++ b/app/livechat/imports/server/rest/integrations.js
@@ -1,4 +1,4 @@
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findIntegrationSettings } from '../../../server/api/lib/integrations';
API.v1.addRoute('livechat/integrations.settings', { authRequired: true }, {
diff --git a/app/livechat/imports/server/rest/messages.js b/app/livechat/imports/server/rest/messages.js
index a40557231b5..f5ad166c8c7 100644
--- a/app/livechat/imports/server/rest/messages.js
+++ b/app/livechat/imports/server/rest/messages.js
@@ -1,7 +1,7 @@
import { check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findExternalMessages } from '../../../server/api/lib/messages';
API.v1.addRoute('livechat/messages.external/:roomId', { authRequired: true }, {
diff --git a/app/livechat/imports/server/rest/officeHour.js b/app/livechat/imports/server/rest/officeHour.js
index f321a31ea5a..7b2cd02497e 100644
--- a/app/livechat/imports/server/rest/officeHour.js
+++ b/app/livechat/imports/server/rest/officeHour.js
@@ -1,4 +1,4 @@
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findLivechatOfficeHours } from '../../../server/api/lib/officeHour';
API.v1.addRoute('livechat/office-hours', { authRequired: true }, {
diff --git a/app/livechat/imports/server/rest/queue.js b/app/livechat/imports/server/rest/queue.js
index d5f319f2e3c..43b586431ea 100644
--- a/app/livechat/imports/server/rest/queue.js
+++ b/app/livechat/imports/server/rest/queue.js
@@ -1,4 +1,4 @@
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findQueueMetrics } from '../../../server/api/lib/queue';
API.v1.addRoute('livechat/queue', { authRequired: true }, {
diff --git a/app/livechat/imports/server/rest/rooms.js b/app/livechat/imports/server/rest/rooms.js
index d7556006597..052da5db396 100644
--- a/app/livechat/imports/server/rest/rooms.js
+++ b/app/livechat/imports/server/rest/rooms.js
@@ -1,7 +1,7 @@
import { Match, check } from 'meteor/check';
import { hasPermission } from '../../../../authorization/server';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findRooms } from '../../../server/api/lib/rooms';
const validateDateParams = (property, date) => {
diff --git a/app/livechat/imports/server/rest/sms.js b/app/livechat/imports/server/rest/sms.js
index edebecb583a..f813e7def80 100644
--- a/app/livechat/imports/server/rest/sms.js
+++ b/app/livechat/imports/server/rest/sms.js
@@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { LivechatRooms, LivechatVisitors, LivechatDepartment } from '../../../../models';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { SMS } from '../../../../sms';
import { Livechat } from '../../../server/lib/Livechat';
diff --git a/app/livechat/imports/server/rest/triggers.js b/app/livechat/imports/server/rest/triggers.js
index ca6ebe7a12a..de3d0b57f27 100644
--- a/app/livechat/imports/server/rest/triggers.js
+++ b/app/livechat/imports/server/rest/triggers.js
@@ -1,6 +1,6 @@
import { check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findTriggers, findTriggerById } from '../../../server/api/lib/triggers';
API.v1.addRoute('livechat/triggers', { authRequired: true }, {
diff --git a/app/livechat/imports/server/rest/upload.js b/app/livechat/imports/server/rest/upload.js
index 3d28f420402..4c27811749a 100644
--- a/app/livechat/imports/server/rest/upload.js
+++ b/app/livechat/imports/server/rest/upload.js
@@ -6,7 +6,7 @@ import { settings } from '../../../../settings';
import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models';
import { fileUploadIsValidContentType } from '../../../../utils';
import { FileUpload } from '../../../../file-upload';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
let maxFileSize;
diff --git a/app/livechat/imports/server/rest/users.js b/app/livechat/imports/server/rest/users.js
index 04e1815b4f0..f0c88aa25c5 100644
--- a/app/livechat/imports/server/rest/users.js
+++ b/app/livechat/imports/server/rest/users.js
@@ -2,7 +2,7 @@ import { check } from 'meteor/check';
import _ from 'underscore';
import { hasPermission } from '../../../../authorization';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { Users } from '../../../../models';
import { Livechat } from '../../../server/lib/Livechat';
import { findAgents, findManagers } from '../../../server/api/lib/users';
diff --git a/app/livechat/imports/server/rest/visitors.js b/app/livechat/imports/server/rest/visitors.js
index 42b5a20b25d..cc38c3bc400 100644
--- a/app/livechat/imports/server/rest/visitors.js
+++ b/app/livechat/imports/server/rest/visitors.js
@@ -1,8 +1,8 @@
import { check } from 'meteor/check';
-import { API } from '../../../../api';
-import { findVisitorInfo, findVisitedPages, findChatHistory } from '../../../server/api/lib/visitors';
+import { API } from '../../../../api/server';
+import { findVisitorInfo, findVisitedPages, findChatHistory, findVisitorsToAutocomplete } from '../../../server/api/lib/visitors';
API.v1.addRoute('livechat/visitors.info', { authRequired: true }, {
get() {
@@ -62,3 +62,17 @@ API.v1.addRoute('livechat/visitors.chatHistory/room/:roomId/visitor/:visitorId',
return API.v1.success(history);
},
});
+
+API.v1.addRoute('livechat/visitors.autocomplete', { authRequired: true }, {
+ get() {
+ const { selector } = this.queryParams;
+ if (!selector) {
+ return API.v1.failure('The \'selector\' param is required');
+ }
+
+ return API.v1.success(Promise.await(findVisitorsToAutocomplete({
+ userId: this.userId,
+ selector: JSON.parse(selector),
+ })));
+ },
+});
diff --git a/app/livechat/lib/messageTypes.js b/app/livechat/lib/messageTypes.js
index c68e120f054..8f870923bd2 100644
--- a/app/livechat/lib/messageTypes.js
+++ b/app/livechat/lib/messageTypes.js
@@ -1,12 +1,6 @@
-import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import { Livechat } from 'meteor/rocketchat:livechat';
import { MessageTypes } from '../../ui-utils';
-import { actionLinks } from '../../action-links';
-import { Notifications } from '../../notifications';
-import { Messages, LivechatRooms } from '../../models';
-import { settings } from '../../settings';
MessageTypes.registerType({
id: 'livechat_navigation_history',
@@ -60,29 +54,3 @@ MessageTypes.registerType({
system: true,
message: 'New_videocall_request',
});
-
-actionLinks.register('createLivechatCall', function(message, params, instance) {
- if (Meteor.isClient) {
- instance.tabBar.open('video');
- }
-});
-
-actionLinks.register('denyLivechatCall', function(message/* , params*/) {
- if (Meteor.isServer) {
- const user = Meteor.user();
-
- Messages.createWithTypeRoomIdMessageAndUser('command', message.rid, 'endCall', user);
- Notifications.notifyRoom(message.rid, 'deleteMessage', { _id: message._id });
-
- const language = user.language || settings.get('Language') || 'en';
-
- Livechat.closeRoom({
- user,
- room: LivechatRooms.findOneById(message.rid),
- comment: TAPi18n.__('Videocall_declined', { lng: language }),
- });
- Meteor.defer(() => {
- Messages.setHiddenById(message._id);
- });
- }
-});
diff --git a/app/livechat/server/api/lib/integrations.js b/app/livechat/server/api/lib/integrations.js
index 942dada044f..bf9397523f1 100644
--- a/app/livechat/server/api/lib/integrations.js
+++ b/app/livechat/server/api/lib/integrations.js
@@ -6,7 +6,7 @@ export async function findIntegrationSettings({ userId }) {
throw new Error('error-not-authorized');
}
- const settings = await Settings.findByIds(['Livechat_webhookUrl', 'Livechat_secret_token', 'Livechat_webhook_on_close', 'Livechat_webhook_on_offline_msg', 'Livechat_webhook_on_visitor_message', 'Livechat_webhook_on_agent_message']).toArray();
+ const settings = await Settings.findByIds(['Livechat_webhookUrl', 'Livechat_secret_token', 'Livechat_webhook_on_start', 'Livechat_webhook_on_close', 'Livechat_webhook_on_chat_taken', 'Livechat_webhook_on_chat_queued', 'Livechat_webhook_on_forward', 'Livechat_webhook_on_offline_msg', 'Livechat_webhook_on_visitor_message', 'Livechat_webhook_on_agent_message']).toArray();
return { settings };
}
diff --git a/app/livechat/server/api/lib/livechat.js b/app/livechat/server/api/lib/livechat.js
index b1893f9a0f8..4626ce819b3 100644
--- a/app/livechat/server/api/lib/livechat.js
+++ b/app/livechat/server/api/lib/livechat.js
@@ -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) {
diff --git a/app/livechat/server/api/lib/transfer.js b/app/livechat/server/api/lib/transfer.js
new file mode 100644
index 00000000000..60070dfc264
--- /dev/null
+++ b/app/livechat/server/api/lib/transfer.js
@@ -0,0 +1,27 @@
+import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
+import { Messages } from '../../../../models/server/raw';
+
+const normalizeTransferHistory = ({ transferData }) => transferData;
+export async function findLivechatTransferHistory({ userId, rid, pagination: { offset, count, sort } }) {
+ if (!await hasPermissionAsync(userId, 'view-livechat-rooms')) {
+ throw new Error('error-not-authorized');
+ }
+
+ const cursor = await Messages.find({ rid, t: 'livechat_transfer_history' }, {
+ fields: { transferData: 1 },
+ sort: sort || { ts: 1 },
+ skip: offset,
+ limit: count,
+ });
+
+ const total = await cursor.count();
+ const messages = await cursor.toArray();
+ const history = messages.map(normalizeTransferHistory);
+
+ return {
+ history,
+ count: history.length,
+ offset,
+ total,
+ };
+}
diff --git a/app/livechat/server/api/lib/visitors.js b/app/livechat/server/api/lib/visitors.js
index ad7cb3da93e..d1c7477fdd5 100644
--- a/app/livechat/server/api/lib/visitors.js
+++ b/app/livechat/server/api/lib/visitors.js
@@ -72,3 +72,27 @@ export async function findChatHistory({ userId, roomId, visitorId, pagination: {
total,
};
}
+
+export async function findVisitorsToAutocomplete({ userId, selector }) {
+ if (!await hasPermissionAsync(userId, 'view-l-room')) {
+ return { items: [] };
+ }
+ const { exceptions = [], conditions = {} } = selector;
+
+ const options = {
+ fields: {
+ _id: 1,
+ name: 1,
+ username: 1,
+ },
+ limit: 10,
+ sort: {
+ name: 1,
+ },
+ };
+
+ const items = await LivechatVisitors.findByNameRegexWithExceptionsAndConditions(selector.term, exceptions, conditions, options).toArray();
+ return {
+ items,
+ };
+}
diff --git a/app/livechat/server/api/rest.js b/app/livechat/server/api/rest.js
index 3731e72f6b6..a63794bf1db 100644
--- a/app/livechat/server/api/rest.js
+++ b/app/livechat/server/api/rest.js
@@ -8,3 +8,4 @@ import './v1/message.js';
import './v1/customField.js';
import './v1/room.js';
import './v1/videoCall.js';
+import './v1/transfer.js';
diff --git a/app/livechat/server/api/v1/agent.js b/app/livechat/server/api/v1/agent.js
index 68ceef87da1..7e232905854 100644
--- a/app/livechat/server/api/v1/agent.js
+++ b/app/livechat/server/api/v1/agent.js
@@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findRoom, findGuest, findAgent, findOpenRoom } from '../lib/livechat';
import { Livechat } from '../../lib/Livechat';
diff --git a/app/livechat/server/api/v1/config.js b/app/livechat/server/api/v1/config.js
index a1f4bab0340..85ef7a10a86 100644
--- a/app/livechat/server/api/v1/config.js
+++ b/app/livechat/server/api/v1/config.js
@@ -1,6 +1,6 @@
import { Match, check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findGuest, settings, online, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat';
API.v1.addRoute('livechat/config', {
@@ -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) {
diff --git a/app/livechat/server/api/v1/customField.js b/app/livechat/server/api/v1/customField.js
index f64266d3be6..3b19e832bc6 100644
--- a/app/livechat/server/api/v1/customField.js
+++ b/app/livechat/server/api/v1/customField.js
@@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findGuest } from '../lib/livechat';
import { Livechat } from '../../lib/Livechat';
import { findLivechatCustomFields, findCustomFieldById } from '../lib/customFields';
diff --git a/app/livechat/server/api/v1/message.js b/app/livechat/server/api/v1/message.js
index e2362baf6d3..0811bc8324f 100644
--- a/app/livechat/server/api/v1/message.js
+++ b/app/livechat/server/api/v1/message.js
@@ -4,7 +4,7 @@ import { Random } from 'meteor/random';
import { Messages, LivechatRooms, LivechatVisitors } from '../../../../models';
import { hasPermission } from '../../../../authorization';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { loadMessageHistory } from '../../../../lib';
import { findGuest, findRoom, normalizeHttpHeaderData } from '../lib/livechat';
import { Livechat } from '../../lib/Livechat';
diff --git a/app/livechat/server/api/v1/offlineMessage.js b/app/livechat/server/api/v1/offlineMessage.js
index 1859b5dde3a..8264228c97e 100644
--- a/app/livechat/server/api/v1/offlineMessage.js
+++ b/app/livechat/server/api/v1/offlineMessage.js
@@ -1,7 +1,7 @@
import { Match, check } from 'meteor/check';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { Livechat } from '../../lib/Livechat';
API.v1.addRoute('livechat/offline.message', {
@@ -12,10 +12,11 @@ API.v1.addRoute('livechat/offline.message', {
email: String,
message: String,
department: Match.Maybe(String),
+ host: Match.Maybe(String),
});
- const { name, email, message, department } = this.bodyParams;
- if (!Livechat.sendOfflineMessage({ name, email, message, department })) {
+ const { name, email, message, department, host } = this.bodyParams;
+ if (!Livechat.sendOfflineMessage({ name, email, message, department, host })) {
return API.v1.failure({ message: TAPi18n.__('Error_sending_livechat_offline_message') });
}
diff --git a/app/livechat/server/api/v1/pageVisited.js b/app/livechat/server/api/v1/pageVisited.js
index e5ef7c42ba6..4f8c638e614 100644
--- a/app/livechat/server/api/v1/pageVisited.js
+++ b/app/livechat/server/api/v1/pageVisited.js
@@ -1,7 +1,7 @@
import { Match, check } from 'meteor/check';
import _ from 'underscore';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { Livechat } from '../../lib/Livechat';
API.v1.addRoute('livechat/page.visited', {
diff --git a/app/livechat/server/api/v1/room.js b/app/livechat/server/api/v1/room.js
index 83af5ffe1b6..5ec9ef1cf06 100644
--- a/app/livechat/server/api/v1/room.js
+++ b/app/livechat/server/api/v1/room.js
@@ -5,7 +5,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { settings as rcSettings } from '../../../../settings';
import { Messages, LivechatRooms } from '../../../../models';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findGuest, findRoom, getRoom, settings, findAgent, onCheckRoomParams } from '../lib/livechat';
import { Livechat } from '../../lib/Livechat';
import { normalizeTransferredByData } from '../../lib/Helper';
diff --git a/app/livechat/server/api/v1/transcript.js b/app/livechat/server/api/v1/transcript.js
index 02c0d9d2756..f8f3c923d25 100644
--- a/app/livechat/server/api/v1/transcript.js
+++ b/app/livechat/server/api/v1/transcript.js
@@ -1,7 +1,7 @@
import { check } from 'meteor/check';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { Livechat } from '../../lib/Livechat';
API.v1.addRoute('livechat/transcript', {
diff --git a/app/livechat/server/api/v1/transfer.js b/app/livechat/server/api/v1/transfer.js
new file mode 100644
index 00000000000..aa3fb7facd0
--- /dev/null
+++ b/app/livechat/server/api/v1/transfer.js
@@ -0,0 +1,36 @@
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
+
+import { LivechatRooms } from '../../../../models';
+import { API } from '../../../../api/server';
+import { findLivechatTransferHistory } from '../lib/transfer';
+
+API.v1.addRoute('livechat/transfer.history/:rid', { authRequired: true }, {
+ get() {
+ check(this.urlParams, {
+ rid: String,
+ });
+
+ const { rid } = this.urlParams;
+
+ const room = LivechatRooms.findOneById(rid, { _id: 1 });
+ if (!room) {
+ throw new Meteor.Error('invalid-room');
+ }
+
+ const { offset, count } = this.getPaginationItems();
+ const { sort } = this.parseJsonQuery();
+
+ const history = Promise.await(findLivechatTransferHistory({
+ userId: this.userId,
+ rid,
+ pagination: {
+ offset,
+ count,
+ sort,
+ },
+ }));
+
+ return API.v1.success(history);
+ },
+});
diff --git a/app/livechat/server/api/v1/videoCall.js b/app/livechat/server/api/v1/videoCall.js
index 0aaa231da65..56159d8c349 100644
--- a/app/livechat/server/api/v1/videoCall.js
+++ b/app/livechat/server/api/v1/videoCall.js
@@ -4,7 +4,7 @@ import { Random } from 'meteor/random';
import { Messages } from '../../../../models';
import { settings as rcSettings } from '../../../../settings';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findGuest, getRoom, settings } from '../lib/livechat';
API.v1.addRoute('livechat/video.call/:token', {
diff --git a/app/livechat/server/api/v1/visitor.js b/app/livechat/server/api/v1/visitor.js
index f34930a1b9c..98007540876 100644
--- a/app/livechat/server/api/v1/visitor.js
+++ b/app/livechat/server/api/v1/visitor.js
@@ -3,7 +3,7 @@ import { Match, check } from 'meteor/check';
import { LivechatRooms, LivechatVisitors, LivechatCustomField } from '../../../../models';
import { hasPermission } from '../../../../authorization';
-import { API } from '../../../../api';
+import { API } from '../../../../api/server';
import { findGuest, normalizeHttpHeaderData } from '../lib/livechat';
import { Livechat } from '../../lib/Livechat';
diff --git a/app/livechat/server/config.js b/app/livechat/server/config.js
index 4af508e8c01..6bba3e28d52 100644
--- a/app/livechat/server/config.js
+++ b/app/livechat/server/config.js
@@ -192,6 +192,13 @@ Meteor.startup(function() {
secret: true,
});
+ settings.add('Livechat_webhook_on_start', false, {
+ type: 'boolean',
+ group: 'Omnichannel',
+ section: 'CRM_Integration',
+ i18nLabel: 'Send_request_on_chat_start',
+ });
+
settings.add('Livechat_webhook_on_close', false, {
type: 'boolean',
group: 'Omnichannel',
@@ -199,6 +206,27 @@ Meteor.startup(function() {
i18nLabel: 'Send_request_on_chat_close',
});
+ settings.add('Livechat_webhook_on_chat_taken', false, {
+ type: 'boolean',
+ group: 'Omnichannel',
+ section: 'CRM_Integration',
+ i18nLabel: 'Send_request_on_chat_taken',
+ });
+
+ settings.add('Livechat_webhook_on_chat_queued', false, {
+ type: 'boolean',
+ group: 'Omnichannel',
+ section: 'CRM_Integration',
+ i18nLabel: 'Send_request_on_chat_queued',
+ });
+
+ settings.add('Livechat_webhook_on_forward', false, {
+ type: 'boolean',
+ group: 'Omnichannel',
+ section: 'CRM_Integration',
+ i18nLabel: 'Send_request_on_forwarding',
+ });
+
settings.add('Livechat_webhook_on_offline_msg', false, {
type: 'boolean',
group: 'Omnichannel',
@@ -250,31 +278,6 @@ Meteor.startup(function() {
i18nLabel: 'Lead_capture_phone_regex',
});
- settings.add('Livechat_Knowledge_Enabled', false, {
- type: 'boolean',
- group: 'Omnichannel',
- section: 'Knowledge_Base',
- public: true,
- i18nLabel: 'Enabled',
- });
-
- settings.add('Livechat_Knowledge_Apiai_Key', '', {
- type: 'string',
- group: 'Omnichannel',
- section: 'Knowledge_Base',
- public: true,
- i18nLabel: 'Apiai_Key',
- secret: true,
- });
-
- settings.add('Livechat_Knowledge_Apiai_Language', 'en', {
- type: 'string',
- group: 'Omnichannel',
- section: 'Knowledge_Base',
- public: true,
- i18nLabel: 'Apiai_Language',
- });
-
settings.add('Livechat_history_monitor_type', 'url', {
type: 'select',
group: 'Omnichannel',
@@ -366,6 +369,22 @@ Meteor.startup(function() {
i18nDescription: 'Domains_allowed_to_embed_the_livechat_widget',
});
+ settings.add('Livechat_OfflineMessageToChannel_enabled', false, {
+ type: 'boolean',
+ group: 'Omnichannel',
+ section: 'Livechat',
+ public: true,
+ });
+
+ settings.add('Livechat_OfflineMessageToChannel_channel_name', '', {
+ type: 'string',
+ group: 'Omnichannel',
+ section: 'Livechat',
+ public: true,
+ enableQuery: { _id: 'Livechat_OfflineMessageToChannel_enabled', value: true },
+ i18nLabel: 'Channel_name',
+ });
+
settings.add('Livechat_Facebook_Enabled', false, {
type: 'boolean',
group: 'Omnichannel',
diff --git a/app/livechat/server/hooks/externalMessage.js b/app/livechat/server/hooks/externalMessage.js
deleted file mode 100644
index 41bb98396fa..00000000000
--- a/app/livechat/server/hooks/externalMessage.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { HTTP } from 'meteor/http';
-import _ from 'underscore';
-
-import { settings } from '../../../settings';
-import { callbacks } from '../../../callbacks';
-import { SystemLogger } from '../../../logger';
-import { LivechatExternalMessage } from '../../../models/server';
-import { normalizeMessageFileUpload } from '../../../utils/server/functions/normalizeMessageFileUpload';
-
-let knowledgeEnabled = false;
-let apiaiKey = '';
-let apiaiLanguage = 'en';
-settings.get('Livechat_Knowledge_Enabled', function(key, value) {
- knowledgeEnabled = value;
-});
-settings.get('Livechat_Knowledge_Apiai_Key', function(key, value) {
- apiaiKey = value;
-});
-settings.get('Livechat_Knowledge_Apiai_Language', function(key, value) {
- apiaiLanguage = value;
-});
-
-callbacks.add('afterSaveMessage', function(message, room) {
- // skips this callback if the message was edited
- if (!message || message.editedAt) {
- return message;
- }
-
- if (!knowledgeEnabled) {
- return message;
- }
-
- if (!(typeof room.t !== 'undefined' && room.t === 'l' && room.v && room.v.token)) {
- return message;
- }
-
- if (message.file) {
- message = normalizeMessageFileUpload(message);
- }
-
- // if the message hasn't a token, it was not sent by the visitor, so ignore it
- if (!message.token) {
- return message;
- }
-
- try {
- const response = HTTP.post('https://api.api.ai/api/query?v=20150910', {
- data: {
- query: message.msg,
- lang: apiaiLanguage,
- sessionId: room._id,
- },
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- Authorization: `Bearer ${ apiaiKey }`,
- },
- });
-
- if (response.data && response.data.status.code === 200 && !_.isEmpty(response.data.result.fulfillment.speech)) {
- LivechatExternalMessage.insert({
- rid: message.rid,
- msg: response.data.result.fulfillment.speech,
- orig: message._id,
- ts: new Date(),
- });
- }
- } catch (e) {
- SystemLogger.error('Error using Api.ai ->', e);
- }
-
- return message;
-}, callbacks.priority.LOW, 'externalWebHook');
diff --git a/app/livechat/server/hooks/offlineMessageToChannel.js b/app/livechat/server/hooks/offlineMessageToChannel.js
new file mode 100644
index 00000000000..8f63447e2f0
--- /dev/null
+++ b/app/livechat/server/hooks/offlineMessageToChannel.js
@@ -0,0 +1,58 @@
+import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
+
+import { callbacks } from '../../../callbacks';
+import { settings } from '../../../settings';
+import { sendMessage } from '../../../lib';
+import { LivechatDepartment, Rooms, Users } from '../../../models';
+
+callbacks.add('livechat.offlineMessage', (data) => {
+ if (!settings.get('Livechat_OfflineMessageToChannel_enabled')) {
+ return data;
+ }
+
+ let channelName = settings.get('Livechat_OfflineMessageToChannel_channel_name');
+ let departmentName;
+ const { name, email, department, message: text, host } = data;
+ if (department && department !== '') {
+ const dept = LivechatDepartment.findOneById(department, { fields: { name: 1, offlineMessageChannelName: 1 } });
+ departmentName = dept?.name;
+ if (dept?.offlineMessageChannelName) {
+ channelName = dept.offlineMessageChannelName;
+ }
+ }
+
+ if (!channelName || channelName === '') {
+ return data;
+ }
+
+ const room = Rooms.findOneByName(channelName, { fields: { t: 1, archived: 1 } });
+ if (!room || room.archived || room.closedAt) {
+ return data;
+ }
+
+ const user = Users.findOneById('rocket.cat', { fields: { username: 1 } });
+ if (!user) {
+ return data;
+ }
+
+ const lng = settings.get('Language') || 'en';
+
+ let msg = `${ TAPi18n.__('New_Livechat_offline_message_has_been_sent', { lng }) }: \n`;
+ if (host && host !== '') {
+ msg = msg.concat(`${ TAPi18n.__('Sent_from', { lng }) }: ${ host } \n`);
+ }
+ msg = msg.concat(`${ TAPi18n.__('Visitor_Name', { lng }) }: ${ name } \n`);
+ msg = msg.concat(`${ TAPi18n.__('Visitor_Email', { lng }) }: ${ email } \n`);
+ if (departmentName) {
+ msg = msg.concat(`${ TAPi18n.__('Department', { lng }) }: ${ departmentName } \n`);
+ }
+ msg = msg.concat(`${ TAPi18n.__('Message', { lng }) }: ${ text } \n`);
+
+ const message = {
+ rid: room._id,
+ msg,
+ groupable: false,
+ };
+
+ sendMessage(user, message, room, true);
+}, callbacks.priority.MEDIUM, 'livechat-send-email-offline-message-to-channel');
diff --git a/app/livechat/server/hooks/sendToCRM.js b/app/livechat/server/hooks/sendToCRM.js
index df4da478cfa..2b80cd7408e 100644
--- a/app/livechat/server/hooks/sendToCRM.js
+++ b/app/livechat/server/hooks/sendToCRM.js
@@ -4,19 +4,52 @@ import { Messages, LivechatRooms } from '../../../models';
import { Livechat } from '../lib/Livechat';
import { normalizeMessageFileUpload } from '../../../utils/server/functions/normalizeMessageFileUpload';
-const msgNavType = 'livechat_navigation_history';
-const crmEnabled = () => {
- const webhookUrl = settings.get('Livechat_webhookUrl');
- return webhookUrl !== '' && webhookUrl !== undefined;
-};
+const msgNavType = 'livechat_navigation_history';
+const msgClosingType = 'livechat-close';
+
+let sendNavHistoryMessageEnabled = false;
+let sendNavHistoryWebhookEnabled = false;
+let crmWebhookUrl = '';
+settings.get('Livechat_Visitor_navigation_as_a_message', (key, value) => {
+ sendNavHistoryMessageEnabled = value;
+});
+settings.get('Send_visitor_navigation_history_livechat_webhook_request', (key, value) => {
+ sendNavHistoryWebhookEnabled = value;
+});
+settings.get('Livechat_webhookUrl', (key, value) => {
+ crmWebhookUrl = value;
+});
+
+const crmEnabled = () => crmWebhookUrl !== '' && crmWebhookUrl !== undefined;
const sendMessageType = (msgType) => {
- const sendNavHistory = settings.get('Livechat_Visitor_navigation_as_a_message') && settings.get('Send_visitor_navigation_history_livechat_webhook_request');
-
- return sendNavHistory && msgType === msgNavType;
+ switch (msgType) {
+ case msgClosingType:
+ return true;
+ case msgNavType:
+ return sendNavHistoryMessageEnabled && sendNavHistoryWebhookEnabled;
+ default:
+ return false;
+ }
};
+const getAdditionalFieldsByType = (type, room) => {
+ const { departmentId, servedBy, closedAt, closedBy, closer, oldServedBy, oldDepartmentId } = room;
+ switch (type) {
+ case 'LivechatSessionStarted':
+ case 'LivechatSessionQueued':
+ return { departmentId };
+ case 'LivechatSession':
+ return { departmentId, servedBy, closedAt, closedBy, closer };
+ case 'LivechatSessionTaken':
+ return { departmentId, servedBy };
+ case 'LivechatSessionForwarded':
+ return { departmentId, servedBy, oldDepartmentId, oldServedBy };
+ default:
+ return {};
+ }
+};
function sendToCRM(type, room, includeMessages = true) {
if (crmEnabled() === false) {
return room;
@@ -56,6 +89,10 @@ function sendToCRM(type, room, includeMessages = true) {
msg.navigation = message.navigation;
}
+ if (message.t === msgClosingType) {
+ msg.closingMessage = true;
+ }
+
if (message.file) {
msg.file = message.file;
msg.attachments = message.attachments;
@@ -66,7 +103,10 @@ function sendToCRM(type, room, includeMessages = true) {
});
}
- const response = Livechat.sendRequest(postData);
+ const additionalData = getAdditionalFieldsByType(type, room);
+ const responseData = Object.assign(postData, additionalData);
+
+ const response = Livechat.sendRequest(responseData);
if (response && response.data && response.data.data) {
LivechatRooms.saveCRMDataByRoomId(room._id, response.data.data);
@@ -83,6 +123,57 @@ callbacks.add('livechat.closeRoom', (room) => {
return sendToCRM('LivechatSession', room);
}, callbacks.priority.MEDIUM, 'livechat-send-crm-close-room');
+callbacks.add('livechat.newRoom', (room) => {
+ if (!settings.get('Livechat_webhook_on_start')) {
+ return room;
+ }
+
+ return sendToCRM('LivechatSessionStart', room);
+}, callbacks.priority.MEDIUM, 'livechat-send-crm-start-room');
+
+callbacks.add('livechat.afterTakeInquiry', (inquiry) => {
+ if (!settings.get('Livechat_webhook_on_chat_taken')) {
+ return inquiry;
+ }
+
+ const { rid } = inquiry;
+ const room = LivechatRooms.findOneById(rid);
+
+ return sendToCRM('LivechatSessionTaken', room);
+}, callbacks.priority.MEDIUM, 'livechat-send-crm-room-taken');
+
+callbacks.add('livechat.chatQueued', (room) => {
+ if (!settings.get('Livechat_webhook_on_chat_queued')) {
+ return room;
+ }
+
+ return sendToCRM('LivechatSessionQueued', room);
+}, callbacks.priority.MEDIUM, 'livechat-send-crm-room-queued');
+
+callbacks.add('livechat.afterForwardChatToAgent', (params) => {
+ const { rid, oldServedBy } = params;
+ if (!settings.get('Livechat_webhook_on_forward')) {
+ return params;
+ }
+
+ const originalRoom = LivechatRooms.findOneById(rid);
+ const room = Object.assign(originalRoom, { oldServedBy });
+ sendToCRM('LivechatSessionForwarded', room);
+ return params;
+}, callbacks.priority.MEDIUM, 'livechat-send-crm-room-forwarded-to-agent');
+
+callbacks.add('livechat.afterForwardChatToDepartment', (params) => {
+ const { rid, oldDepartmentId } = params;
+ if (!settings.get('Livechat_webhook_on_forward')) {
+ return params;
+ }
+
+ const originalRoom = LivechatRooms.findOneById(rid);
+ const room = Object.assign(originalRoom, { oldDepartmentId });
+ sendToCRM('LivechatSessionForwarded', room);
+ return params;
+}, callbacks.priority.MEDIUM, 'livechat-send-crm-room-forwarded-to-department');
+
callbacks.add('livechat.saveInfo', (room) => {
// Do not send to CRM if the chat is still open
if (room.open) {
diff --git a/app/livechat/server/index.js b/app/livechat/server/index.js
index f6deeb0f893..527bc59802d 100644
--- a/app/livechat/server/index.js
+++ b/app/livechat/server/index.js
@@ -7,10 +7,10 @@ import './config';
import './roomType';
import './hooks/beforeCloseRoom';
import './hooks/beforeGetNextAgent';
-import './hooks/externalMessage';
import './hooks/leadCapture';
import './hooks/markRoomResponded';
import './hooks/offlineMessage';
+import './hooks/offlineMessageToChannel';
import './hooks/RDStation';
import './hooks/saveAnalyticsData';
import './hooks/sendToCRM';
@@ -84,5 +84,6 @@ import './sendMessageBySMS';
import './api';
import './api/rest';
import './externalFrame';
+import './lib/messageTypes';
export { Livechat } from './lib/Livechat';
diff --git a/app/livechat/server/lib/Analytics.js b/app/livechat/server/lib/Analytics.js
index 6832a8f982d..b55486751f8 100644
--- a/app/livechat/server/lib/Analytics.js
+++ b/app/livechat/server/lib/Analytics.js
@@ -18,7 +18,9 @@ export const Analytics = {
return;
}
- return this.AgentOverviewData[options.chartOptions.name](from, to);
+ const { departmentId } = options;
+
+ return this.AgentOverviewData[options.chartOptions.name](from, to, departmentId);
},
getAnalyticsChartData(options) {
@@ -43,6 +45,8 @@ export const Analytics = {
dataPoints: [],
};
+ const { departmentId } = options;
+
if (from.diff(to) === 0) { // data for single day
for (let m = moment(from); m.diff(to, 'days') <= 0; m.add(1, 'hours')) {
const hour = m.format('H');
@@ -53,7 +57,7 @@ export const Analytics = {
lt: moment(m).add(1, 'hours'),
};
- data.dataPoints.push(this.ChartData[options.chartOptions.name](date));
+ data.dataPoints.push(this.ChartData[options.chartOptions.name](date, departmentId));
}
} else {
for (let m = moment(from); m.diff(to, 'days') <= 0; m.add(1, 'days')) {
@@ -64,7 +68,7 @@ export const Analytics = {
lt: moment(m).add(1, 'days'),
};
- data.dataPoints.push(this.ChartData[options.chartOptions.name](date));
+ data.dataPoints.push(this.ChartData[options.chartOptions.name](date, departmentId));
}
}
@@ -74,6 +78,7 @@ export const Analytics = {
getAnalyticsOverviewData(options) {
const from = moment(options.daterange.from);
const to = moment(options.daterange.to);
+ const { departmentId } = options;
if (!(moment(from).isValid() && moment(to).isValid())) {
console.log('livechat:getAnalyticsOverviewData => Invalid dates');
@@ -85,7 +90,7 @@ export const Analytics = {
return;
}
- return this.OverviewData[options.analyticsOptions.name](from, to);
+ return this.OverviewData[options.analyticsOptions.name](from, to, departmentId);
},
ChartData: {
@@ -95,15 +100,15 @@ export const Analytics = {
*
* @returns {Integer}
*/
- Total_conversations(date) {
- return LivechatRooms.getTotalConversationsBetweenDate('l', date);
+ Total_conversations(date, departmentId) {
+ return LivechatRooms.getTotalConversationsBetweenDate('l', date, { departmentId });
},
- Avg_chat_duration(date) {
+ Avg_chat_duration(date, departmentId) {
let total = 0;
let count = 0;
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.chatDuration) {
total += metrics.chatDuration;
count++;
@@ -114,10 +119,10 @@ export const Analytics = {
return Math.round(avgCD * 100) / 100;
},
- Total_messages(date) {
+ Total_messages(date, departmentId) {
let total = 0;
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ msgs }) => {
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ msgs }) => {
if (msgs) {
total += msgs;
}
@@ -132,10 +137,10 @@ export const Analytics = {
*
* @returns {Double}
*/
- Avg_first_response_time(date) {
+ Avg_first_response_time(date, departmentId) {
let frt = 0;
let count = 0;
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.response && metrics.response.ft) {
frt += metrics.response.ft;
count++;
@@ -152,10 +157,10 @@ export const Analytics = {
*
* @returns {Double}
*/
- Best_first_response_time(date) {
+ Best_first_response_time(date, departmentId) {
let maxFrt;
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.response && metrics.response.ft) {
maxFrt = maxFrt ? Math.min(maxFrt, metrics.response.ft) : metrics.response.ft;
}
@@ -172,10 +177,10 @@ export const Analytics = {
*
* @returns {Double}
*/
- Avg_response_time(date) {
+ Avg_response_time(date, departmentId) {
let art = 0;
let count = 0;
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.response && metrics.response.avg) {
art += metrics.response.avg;
count++;
@@ -193,10 +198,10 @@ export const Analytics = {
*
* @returns {Double}
*/
- Avg_reaction_time(date) {
+ Avg_reaction_time(date, departmentId) {
let arnt = 0;
let count = 0;
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.reaction && metrics.reaction.ft) {
arnt += metrics.reaction.ft;
count++;
@@ -237,7 +242,7 @@ export const Analytics = {
*
* @returns {Array[Object]}
*/
- Conversations(from, to) {
+ Conversations(from, to, departmentId) {
let totalConversations = 0; // Total conversations
let openConversations = 0; // open conversations
let totalMessages = 0; // total msgs
@@ -261,7 +266,7 @@ export const Analytics = {
lt: moment(m).add(1, 'days'),
};
- const result = Promise.await(LivechatRooms.getAnalyticsBetweenDate(date).toArray());
+ const result = Promise.await(LivechatRooms.getAnalyticsBetweenDate(date, { departmentId }).toArray());
totalConversations += result.length;
result.forEach(summarize(m));
@@ -278,7 +283,7 @@ export const Analytics = {
gte: h,
lt: moment(h).add(1, 'hours'),
};
- Promise.await(LivechatRooms.getAnalyticsBetweenDate(date).toArray()).forEach(({
+ Promise.await(LivechatRooms.getAnalyticsBetweenDate(date, { departmentId }).toArray()).forEach(({
msgs,
}) => {
const dayHour = h.format('H'); // @int : 0, 1, ... 23
@@ -319,7 +324,7 @@ export const Analytics = {
*
* @returns {Array[Object]}
*/
- Productivity(from, to) {
+ Productivity(from, to, departmentId) {
let avgResponseTime = 0;
let firstResponseTime = 0;
let avgReactionTime = 0;
@@ -330,7 +335,7 @@ export const Analytics = {
lt: to.add(1, 'days'),
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
}) => {
if (metrics && metrics.response && metrics.reaction) {
@@ -395,7 +400,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
- Total_conversations(from, to) {
+ Total_conversations(from, to, departmentId) {
let total = 0;
const agentConversations = new Map(); // stores total conversations for each agent
const date = {
@@ -412,7 +417,7 @@ export const Analytics = {
data: [],
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
servedBy,
}) => {
if (servedBy) {
@@ -446,7 +451,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
- Avg_chat_duration(from, to) {
+ Avg_chat_duration(from, to, departmentId) {
const agentChatDurations = new Map(); // stores total conversations for each agent
const date = {
gte: from,
@@ -462,7 +467,7 @@ export const Analytics = {
data: [],
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@@ -506,7 +511,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
- Total_messages(from, to) {
+ Total_messages(from, to, departmentId) {
const agentMessages = new Map(); // stores total conversations for each agent
const date = {
gte: from,
@@ -522,7 +527,7 @@ export const Analytics = {
data: [],
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
servedBy,
msgs,
}) => {
@@ -550,7 +555,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
- Avg_first_response_time(from, to) {
+ Avg_first_response_time(from, to, departmentId) {
const agentAvgRespTime = new Map(); // stores avg response time for each agent
const date = {
gte: from,
@@ -566,7 +571,7 @@ export const Analytics = {
data: [],
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@@ -610,7 +615,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
- Best_first_response_time(from, to) {
+ Best_first_response_time(from, to, departmentId) {
const agentFirstRespTime = new Map(); // stores avg response time for each agent
const date = {
gte: from,
@@ -626,7 +631,7 @@ export const Analytics = {
data: [],
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@@ -662,7 +667,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
- Avg_response_time(from, to) {
+ Avg_response_time(from, to, departmentId) {
const agentAvgRespTime = new Map(); // stores avg response time for each agent
const date = {
gte: from,
@@ -678,7 +683,7 @@ export const Analytics = {
data: [],
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@@ -722,7 +727,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
- Avg_reaction_time(from, to) {
+ Avg_reaction_time(from, to, departmentId) {
const agentAvgReactionTime = new Map(); // stores avg reaction time for each agent
const date = {
gte: from,
@@ -738,7 +743,7 @@ export const Analytics = {
data: [],
};
- LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
+ LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
diff --git a/app/livechat/server/lib/Helper.js b/app/livechat/server/lib/Helper.js
index bfa83dc0d74..8a7c9ada3a2 100644
--- a/app/livechat/server/lib/Helper.js
+++ b/app/livechat/server/lib/Helper.js
@@ -7,6 +7,7 @@ import { Livechat } from './Livechat';
import { RoutingManager } from './RoutingManager';
import { callbacks } from '../../../callbacks/server';
import { settings } from '../../../settings';
+import { Apps, AppEvents } from '../../../apps/server';
export const createLivechatRoom = (rid, name, guest, roomInfo = {}, extraData = {}) => {
check(rid, String);
@@ -42,6 +43,8 @@ export const createLivechatRoom = (rid, name, guest, roomInfo = {}, extraData =
}, extraRoomInfo);
const roomId = Rooms.insert(room);
+
+ Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomStarted, room);
callbacks.run('livechat.newRoom', room);
return roomId;
};
@@ -157,8 +160,38 @@ export const createLivechatQueueView = () => {
};
export const removeAgentFromSubscription = (rid, { _id, username }) => {
+ const room = LivechatRooms.findOneById(rid);
+ const user = Users.findOneById(_id);
+
Subscriptions.removeByRoomIdAndUserId(rid, _id);
Messages.createUserLeaveWithRoomIdAndUser(rid, { _id, username });
+
+ Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentUnassigned, { room, user });
+};
+
+export const parseAgentCustomFields = (customFields) => {
+ if (!customFields) {
+ return;
+ }
+
+ const externalCustomFields = () => {
+ const accountCustomFields = settings.get('Accounts_CustomFields');
+ if (!accountCustomFields || accountCustomFields.trim() === '') {
+ return [];
+ }
+
+ try {
+ const parseCustomFields = JSON.parse(accountCustomFields);
+ return Object.keys(parseCustomFields)
+ .filter((customFieldKey) => parseCustomFields[customFieldKey].sendToIntegrations === true);
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ };
+
+ const externalCF = externalCustomFields();
+ return Object.keys(customFields).reduce((newObj, key) => (externalCF.includes(key) ? { ...newObj, [key]: customFields[key] } : newObj), null);
};
export const normalizeAgent = (agentId) => {
@@ -166,7 +199,15 @@ export const normalizeAgent = (agentId) => {
return;
}
- return settings.get('Livechat_show_agent_info') ? Users.getAgentInfo(agentId) : { hiddenInfo: true };
+ if (!settings.get('Livechat_show_agent_info')) {
+ return { hiddenInfo: true };
+ }
+
+ const agent = Users.getAgentInfo(agentId);
+ const { customFields: agentCustomFields, ...extraData } = agent;
+ const customFields = parseAgentCustomFields(agentCustomFields);
+
+ return Object.assign(extraData, { ...customFields && { customFields } });
};
export const dispatchAgentDelegated = (rid, agentId) => {
@@ -218,6 +259,7 @@ export const forwardRoomToAgent = async (room, transferData) => {
Messages.createUserJoinWithRoomIdAndUser(rid, { _id: servedBy._id, username: servedBy.username });
}
+ callbacks.run('livechat.afterForwardChatToAgent', { rid, servedBy, oldServedBy });
return true;
};
diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js
index 501e5340659..ae5004a8450 100644
--- a/app/livechat/server/lib/Livechat.js
+++ b/app/livechat/server/lib/Livechat.js
@@ -1,6 +1,5 @@
import dns from 'dns';
-import { AppInterface } from '@rocket.chat/apps-engine/server/compiler';
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { Random } from 'meteor/random';
@@ -31,14 +30,14 @@ import {
LivechatInquiry,
} from '../../../models';
import { Logger } from '../../../logger';
-import { addUserRoles, hasRole, removeUserFromRoles } from '../../../authorization';
+import { addUserRoles, hasPermission, hasRole, removeUserFromRoles } from '../../../authorization';
import * as Mailer from '../../../mailer';
import { sendMessage } from '../../../lib/server/functions/sendMessage';
import { updateMessage } from '../../../lib/server/functions/updateMessage';
import { deleteMessage } from '../../../lib/server/functions/deleteMessage';
import { FileUpload } from '../../../file-upload/server';
-import { normalizeTransferredByData } from './Helper';
-import { Apps } from '../../../apps/server';
+import { normalizeTransferredByData, parseAgentCustomFields } from './Helper';
+import { Apps, AppEvents } from '../../../apps/server';
export const Livechat = {
Analytics,
@@ -118,8 +117,9 @@ export const Livechat = {
}
if (room == null) {
+ const defaultAgent = callbacks.run('livechat.checkDefaultAgentOnNewRoom', agent, guest);
// if no department selected verify if there is at least one active and pick the first
- if (!agent && !guest.department) {
+ if (!defaultAgent && !guest.department) {
const department = this.getRequiredDepartment();
if (department) {
@@ -128,7 +128,7 @@ export const Livechat = {
}
// delegate room creation to QueueManager
- room = await QueueManager.requestRoom({ guest, message, roomInfo, agent, extraData });
+ room = await QueueManager.requestRoom({ guest, message, roomInfo, agent: defaultAgent, extraData });
newRoom = true;
}
@@ -277,7 +277,7 @@ export const Livechat = {
return false;
},
- saveGuest({ _id, name, email, phone, livechatData = {} }) {
+ saveGuest({ _id, name, email, phone, livechatData = {} }, userId) {
const updateData = {};
if (name) {
@@ -292,20 +292,23 @@ export const Livechat = {
const customFields = {};
const fields = LivechatCustomField.find({ scope: 'visitor' });
- fields.forEach((field) => {
- if (!livechatData.hasOwnProperty(field._id)) {
- return;
- }
- const value = s.trim(livechatData[field._id]);
- if (value !== '' && field.regexp !== undefined && field.regexp !== '') {
- const regexp = new RegExp(field.regexp);
- if (!regexp.test(value)) {
- throw new Meteor.Error(TAPi18n.__('error-invalid-custom-field-value', { field: field.label }));
+
+ if (!userId || hasPermission(userId, 'edit-livechat-room-customfields')) {
+ fields.forEach((field) => {
+ if (!livechatData.hasOwnProperty(field._id)) {
+ return;
}
- }
- customFields[field._id] = value;
- });
- updateData.livechatData = customFields;
+ const value = s.trim(livechatData[field._id]);
+ if (value !== '' && field.regexp !== undefined && field.regexp !== '') {
+ const regexp = new RegExp(field.regexp);
+ if (!regexp.test(value)) {
+ throw new Meteor.Error(TAPi18n.__('error-invalid-custom-field-value', { field: field.label }));
+ }
+ }
+ customFields[field._id] = value;
+ });
+ updateData.livechatData = customFields;
+ }
const ret = LivechatVisitors.saveGuestById(_id, updateData);
Meteor.defer(() => {
@@ -368,7 +371,12 @@ export const Livechat = {
Messages.createCommandWithRoomIdAndUser('promptTranscript', rid, closeData.closedBy);
Meteor.defer(() => {
- Apps.getBridges().getListenerBridge().livechatEvent(AppInterface.ILivechatRoomClosedHandler, room);
+ /**
+ * @deprecated the `AppEvents.ILivechatRoomClosedHandler` event will be removed
+ * in the next major version of the Apps-Engine
+ */
+ Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, room);
+ Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, room);
callbacks.run('livechat.closeRoom', room);
});
@@ -456,25 +464,27 @@ export const Livechat = {
return rcSettings;
},
- saveRoomInfo(roomData, guestData) {
+ saveRoomInfo(roomData, guestData, userId) {
const { livechatData = {} } = roomData;
const customFields = {};
- const fields = LivechatCustomField.find({ scope: 'room' });
- fields.forEach((field) => {
- if (!livechatData.hasOwnProperty(field._id)) {
- return;
- }
- const value = s.trim(livechatData[field._id]);
- if (value !== '' && field.regexp !== undefined && field.regexp !== '') {
- const regexp = new RegExp(field.regexp);
- if (!regexp.test(value)) {
- throw new Meteor.Error(TAPi18n.__('error-invalid-custom-field-value', { field: field.label }));
+ if (!userId || hasPermission(userId, 'edit-livechat-room-customfields')) {
+ const fields = LivechatCustomField.find({ scope: 'room' });
+ fields.forEach((field) => {
+ if (!livechatData.hasOwnProperty(field._id)) {
+ return;
}
- }
- customFields[field._id] = value;
- });
- roomData.livechatData = customFields;
+ const value = s.trim(livechatData[field._id]);
+ if (value !== '' && field.regexp !== undefined && field.regexp !== '') {
+ const regexp = new RegExp(field.regexp);
+ if (!regexp.test(value)) {
+ throw new Meteor.Error(TAPi18n.__('error-invalid-custom-field-value', { field: field.label }));
+ }
+ }
+ customFields[field._id] = value;
+ });
+ roomData.livechatData = customFields;
+ }
if (!LivechatRooms.saveRoomById(roomData)) {
return false;
@@ -599,9 +609,19 @@ export const Livechat = {
if (!inquiry) {
return false;
}
+
const transferredBy = normalizeTransferredByData(user, room);
const transferData = { roomId: rid, scope: 'queue', departmentId, transferredBy };
- return this.saveTransferHistory(room, transferData) && RoutingManager.unassignAgent(inquiry, departmentId);
+ try {
+ this.saveTransferHistory(room, transferData);
+ RoutingManager.unassignAgent(inquiry, departmentId);
+ Meteor.defer(() => callbacks.run('livechat.chatQueued', LivechatRooms.findOneById(rid)));
+ } catch (e) {
+ console.error(e);
+ throw new Meteor.Error('error-returning-inquiry', 'Error returning inquiry to the queue', { method: 'livechat:returnRoomAsInquiry' });
+ }
+
+ return true;
},
sendRequest(postData, callback, trying = 1) {
@@ -629,16 +649,6 @@ export const Livechat = {
const visitor = LivechatVisitors.findOneById(room.v._id);
const agent = Users.findOneById(room.servedBy && room.servedBy._id);
- const externalCustomFields = () => {
- try {
- const customFields = JSON.parse(settings.get('Accounts_CustomFields'));
- return Object.keys(customFields)
- .filter((customFieldKey) => customFields[customFieldKey].sendToIntegrations === true);
- } catch (error) {
- return [];
- }
- };
-
const ua = new UAParser();
ua.setUA(visitor.userAgent);
@@ -666,9 +676,7 @@ export const Livechat = {
};
if (agent) {
- const { customFields: agentCustomFields = {} } = agent;
- const externalCF = externalCustomFields();
- const customFields = Object.keys(agentCustomFields).reduce((newObj, key) => (externalCF.includes(key) ? { ...newObj, [key]: agentCustomFields[key] } : newObj), null);
+ const customFields = parseAgentCustomFields(agent.customFields);
postData.agent = {
_id: agent._id,
@@ -981,13 +989,18 @@ export const Livechat = {
return false;
}
- const message = `${ data.message }`.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1
$2');
+ const { message, name, email, department, host } = data;
+ const emailMessage = `${ message }`.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1
$2');
- const html = `
-
New livechat message
-
Visitor name: ${ data.name }
-
Visitor email: ${ data.email }
-
Message: ${ message }
`;
+ let html = '
New livechat message ';
+ if (host && host !== '') {
+ html = html.concat(`
Sent from: ${ host }
`);
+ }
+ html = html.concat(`
+
Visitor name: ${ name }
+
Visitor email: ${ email }
+
Message: ${ emailMessage }
`,
+ );
let fromEmail = settings.get('From_Email').match(/\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}\b/i);
@@ -998,7 +1011,7 @@ export const Livechat = {
}
if (settings.get('Livechat_validate_offline_email')) {
- const emailDomain = data.email.substr(data.email.lastIndexOf('@') + 1);
+ const emailDomain = email.substr(email.lastIndexOf('@') + 1);
try {
Meteor.wrapAsync(dns.resolveMx)(emailDomain);
@@ -1008,15 +1021,14 @@ export const Livechat = {
}
let emailTo = settings.get('Livechat_offline_email');
- if (data.department) {
- const dep = LivechatDepartment.findOneByIdOrName(data.department);
+ if (department && department !== '') {
+ const dep = LivechatDepartment.findOneByIdOrName(department);
emailTo = dep.email || emailTo;
}
- const from = `${ data.name } - ${ data.email } <${ fromEmail }>`;
- const replyTo = `${ data.name } <${ data.email }>`;
- const subject = `Livechat offline message from ${ data.name }: ${ `${ data.message }`.substring(0, 20) }`;
-
+ const from = `${ name } - ${ email } <${ fromEmail }>`;
+ const replyTo = `${ name } <${ email }>`;
+ const subject = `Livechat offline message from ${ name }: ${ `${ emailMessage }`.substring(0, 20) }`;
this.sendEmail(from, emailTo, replyTo, subject, html);
Meteor.defer(() => {
diff --git a/app/livechat/server/lib/QueueManager.js b/app/livechat/server/lib/QueueManager.js
index d2b9765d44c..46ed51b7a14 100644
--- a/app/livechat/server/lib/QueueManager.js
+++ b/app/livechat/server/lib/QueueManager.js
@@ -35,10 +35,14 @@ export const QueueManager = {
}
inquiry = await callbacks.run('livechat.beforeRouteChat', inquiry, agent);
- if (inquiry.status !== 'ready') {
- return room;
+ if (inquiry.status === 'ready') {
+ return RoutingManager.delegateInquiry(inquiry, agent);
}
- return RoutingManager.delegateInquiry(inquiry, agent);
+ if (inquiry.status === 'queued') {
+ Meteor.defer(() => callbacks.run('livechat.chatQueued', room));
+ }
+
+ return room;
},
};
diff --git a/app/livechat/server/lib/RoutingManager.js b/app/livechat/server/lib/RoutingManager.js
index 3b65ba14cea..5558d1be5d0 100644
--- a/app/livechat/server/lib/RoutingManager.js
+++ b/app/livechat/server/lib/RoutingManager.js
@@ -11,6 +11,7 @@ import { createLivechatSubscription,
} from './Helper';
import { callbacks } from '../../../callbacks/server';
import { LivechatRooms, Rooms, Messages, Users, LivechatInquiry } from '../../../models/server';
+import { Apps, AppEvents } from '../../../apps/server';
export const RoutingManager = {
methodName: null,
@@ -74,8 +75,12 @@ export const RoutingManager = {
Rooms.incUsersCountById(rid);
const user = Users.findOneById(agent.agentId);
+ const room = LivechatRooms.findOneById(rid);
+
Messages.createCommandWithRoomIdAndUser('connected', rid, user);
dispatchAgentDelegated(rid, agent.agentId);
+
+ Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user });
return inquiry;
},
@@ -98,6 +103,7 @@ export const RoutingManager = {
}
const { servedBy } = room;
+
if (servedBy) {
removeAgentFromSubscription(rid, servedBy);
LivechatRooms.removeAgentByRoomId(rid);
@@ -139,7 +145,7 @@ export const RoutingManager = {
LivechatInquiry.takeInquiry(_id);
const inq = this.assignAgent(inquiry, agent);
- callbacks.run('livechat.afterTakeInquiry', inq);
+ callbacks.runAsync('livechat.afterTakeInquiry', inq, agent);
return LivechatRooms.findOneById(rid);
},
diff --git a/app/livechat/server/lib/analytics/dashboards.js b/app/livechat/server/lib/analytics/dashboards.js
index 62fa53e8ee4..e3d9c2db552 100644
--- a/app/livechat/server/lib/analytics/dashboards.js
+++ b/app/livechat/server/lib/analytics/dashboards.js
@@ -43,6 +43,7 @@ const getProductivityMetricsAsync = async ({
analyticsOptions: {
name: 'Productivity',
},
+ departmentId,
});
const averageWaitingTime = await findAllAverageWaitingTimeAsync({
start,
@@ -91,6 +92,7 @@ const getAgentsProductivityMetricsAsync = async ({
analyticsOptions: {
name: 'Conversations',
},
+ departmentId,
});
const totalOfServiceTime = averageOfServiceTime.departments.length;
@@ -166,6 +168,7 @@ const getChatsMetricsAsync = async ({
const getConversationsMetricsAsync = async ({
start,
end,
+ departmentId,
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
@@ -178,9 +181,10 @@ const getConversationsMetricsAsync = async ({
analyticsOptions: {
name: 'Conversations',
},
+ ...departmentId && departmentId !== 'undefined' && { departmentId },
});
const metrics = ['Total_conversations', 'Open_conversations', 'Total_messages'];
- const visitorsCount = await LivechatVisitors.getVisitorsBetweenDate({ start, end }).count();
+ const visitorsCount = await LivechatVisitors.getVisitorsBetweenDate({ start, end, department: departmentId }).count();
return {
totalizers: [
...totalizers.filter((metric) => metrics.includes(metric.title)),
diff --git a/app/livechat/server/lib/messageTypes.js b/app/livechat/server/lib/messageTypes.js
new file mode 100644
index 00000000000..3d32da6f401
--- /dev/null
+++ b/app/livechat/server/lib/messageTypes.js
@@ -0,0 +1,26 @@
+import { Meteor } from 'meteor/meteor';
+import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
+
+import { actionLinks } from '../../../action-links/server';
+import { Notifications } from '../../../notifications/server';
+import { Messages, LivechatRooms } from '../../../models/server';
+import { settings } from '../../../settings/server';
+import { Livechat } from './Livechat';
+
+actionLinks.register('denyLivechatCall', function(message/* , params*/) {
+ const user = Meteor.user();
+
+ Messages.createWithTypeRoomIdMessageAndUser('command', message.rid, 'endCall', user);
+ Notifications.notifyRoom(message.rid, 'deleteMessage', { _id: message._id });
+
+ const language = user.language || settings.get('Language') || 'en';
+
+ Livechat.closeRoom({
+ user,
+ room: LivechatRooms.findOneById(message.rid),
+ comment: TAPi18n.__('Videocall_declined', { lng: language }),
+ });
+ Meteor.defer(() => {
+ Messages.setHiddenById(message._id);
+ });
+});
diff --git a/app/livechat/server/lib/routing/ManualSelection.js b/app/livechat/server/lib/routing/ManualSelection.js
index f217424cf06..1c14fe05e18 100644
--- a/app/livechat/server/lib/routing/ManualSelection.js
+++ b/app/livechat/server/lib/routing/ManualSelection.js
@@ -5,6 +5,7 @@ import { Livechat } from '../Livechat';
import { RoutingManager } from '../RoutingManager';
import { sendNotification } from '../../../../lib/server';
import { LivechatRooms, LivechatInquiry, Users } from '../../../../models/server';
+import { callbacks } from '../../../../callbacks/server';
/* Manual Selection Queuing Method:
*
@@ -82,6 +83,8 @@ class ManualSelection {
});
});
+ Meteor.defer(() => callbacks.run('livechat.chatQueued', room));
+
return agent;
}
}
diff --git a/app/livechat/server/livechat.js b/app/livechat/server/livechat.js
index a60126beda7..7e81220805a 100644
--- a/app/livechat/server/livechat.js
+++ b/app/livechat/server/livechat.js
@@ -26,11 +26,11 @@ WebApp.connectHandlers.use('/livechat', Meteor.bindEnvironment((req, res, next)
const referer = url.parse(req.headers.referer);
if (!_.contains(domainWhiteList, referer.host)) {
- res.setHeader('X-FRAME-OPTIONS', 'DENY');
+ res.setHeader('Content-Security-Policy', 'frame-ancestors \'none\'');
return next();
}
- res.setHeader('X-FRAME-OPTIONS', `ALLOW-FROM ${ referer.protocol }//${ referer.host }`);
+ res.setHeader('Content-Security-Policy', `frame-ancestors ${ referer.protocol }//${ referer.host }`);
}
res.write(indexHtmlWithServerURL);
diff --git a/app/livechat/server/methods/saveCustomField.js b/app/livechat/server/methods/saveCustomField.js
index 4630b967cd2..772ca03cfae 100644
--- a/app/livechat/server/methods/saveCustomField.js
+++ b/app/livechat/server/methods/saveCustomField.js
@@ -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 });
},
});
diff --git a/app/livechat/server/methods/saveInfo.js b/app/livechat/server/methods/saveInfo.js
index a5f2eef0063..c5c1f5eecfd 100644
--- a/app/livechat/server/methods/saveInfo.js
+++ b/app/livechat/server/methods/saveInfo.js
@@ -29,7 +29,7 @@ Meteor.methods({
livechatData: Match.Optional(Object),
}));
- const room = LivechatRooms.findOneById(roomData._id, { t: 1, servedBy: 1 });
+ const room = LivechatRooms.findOneById(roomData._id);
if (room == null || room.t !== 'l') {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'livechat:saveInfo' });
}
@@ -42,7 +42,7 @@ Meteor.methods({
delete guestData.phone;
}
- const ret = Livechat.saveGuest(guestData) && Livechat.saveRoomInfo(roomData, guestData);
+ const ret = Livechat.saveGuest(guestData, userId) && Livechat.saveRoomInfo(roomData, guestData, userId);
const user = Meteor.users.findOne({ _id: userId }, { fields: { _id: 1, username: 1 } });
diff --git a/app/livechat/server/methods/saveIntegration.js b/app/livechat/server/methods/saveIntegration.js
index 521b01e9dac..b28ab334256 100644
--- a/app/livechat/server/methods/saveIntegration.js
+++ b/app/livechat/server/methods/saveIntegration.js
@@ -18,10 +18,26 @@ Meteor.methods({
settings.updateById('Livechat_secret_token', s.trim(values.Livechat_secret_token));
}
+ if (typeof values.Livechat_webhook_on_start !== 'undefined') {
+ settings.updateById('Livechat_webhook_on_start', !!values.Livechat_webhook_on_start);
+ }
+
if (typeof values.Livechat_webhook_on_close !== 'undefined') {
settings.updateById('Livechat_webhook_on_close', !!values.Livechat_webhook_on_close);
}
+ if (typeof values.Livechat_webhook_on_chat_taken !== 'undefined') {
+ settings.updateById('Livechat_webhook_on_chat_taken', !!values.Livechat_webhook_on_chat_taken);
+ }
+
+ if (typeof values.Livechat_webhook_on_chat_queued !== 'undefined') {
+ settings.updateById('Livechat_webhook_on_chat_queued', !!values.Livechat_webhook_on_chat_queued);
+ }
+
+ if (typeof values.Livechat_webhook_on_forward !== 'undefined') {
+ settings.updateById('Livechat_webhook_on_forward', !!values.Livechat_webhook_on_forward);
+ }
+
if (typeof values.Livechat_webhook_on_offline_msg !== 'undefined') {
settings.updateById('Livechat_webhook_on_offline_msg', !!values.Livechat_webhook_on_offline_msg);
}
diff --git a/app/livechat/server/startup.js b/app/livechat/server/startup.js
index 8bf9e83e3c3..62f4f846b4a 100644
--- a/app/livechat/server/startup.js
+++ b/app/livechat/server/startup.js
@@ -11,6 +11,17 @@ import { RoutingManager } from './lib/RoutingManager';
import { createLivechatQueueView } from './lib/Helper';
import { LivechatAgentActivityMonitor } from './statistics/LivechatAgentActivityMonitor';
+function allowAccessClosedRoomOfSameDepartment(room, user) {
+ if (!room || !user || room.t !== 'l' || !room.departmentId || room.open) {
+ return;
+ }
+ const agentOfDepartment = LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(user._id, room.departmentId);
+ if (!agentOfDepartment) {
+ return;
+ }
+ return hasPermission(user._id, 'view-livechat-room-closed-same-department');
+}
+
Meteor.startup(() => {
roomTypes.setRoomFind('l', (_id) => LivechatRooms.findOneById(_id));
@@ -24,7 +35,7 @@ Meteor.startup(() => {
}
const { _id: userId } = user;
const { servedBy: { _id: agentId } = {} } = room;
- return userId === agentId;
+ return userId === agentId || (!room.open && hasPermission(user._id, 'view-livechat-room-closed-by-another-agent'));
});
addRoomAccessValidator(function(room, user, extraData) {
@@ -59,6 +70,8 @@ Meteor.startup(() => {
return inquiry && inquiry.status === 'queued';
});
+ addRoomAccessValidator(allowAccessClosedRoomOfSameDepartment);
+
callbacks.add('beforeLeaveRoom', function(user, room) {
if (room.t !== 'l') {
return user;
diff --git a/app/livestream/server/routes.js b/app/livestream/server/routes.js
index 8668217d19a..3a52aec6031 100644
--- a/app/livestream/server/routes.js
+++ b/app/livestream/server/routes.js
@@ -3,7 +3,7 @@ import google from 'googleapis';
import { settings } from '../../settings';
import { Users } from '../../models';
-import { API } from '../../api';
+import { API } from '../../api/server';
const { OAuth2 } = google.auth;
diff --git a/app/logger/client/ansispan.js b/app/logger/client/ansispan.js
deleted file mode 100644
index 1ccf35296f7..00000000000
--- a/app/logger/client/ansispan.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const foregroundColors = {
- 30: 'gray',
- 31: 'red',
- 32: 'lime',
- 33: 'yellow',
- 34: '#6B98FF',
- 35: '#FF00FF',
- 36: 'cyan',
- 37: 'white',
-};
-
-export const ansispan = (str: string) => {
- str = str
- .replace(/\s/g, ' ')
- .replace(/(\\n|\n)/g, '
')
- .replace(/>/g, '>')
- .replace(/$1')
- .replace(/\033\[1m/g, '
')
- .replace(/\033\[22m/g, ' ')
- .replace(/\033\[3m/g, '
')
- .replace(/\033\[23m/g, ' ')
- .replace(/\033\[m/g, '')
- .replace(/\033\[0m/g, '')
- .replace(/\033\[39m/g, '');
- return Object.entries(foregroundColors).reduce((str, [ansiCode, color]) => {
- const span = `
`;
- return (
- str
- .replace(new RegExp(`\\033\\[${ ansiCode }m`, 'g'), span)
- .replace(new RegExp(`\\033\\[0;${ ansiCode }m`, 'g'), span)
- );
- }, str);
-};
diff --git a/app/logger/client/index.js b/app/logger/client/index.js
index 689a3ac95d8..91037ba8d97 100644
--- a/app/logger/client/index.js
+++ b/app/logger/client/index.js
@@ -1,2 +1 @@
import './logger';
-import './viewLogs';
diff --git a/app/logger/client/viewLogs.js b/app/logger/client/viewLogs.js
deleted file mode 100644
index 7683f9d1d29..00000000000
--- a/app/logger/client/viewLogs.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { BlazeLayout } from 'meteor/kadira:blaze-layout';
-import { Meteor } from 'meteor/meteor';
-import { Mongo } from 'meteor/mongo';
-
-import { hasAllPermission } from '../../authorization';
-import { registerAdminRoute, registerAdminSidebarItem } from '../../ui-admin/client';
-import { t } from '../../utils';
-
-export const stdout = new Mongo.Collection(null);
-
-Meteor.startup(function() {
- registerAdminSidebarItem({
- href: 'admin-view-logs',
- i18nLabel: 'View_Logs',
- icon: 'post',
- permissionGranted() {
- return hasAllPermission('view-logs');
- },
- });
-});
-
-registerAdminRoute('/view-logs', {
- name: 'admin-view-logs',
- async action() {
- await import('./views/viewLogs');
- return BlazeLayout.render('main', {
- center: 'pageSettingsContainer',
- pageTitle: t('View_Logs'),
- pageTemplate: 'viewLogs',
- noScroll: true,
- });
- },
-});
diff --git a/app/logger/client/views/viewLogs.css b/app/logger/client/views/viewLogs.css
deleted file mode 100644
index e7619344c08..00000000000
--- a/app/logger/client/views/viewLogs.css
+++ /dev/null
@@ -1,29 +0,0 @@
-.view-logs {
- &__terminal {
- overflow-y: scroll;
- flex: 1;
-
- margin: 0;
- margin-bottom: 0 !important;
- padding: 8px 10px !important;
-
- color: var(--color-white);
- border: none !important;
- background-color: #444444 !important;
-
- font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- font-weight: 500;
-
- &-line {
- word-break: break-all;
- }
-
- &-time {
- color: #7f7f7f;
- }
-
- .rtl & {
- direction: ltr;
- }
- }
-}
diff --git a/app/logger/client/views/viewLogs.html b/app/logger/client/views/viewLogs.html
deleted file mode 100644
index 9c664aac2f8..00000000000
--- a/app/logger/client/views/viewLogs.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
- {{#if hasPermission}}
-
- {{#each logs}}
-
- {{{ansispan string}}}
-
- {{/each}}
-
-
-
- {{_ "New_logs"}}
-
- {{else}}
- {{_ "Not_authorized"}}
- {{/if}}
-
diff --git a/app/logger/client/views/viewLogs.js b/app/logger/client/views/viewLogs.js
deleted file mode 100644
index a10fbdef10c..00000000000
--- a/app/logger/client/views/viewLogs.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import _ from 'underscore';
-import { Meteor } from 'meteor/meteor';
-import { Template } from 'meteor/templating';
-import { Tracker } from 'meteor/tracker';
-
-import { ansispan } from '../ansispan';
-import { stdout } from '../viewLogs';
-import { hasAllPermission } from '../../../authorization';
-import { SideNav } from '../../../ui-utils/client';
-import './viewLogs.html';
-import './viewLogs.css';
-import { APIClient } from '../../../utils/client';
-
-const stdoutStreamer = new Meteor.Streamer('stdout');
-
-Template.viewLogs.onCreated(async function() {
- const { queue } = await APIClient.v1.get('stdout.queue');
- (queue || []).forEach((item) => stdout.insert(item));
- stdoutStreamer.on('stdout', (item) => stdout.insert(item));
- this.atBottom = true;
-});
-
-Template.viewLogs.onDestroyed(() => {
- stdout.remove({});
- stdoutStreamer.removeListener('stdout');
-});
-
-Template.viewLogs.helpers({
- hasPermission() {
- return hasAllPermission('view-logs');
- },
- logs() {
- return stdout.find({}, { sort: { ts: 1 } });
- },
- ansispan,
-});
-
-Template.viewLogs.events({
- 'click .new-logs'(event, instance) {
- instance.atBottom = true;
- instance.sendToBottomIfNecessary();
- },
-});
-
-Template.viewLogs.onRendered(function() {
- Tracker.afterFlush(() => {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
- });
-
- const wrapper = this.find('.js-terminal');
- const newLogs = this.find('.js-new-logs');
-
- this.isAtBottom = (scrollThreshold) => {
- if (scrollThreshold == null) {
- scrollThreshold = 0;
- }
- if (wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight) {
- newLogs.className = 'new-logs not';
- return true;
- }
- return false;
- };
-
- this.sendToBottom = () => {
- wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight;
- newLogs.className = 'new-logs not';
- };
-
- this.checkIfScrollIsAtBottom = () => {
- this.atBottom = this.isAtBottom(100);
- };
-
- this.sendToBottomIfNecessary = () => {
- if (this.atBottom === true && this.isAtBottom() !== true) {
- this.sendToBottom();
- } else if (this.atBottom === false) {
- newLogs.className = 'new-logs';
- }
- };
-
- this.sendToBottomIfNecessaryDebounced = _.debounce(this.sendToBottomIfNecessary, 10);
- this.sendToBottomIfNecessary();
-
- if (window.MutationObserver) {
- const observer = new MutationObserver((mutations) => {
- mutations.forEach(() => this.sendToBottomIfNecessaryDebounced());
- });
- observer.observe(wrapper, { childList: true });
- } else {
- wrapper.addEventListener('DOMSubtreeModified', () => this.sendToBottomIfNecessaryDebounced());
- }
-
- this.onWindowResize = () => {
- Meteor.defer(() => this.sendToBottomIfNecessaryDebounced());
- };
- window.addEventListener('resize', this.onWindowResize);
-
- wrapper.addEventListener('mousewheel', () => {
- this.atBottom = false;
- Meteor.defer(() => this.checkIfScrollIsAtBottom());
- });
-
- wrapper.addEventListener('wheel', () => {
- this.atBottom = false;
- Meteor.defer(() => this.checkIfScrollIsAtBottom());
- });
-
- wrapper.addEventListener('touchstart', () => {
- this.atBottom = false;
- });
-
- wrapper.addEventListener('touchend', () => {
- Meteor.defer(() => this.checkIfScrollIsAtBottom());
- });
-
- wrapper.addEventListener('scroll', () => {
- this.atBottom = false;
- Meteor.defer(() => this.checkIfScrollIsAtBottom());
- });
-});
diff --git a/app/mail-messages/client/startup.js b/app/mail-messages/client/startup.js
index 5148b91bd57..2aef29460b2 100644
--- a/app/mail-messages/client/startup.js
+++ b/app/mail-messages/client/startup.js
@@ -1,5 +1,5 @@
import { hasAllPermission } from '../../authorization';
-import { registerAdminSidebarItem } from '../../ui-admin/client';
+import { registerAdminSidebarItem } from '../../../client/admin';
registerAdminSidebarItem({
href: 'admin-mailer',
diff --git a/app/mail-messages/server/startup.js b/app/mail-messages/server/startup.js
index 10dd89d2b8c..eadfc796e77 100644
--- a/app/mail-messages/server/startup.js
+++ b/app/mail-messages/server/startup.js
@@ -3,10 +3,5 @@ import { Meteor } from 'meteor/meteor';
import { Permissions } from '../../models';
Meteor.startup(function() {
- return Permissions.upsert('access-mailer', {
- $setOnInsert: {
- _id: 'access-mailer',
- roles: ['admin'],
- },
- });
+ return Permissions.create('access-mailer', ['admin']);
});
diff --git a/app/mailer/server/api.js b/app/mailer/server/api.js
index 45c7f10030f..38bb1141827 100644
--- a/app/mailer/server/api.js
+++ b/app/mailer/server/api.js
@@ -23,7 +23,7 @@ settings.get('Language', (key, value) => {
lng = value || 'en';
});
-export const replacekey = (str, key, value = '') => str.replace(new RegExp(`(\\[${ key }\\]|__${ key }__)`, 'igm'), value);
+export const replacekey = (str, key, value = '') => str.replace(new RegExp(`(\\[${ key }\\]|__${ key }__)`, 'igm'), s.escapeHTML(value));
export const translate = (str) => str.replace(/\{ ?([^\} ]+)(( ([^\}]+))+)? ?\}/gmi, (match, key) => TAPi18n.__(key, { lng }));
export const replace = function replace(str, data = {}) {
if (!str) {
diff --git a/app/message-pin/server/settings.js b/app/message-pin/server/settings.js
index c2af7eaf276..c16f82a183c 100644
--- a/app/message-pin/server/settings.js
+++ b/app/message-pin/server/settings.js
@@ -9,9 +9,5 @@ Meteor.startup(function() {
group: 'Message',
public: true,
});
- return Permissions.upsert('pin-message', {
- $setOnInsert: {
- roles: ['owner', 'moderator', 'admin'],
- },
- });
+ return Permissions.create('pin-message', ['owner', 'moderator', 'admin']);
});
diff --git a/app/message-snippet/server/startup/settings.js b/app/message-snippet/server/startup/settings.js
index 04047eb56bf..15f8c349e8e 100644
--- a/app/message-snippet/server/startup/settings.js
+++ b/app/message-snippet/server/startup/settings.js
@@ -9,9 +9,5 @@ Meteor.startup(function() {
public: true,
group: 'Message',
});
- Permissions.upsert('snippet-message', {
- $setOnInsert: {
- roles: ['owner', 'moderator', 'admin'],
- },
- });
+ Permissions.create('snippet-message', ['owner', 'moderator', 'admin']);
});
diff --git a/app/message-star/client/actionButton.js b/app/message-star/client/actionButton.js
index 2dec67f2eb0..bdb86a5530b 100644
--- a/app/message-star/client/actionButton.js
+++ b/app/message-star/client/actionButton.js
@@ -72,7 +72,7 @@ Meteor.startup(function() {
RoomHistoryManager.getSurroundingMessages(message, 50);
},
condition({ msg, subscription, u }) {
- if (subscription == null || settings.get('Message_AllowStarring')) {
+ if (subscription == null || !settings.get('Message_AllowStarring')) {
return false;
}
diff --git a/app/message-star/client/views/starredMessages.html b/app/message-star/client/views/starredMessages.html
index f76bdf72c0b..1f2e7115008 100644
--- a/app/message-star/client/views/starredMessages.html
+++ b/app/message-star/client/views/starredMessages.html
@@ -10,7 +10,7 @@
{{# with messageContext}}
{{#each msg in messages}}
- {{>message msg=msg room=room groupable=true subscription=subscription settings=settings u=u}}
+ {{>message msg=msg context="starred" room=room groupable=false subscription=subscription settings=settings u=u}}
{{/each}}
{{/with}}
diff --git a/app/meteor-accounts-saml/server/saml_utils.js b/app/meteor-accounts-saml/server/saml_utils.js
index fa6a9c65c89..80cadf2d377 100644
--- a/app/meteor-accounts-saml/server/saml_utils.js
+++ b/app/meteor-accounts-saml/server/saml_utils.js
@@ -373,8 +373,10 @@ SAML.prototype.validateLogoutRequest = function(samlRequest, callback) {
return callback(err, null);
}
- debugLog(`LogoutRequest: ${ decoded }`);
- const doc = new xmldom.DOMParser().parseFromString(array2string(decoded), 'text/xml');
+ const xmlString = array2string(decoded);
+ debugLog(`LogoutRequest: ${ xmlString }`);
+
+ const doc = new xmldom.DOMParser().parseFromString(xmlString, 'text/xml');
if (!doc) {
return callback('No Doc Found');
}
@@ -385,17 +387,10 @@ SAML.prototype.validateLogoutRequest = function(samlRequest, callback) {
}
try {
- const sessionNode = request.getElementsByTagName('samlp:SessionIndex')[0];
+ const sessionNode = request.getElementsByTagNameNS('*', 'SessionIndex')[0];
+ const nameIdNode = request.getElementsByTagNameNS('*', 'NameID')[0];
- const nameNodes1 = request.getElementsByTagName('saml:NameID');
- const nameNodes2 = request.getElementsByTagName('NameID');
-
- let nameIdNode;
- if (nameNodes1 && nameNodes1.length) {
- nameIdNode = nameNodes1[0];
- } else if (nameNodes2 && nameNodes2.length) {
- nameIdNode = nameNodes2[0];
- } else {
+ if (!nameIdNode) {
throw new Error('SAML Logout Request: No NameID node found');
}
diff --git a/app/models/server/models/LivechatDepartmentAgents.js b/app/models/server/models/LivechatDepartmentAgents.js
index 6ca6a234542..f729b911f8b 100644
--- a/app/models/server/models/LivechatDepartmentAgents.js
+++ b/app/models/server/models/LivechatDepartmentAgents.js
@@ -23,6 +23,10 @@ export class LivechatDepartmentAgents extends Base {
return this.find({ agentId });
}
+ findOneByAgentIdAndDepartmentId(agentId, departmentId) {
+ return this.findOne({ agentId, departmentId });
+ }
+
saveAgent(agent) {
return this.upsert({
agentId: agent.agentId,
diff --git a/app/models/server/models/LivechatInquiry.js b/app/models/server/models/LivechatInquiry.js
index 954c7083ab6..9dc6251a17c 100644
--- a/app/models/server/models/LivechatInquiry.js
+++ b/app/models/server/models/LivechatInquiry.js
@@ -45,6 +45,7 @@ export class LivechatInquiry extends Base {
_id: inquiryId,
}, {
$set: { status: 'taken' },
+ $unset: { defaultAgent: 1 },
});
}
@@ -62,11 +63,14 @@ export class LivechatInquiry extends Base {
/*
* mark inquiry as queued
*/
- queueInquiry(inquiryId) {
+ queueInquiry(inquiryId, defaultAgent) {
return this.update({
_id: inquiryId,
}, {
- $set: { status: 'queued' },
+ $set: {
+ status: 'queued',
+ ...defaultAgent && { defaultAgent },
+ },
});
}
@@ -180,6 +184,14 @@ export class LivechatInquiry extends Base {
return collectionObj.aggregate(aggregate).toArray();
}
+ removeDefaultAgentById(inquiryId) {
+ return this.update({
+ _id: inquiryId,
+ }, {
+ $unset: { defaultAgent: 1 },
+ });
+ }
+
/*
* remove the inquiry by roomId
*/
diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js
index 7e9ceb876ce..6f9e1957c49 100644
--- a/app/models/server/models/LivechatRooms.js
+++ b/app/models/server/models/LivechatRooms.js
@@ -16,6 +16,8 @@ export class LivechatRooms extends Base {
this.tryEnsureIndex({ 'metrics.serviceTimeDuration': 1 }, { sparse: true });
this.tryEnsureIndex({ 'metrics.visitorInactivity': 1 }, { sparse: true });
this.tryEnsureIndex({ 'omnichannel.predictedVisitorAbandonmentAt': 1 }, { sparse: true });
+ this.tryEnsureIndex({ closedAt: 1 }, { sparse: true });
+ this.tryEnsureIndex({ servedBy: 1 }, { sparse: true });
}
findLivechat(filter = {}, offset = 0, limit = 20) {
@@ -164,6 +166,18 @@ export class LivechatRooms extends Base {
return this.findOne(query, options);
}
+ findOneLastServedAndClosedByVisitorToken(visitorToken, options = {}) {
+ const query = {
+ t: 'l',
+ 'v.token': visitorToken,
+ closedAt: { $exists: true },
+ servedBy: { $exists: true },
+ };
+
+ options.sort = { closedAt: -1 };
+ return this.findOne(query, options);
+ }
+
findOneByVisitorToken(visitorToken, fields) {
const options = {};
@@ -238,6 +252,16 @@ export class LivechatRooms extends Base {
return this.find(query);
}
+ findByVisitorIdAndAgentId(visitorId, agentId, options) {
+ const query = {
+ t: 'l',
+ ...visitorId && { 'v._id': visitorId },
+ ...agentId && { 'servedBy._id': agentId },
+ };
+
+ return this.find(query, options);
+ }
+
findByVisitorId(visitorId) {
const query = {
t: 'l',
@@ -351,31 +375,33 @@ export class LivechatRooms extends Base {
}, update);
}
- getTotalConversationsBetweenDate(t, date) {
+ getTotalConversationsBetweenDate(t, date, { departmentId } = {}) {
const query = {
t,
ts: {
$gte: new Date(date.gte), // ISO Date, ts >= date.gte
$lt: new Date(date.lt), // ISODate, ts < date.lt
},
+ ...departmentId && departmentId !== 'undefined' && { departmentId },
};
return this.find(query).count();
}
- getAnalyticsMetricsBetweenDate(t, date) {
+ getAnalyticsMetricsBetweenDate(t, date, { departmentId } = {}) {
const query = {
t,
ts: {
$gte: new Date(date.gte), // ISO Date, ts >= date.gte
$lt: new Date(date.lt), // ISODate, ts < date.lt
},
+ ...departmentId && departmentId !== 'undefined' && { departmentId },
};
return this.find(query, { fields: { ts: 1, departmentId: 1, open: 1, servedBy: 1, metrics: 1, msgs: 1 } });
}
- getAnalyticsBetweenDate(date) {
+ getAnalyticsBetweenDate(date, { departmentId } = {}) {
return this.model.rawCollection().aggregate([
{
$match: {
@@ -384,6 +410,7 @@ export class LivechatRooms extends Base {
$gte: new Date(date.gte), // ISO Date, ts >= date.gte
$lt: new Date(date.lt), // ISODate, ts < date.lt
},
+ ...departmentId && departmentId !== 'undefined' && { departmentId },
},
},
{
diff --git a/app/models/server/models/LivechatVisitors.js b/app/models/server/models/LivechatVisitors.js
index 803ecf2dc56..571d950b0fe 100644
--- a/app/models/server/models/LivechatVisitors.js
+++ b/app/models/server/models/LivechatVisitors.js
@@ -78,6 +78,20 @@ export class LivechatVisitors extends Base {
return this.update(query, update);
}
+ updateLastAgentByToken(token, lastAgent) {
+ const query = {
+ token,
+ };
+
+ const update = {
+ $set: {
+ lastAgent,
+ },
+ };
+
+ return this.update(query, update);
+ }
+
/**
* Find a visitor by their phone number
* @return {object} User from db
diff --git a/app/models/server/models/Permissions.js b/app/models/server/models/Permissions.js
index d1b17ce6469..009f29d37f9 100644
--- a/app/models/server/models/Permissions.js
+++ b/app/models/server/models/Permissions.js
@@ -15,15 +15,34 @@ export class Permissions extends Base {
}
createOrUpdate(name, roles) {
+ const exists = this.findOne({
+ _id: name,
+ roles,
+ }, { fields: { _id: 1 } });
+
+ if (exists) {
+ return exists._id;
+ }
+
+ this.upsert({ _id: name }, { $set: { roles } });
+ }
+
+ create(name, roles) {
+ const exists = this.findOneById(name, { fields: { _id: 1 } });
+
+ if (exists) {
+ return exists._id;
+ }
+
this.upsert({ _id: name }, { $set: { roles } });
}
addRole(permission, role) {
- this.update({ _id: permission }, { $addToSet: { roles: role } });
+ this.update({ _id: permission, roles: { $ne: role } }, { $addToSet: { roles: role } });
}
removeRole(permission, role) {
- this.update({ _id: permission }, { $pull: { roles: role } });
+ this.update({ _id: permission, roles: role }, { $pull: { roles: role } });
}
}
diff --git a/app/models/server/models/Roles.js b/app/models/server/models/Roles.js
index 2715ab05d7c..f14c0b9ac15 100644
--- a/app/models/server/models/Roles.js
+++ b/app/models/server/models/Roles.js
@@ -29,21 +29,26 @@ export class Roles extends Base {
});
}
- createOrUpdate(name, scope = 'Users', description, protectedRole, mandatory2fa) {
- const updateData = {};
- updateData.name = name;
- updateData.scope = scope;
+ createOrUpdate(name, scope = 'Users', description = '', protectedRole = true, mandatory2fa = false) {
+ const queryData = {
+ name,
+ scope,
+ protected: protectedRole,
+ };
- if (description != null) {
- updateData.description = description;
- }
+ const updateData = {
+ ...queryData,
+ description,
+ mandatory2fa,
+ };
- if (protectedRole) {
- updateData.protected = protectedRole;
- }
+ const exists = this.findOne({
+ _id: name,
+ ...queryData,
+ }, { fields: { _id: 1 } });
- if (mandatory2fa != null) {
- updateData.mandatory2fa = mandatory2fa;
+ if (exists) {
+ return exists._id;
}
this.upsert({ _id: name }, { $set: updateData });
diff --git a/app/models/server/models/Rooms.js b/app/models/server/models/Rooms.js
index 0f038458b70..6e5007cabf9 100644
--- a/app/models/server/models/Rooms.js
+++ b/app/models/server/models/Rooms.js
@@ -20,9 +20,6 @@ export class Rooms extends Base {
// discussions
this.tryEnsureIndex({ prid: 1 }, { sparse: true });
this.tryEnsureIndex({ fname: 1 }, { sparse: true });
- // Livechat - statistics
- this.tryEnsureIndex({ closedAt: 1 }, { sparse: true });
-
// field used for DMs only
this.tryEnsureIndex({ uids: 1 }, { sparse: true });
}
diff --git a/app/models/server/models/Settings.js b/app/models/server/models/Settings.js
index 6cca3203f25..a03c81a0410 100644
--- a/app/models/server/models/Settings.js
+++ b/app/models/server/models/Settings.js
@@ -58,7 +58,7 @@ export class Settings extends Base {
filter._id = { $in: ids };
}
- return this.find(filter, { fields: { _id: 1, value: 1 } });
+ return this.find(filter, { fields: { _id: 1, value: 1, editor: 1 } });
}
findNotHiddenPublicUpdatedAfter(updatedAt) {
@@ -70,7 +70,7 @@ export class Settings extends Base {
},
};
- return this.find(filter, { fields: { _id: 1, value: 1 } });
+ return this.find(filter, { fields: { _id: 1, value: 1, editor: 1 } });
}
findNotHiddenPrivate() {
diff --git a/app/models/server/models/Subscriptions.js b/app/models/server/models/Subscriptions.js
index 3442cfcd5a4..e991c1d925a 100644
--- a/app/models/server/models/Subscriptions.js
+++ b/app/models/server/models/Subscriptions.js
@@ -180,20 +180,6 @@ export class Subscriptions extends Base {
return this.update(query, update);
}
- updateDesktopNotificationDurationById(_id, value) {
- const query = {
- _id,
- };
-
- const update = {
- $set: {
- desktopNotificationDuration: parseInt(value),
- },
- };
-
- return this.update(query, update);
- }
-
updateMobilePushNotificationsById(_id, mobilePushNotifications) {
const query = {
_id,
@@ -366,7 +352,6 @@ export class Subscriptions extends Base {
ignored: 1,
audioNotifications: 1,
audioNotificationValue: 1,
- desktopNotificationDuration: 1,
desktopNotifications: 1,
mobilePushNotifications: 1,
emailNotifications: 1,
@@ -393,7 +378,6 @@ export class Subscriptions extends Base {
'u._id': 1,
audioNotifications: 1,
audioNotificationValue: 1,
- desktopNotificationDuration: 1,
desktopNotifications: 1,
mobilePushNotifications: 1,
emailNotifications: 1,
diff --git a/app/models/server/models/Users.js b/app/models/server/models/Users.js
index 6b388bfa340..5f9374fac4a 100644
--- a/app/models/server/models/Users.js
+++ b/app/models/server/models/Users.js
@@ -124,10 +124,10 @@ export class Users extends Base {
return this.findOne(query);
}
- findOneOnlineAgentByUsername(username) {
+ findOneOnlineAgentByUsername(username, options) {
const query = queryStatusAgentOnline({ username });
- return this.findOne(query);
+ return this.findOne(query, options);
}
findOneOnlineAgentById(_id) {
@@ -523,7 +523,7 @@ export class Users extends Base {
}
findOneByEmailAddress(emailAddress, options) {
- const query = { 'emails.address': new RegExp(`^${ s.escapeRegExp(emailAddress) }$`, 'i') };
+ const query = { 'emails.address': String(emailAddress).trim().toLowerCase() };
return this.findOne(query, options);
}
diff --git a/app/models/server/raw/LivechatAgentActivity.js b/app/models/server/raw/LivechatAgentActivity.js
index 7a86cf1bac8..9d48cf45a83 100644
--- a/app/models/server/raw/LivechatAgentActivity.js
+++ b/app/models/server/raw/LivechatAgentActivity.js
@@ -57,7 +57,7 @@ export class LivechatAgentActivityRaw extends BaseRaw {
},
};
const params = [match];
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
params.push(lookup);
params.push(unwind);
params.push(departmentsMatch);
diff --git a/app/models/server/raw/LivechatRooms.js b/app/models/server/raw/LivechatRooms.js
index e996eac6e36..39fa9454622 100644
--- a/app/models/server/raw/LivechatRooms.js
+++ b/app/models/server/raw/LivechatRooms.js
@@ -5,7 +5,7 @@ export class LivechatRoomsRaw extends BaseRaw {
getQueueMetrics({ departmentId, agentId, includeOfflineAgents, options = {} }) {
const match = { $match: { t: 'l', open: true, servedBy: { $exists: true } } };
const matchUsers = { $match: {} };
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
if (agentId) {
@@ -118,7 +118,7 @@ export class LivechatRoomsRaw extends BaseRaw {
abandonedRooms: 1,
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@@ -176,7 +176,7 @@ export class LivechatRoomsRaw extends BaseRaw {
},
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@@ -218,7 +218,7 @@ export class LivechatRoomsRaw extends BaseRaw {
averageChatDurationTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$rooms', 0] }, 0, { $divide: ['$chatsDuration', '$rooms'] }] } },
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@@ -260,7 +260,7 @@ export class LivechatRoomsRaw extends BaseRaw {
averageWaitingTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$rooms', 0] }, 0, { $divide: ['$chatsFirstResponses', '$rooms'] }] } },
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@@ -303,7 +303,7 @@ export class LivechatRoomsRaw extends BaseRaw {
rooms: 1,
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@@ -347,7 +347,7 @@ export class LivechatRoomsRaw extends BaseRaw {
serviceTimeDuration: { $ceil: '$serviceTimeDuration' },
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@@ -455,7 +455,7 @@ export class LivechatRoomsRaw extends BaseRaw {
},
};
const firstParams = [match, departmentsLookup, departmentsUnwind];
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
firstParams.push({
$match: {
'departments._id': departmentId,
@@ -482,7 +482,7 @@ export class LivechatRoomsRaw extends BaseRaw {
servedBy: { $exists: true },
ts: { $gte: new Date(start), $lte: new Date(end) },
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
return this.find(query).count();
@@ -497,7 +497,7 @@ export class LivechatRoomsRaw extends BaseRaw {
servedBy: { $exists: true },
ts: { $gte: new Date(start), $lte: new Date(end) },
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
return this.find(query).count();
@@ -509,7 +509,7 @@ export class LivechatRoomsRaw extends BaseRaw {
servedBy: { $exists: false },
ts: { $gte: new Date(start), $lte: new Date(end) },
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
return this.find(query).count();
@@ -530,7 +530,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: { $sum: 1 },
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group]).toArray();
@@ -552,7 +552,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: { $sum: 1 },
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group]).toArray();
@@ -597,7 +597,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: 1,
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const params = [match, lookup, unwind, group, project];
@@ -643,7 +643,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: 1,
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const params = [match, lookup, unwind, group, project];
@@ -689,7 +689,7 @@ export class LivechatRoomsRaw extends BaseRaw {
longest: '$maxFirstResponse',
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group, project]).toArray();
@@ -734,7 +734,7 @@ export class LivechatRoomsRaw extends BaseRaw {
longest: '$maxFirstReaction',
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group, project]).toArray();
@@ -780,7 +780,7 @@ export class LivechatRoomsRaw extends BaseRaw {
longest: '$maxChatDuration',
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group, project]).toArray();
@@ -811,7 +811,7 @@ export class LivechatRoomsRaw extends BaseRaw {
averageServiceTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$rooms', 0] }, 0, { $divide: ['$allServiceTime', '$rooms'] }] } },
},
};
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@@ -848,7 +848,7 @@ export class LivechatRoomsRaw extends BaseRaw {
if (roomName) {
query.fname = new RegExp(roomName, 'i');
}
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
if (open !== undefined) {
diff --git a/app/models/server/raw/LivechatVisitors.js b/app/models/server/raw/LivechatVisitors.js
index 4faadab58a7..8be6eccd5d4 100644
--- a/app/models/server/raw/LivechatVisitors.js
+++ b/app/models/server/raw/LivechatVisitors.js
@@ -1,14 +1,56 @@
+import s from 'underscore.string';
+
import { BaseRaw } from './BaseRaw';
export class LivechatVisitorsRaw extends BaseRaw {
- getVisitorsBetweenDate({ start, end }) {
+ getVisitorsBetweenDate({ start, end, department }) {
const query = {
_updatedAt: {
$gte: new Date(start),
$lt: new Date(end),
},
+ ...department && department !== 'undefined' && { department },
};
return this.find(query, { fields: { _id: 1 } });
}
+
+ findByNameRegexWithExceptionsAndConditions(searchTerm, exceptions = [], conditions = {}, options = {}) {
+ if (!Array.isArray(exceptions)) {
+ exceptions = [exceptions];
+ }
+
+ const nameRegex = new RegExp(`^${ s.escapeRegExp(searchTerm).trim() }`, 'i');
+
+ const match = {
+ $match: {
+ name: nameRegex,
+ _id: {
+ $nin: exceptions,
+ },
+ ...conditions,
+ },
+ };
+
+ const { fields, sort, offset, count } = options;
+ const project = {
+ $project: {
+ custom_name: { $concat: ['$username', ' - ', '$name'] },
+ ...fields,
+ },
+ };
+
+ const order = { $sort: sort || { name: 1 } };
+ const params = [match, project, order];
+
+ if (offset) {
+ params.push({ $skip: offset });
+ }
+
+ if (count) {
+ params.push({ $limit: count });
+ }
+
+ return this.col.aggregate(params);
+ }
}
diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js
index 803e1c82e12..245ba2b579d 100644
--- a/app/models/server/raw/Users.js
+++ b/app/models/server/raw/Users.js
@@ -215,7 +215,7 @@ export class UsersRaw extends BaseRaw {
},
};
const params = [match];
- if (departmentId) {
+ if (departmentId && departmentId !== 'undefined') {
params.push(lookup);
params.push(unwind);
params.push(departmentsMatch);
diff --git a/app/oauth2-server-config/client/admin/route.js b/app/oauth2-server-config/client/admin/route.js
deleted file mode 100644
index 4ba33c35c65..00000000000
--- a/app/oauth2-server-config/client/admin/route.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { BlazeLayout } from 'meteor/kadira:blaze-layout';
-
-import { registerAdminRoute } from '../../../ui-admin/client';
-import { t } from '../../../utils';
-
-registerAdminRoute('/oauth-apps', {
- name: 'admin-oauth-apps',
- async action() {
- await import('./views');
- return BlazeLayout.render('main', {
- center: 'oauthApps',
- pageTitle: t('OAuth_Applications'),
- });
- },
-});
-
-registerAdminRoute('/oauth-app/:id?', {
- name: 'admin-oauth-app',
- async action(params) {
- await import('./views');
- return BlazeLayout.render('main', {
- center: 'pageSettingsContainer',
- pageTitle: t('OAuth_Application'),
- pageTemplate: 'oauthApp',
- params,
- });
- },
-});
diff --git a/app/oauth2-server-config/client/admin/startup.js b/app/oauth2-server-config/client/admin/startup.js
index 8ebe8aaf8b1..7ebf9d175ab 100644
--- a/app/oauth2-server-config/client/admin/startup.js
+++ b/app/oauth2-server-config/client/admin/startup.js
@@ -1,5 +1,5 @@
import { hasAllPermission } from '../../../authorization';
-import { registerAdminSidebarItem } from '../../../ui-admin/client';
+import { registerAdminSidebarItem } from '../../../../client/admin';
registerAdminSidebarItem({
href: 'admin-oauth-apps',
diff --git a/app/oauth2-server-config/client/admin/views/index.js b/app/oauth2-server-config/client/admin/views/index.js
deleted file mode 100644
index 071605a56b8..00000000000
--- a/app/oauth2-server-config/client/admin/views/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import './oauthApp.html';
-import './oauthApp';
-import './oauthApps.html';
-import './oauthApps';
diff --git a/app/oauth2-server-config/client/admin/views/oauthApp.html b/app/oauth2-server-config/client/admin/views/oauthApp.html
deleted file mode 100644
index 5bcab86d702..00000000000
--- a/app/oauth2-server-config/client/admin/views/oauthApp.html
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
diff --git a/app/oauth2-server-config/client/admin/views/oauthApp.js b/app/oauth2-server-config/client/admin/views/oauthApp.js
deleted file mode 100644
index fa866ba86a8..00000000000
--- a/app/oauth2-server-config/client/admin/views/oauthApp.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import { Tracker } from 'meteor/tracker';
-import toastr from 'toastr';
-
-import { hasAllPermission } from '../../../../authorization';
-import { modal, SideNav } from '../../../../ui-utils/client';
-import { t, handleError } from '../../../../utils';
-import { APIClient } from '../../../../utils/client';
-
-Template.oauthApp.onCreated(async function() {
- const params = this.data.params();
- this.oauthApp = new ReactiveVar({});
- this.record = new ReactiveVar({
- active: true,
- });
- if (params && params.id) {
- const { oauthApp } = await APIClient.v1.get(`oauth-apps.get?appId=${ params.id }`);
- this.oauthApp.set(oauthApp);
- }
-});
-
-Template.oauthApp.helpers({
- hasPermission() {
- return hasAllPermission('manage-oauth-apps');
- },
- data() {
- const instance = Template.instance();
- if (typeof instance.data.params === 'function') {
- const params = instance.data.params();
- if (params && params.id) {
- const data = Template.instance().oauthApp.get();
- if (data) {
- data.authorization_url = Meteor.absoluteUrl('oauth/authorize');
- data.access_token_url = Meteor.absoluteUrl('oauth/token');
- if (Array.isArray(data.redirectUri)) {
- data.redirectUri = data.redirectUri.join('\n');
- }
-
- Template.instance().record.set(data);
- return data;
- }
- }
- }
- return Template.instance().record.curValue;
- },
-});
-
-Template.oauthApp.events({
- 'click .submit > .delete'() {
- const params = Template.instance().data.params();
- modal.open({
- title: t('Are_you_sure'),
- text: t('You_will_not_be_able_to_recover'),
- type: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#DD6B55',
- confirmButtonText: t('Yes_delete_it'),
- cancelButtonText: t('Cancel'),
- closeOnConfirm: false,
- html: false,
- }, function() {
- Meteor.call('deleteOAuthApp', params.id, function() {
- modal.open({
- title: t('Deleted'),
- text: t('Your_entry_has_been_deleted'),
- type: 'success',
- timer: 1000,
- showConfirmButton: false,
- });
- FlowRouter.go('admin-oauth-apps');
- });
- });
- },
- 'click .submit > .save'() {
- const instance = Template.instance();
- const name = $('[name=name]').val().trim();
- const active = $('[name=active]:checked').val().trim() === '1';
- const redirectUri = $('[name=redirectUri]').val().trim();
- if (name === '') {
- return toastr.error(TAPi18n.__('The_application_name_is_required'));
- }
- if (redirectUri === '') {
- return toastr.error(TAPi18n.__('The_redirectUri_is_required'));
- }
- const app = {
- name,
- active,
- redirectUri,
- };
- if (typeof instance.data.params === 'function') {
- const params = instance.data.params();
- if (params && params.id) {
- return Meteor.call('updateOAuthApp', params.id, app, function(err) {
- if (err != null) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Application_updated'));
- });
- }
- }
- Meteor.call('addOAuthApp', app, function(err, data) {
- if (err != null) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Application_added'));
- FlowRouter.go('admin-oauth-app', { id: data._id });
- });
- },
-});
-
-Template.oauthApp.onRendered(() => {
- Tracker.afterFlush(() => {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
- });
-});
diff --git a/app/oauth2-server-config/client/admin/views/oauthApps.html b/app/oauth2-server-config/client/admin/views/oauthApps.html
deleted file mode 100644
index 99a14212780..00000000000
--- a/app/oauth2-server-config/client/admin/views/oauthApps.html
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
- {{# header sectionName=pageTitle}}
-
- {{/header}}
-
- {{#if hasPermission}}
-
- {{else}}
- {{_ "Not_authorized"}}
- {{/if}}
-
-
-
diff --git a/app/oauth2-server-config/client/admin/views/oauthApps.js b/app/oauth2-server-config/client/admin/views/oauthApps.js
deleted file mode 100644
index 595af845a76..00000000000
--- a/app/oauth2-server-config/client/admin/views/oauthApps.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Template } from 'meteor/templating';
-import { Tracker } from 'meteor/tracker';
-import { ReactiveVar } from 'meteor/reactive-var';
-import moment from 'moment';
-
-import { hasAllPermission } from '../../../../authorization';
-import { SideNav } from '../../../../ui-utils/client';
-import { APIClient } from '../../../../utils/client';
-
-Template.oauthApps.onCreated(async function() {
- this.oauthApps = new ReactiveVar([]);
- const { oauthApps } = await APIClient.v1.get('oauth-apps.list');
- this.oauthApps.set(oauthApps);
-});
-
-Template.oauthApps.helpers({
- hasPermission() {
- return hasAllPermission('manage-oauth-apps');
- },
- applications() {
- return Template.instance().oauthApps.get();
- },
- dateFormated(date) {
- return moment(date).format('L LT');
- },
-});
-
-Template.oauthApps.onRendered(() => {
- Tracker.afterFlush(() => {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
- });
-});
diff --git a/app/oauth2-server-config/client/index.js b/app/oauth2-server-config/client/index.js
index 917437a0b53..5911484bf1c 100644
--- a/app/oauth2-server-config/client/index.js
+++ b/app/oauth2-server-config/client/index.js
@@ -1,5 +1,4 @@
import './oauth/oauth2-client.html';
import './oauth/oauth2-client';
import './admin/startup';
-import './admin/route';
import './oauth/stylesheets/oauth2.css';
diff --git a/app/oauth2-server-config/client/oauth/oauth2-client.html b/app/oauth2-server-config/client/oauth/oauth2-client.html
index 49915dcf473..917f4421791 100644
--- a/app/oauth2-server-config/client/oauth/oauth2-client.html
+++ b/app/oauth2-server-config/client/oauth/oauth2-client.html
@@ -30,9 +30,9 @@
- {{_ "Logout"}}
- {{_ "Cancel"}}
- {{_ "Authorize"}}
+ {{_ "Logout"}}
+ {{_ "Cancel"}}
+ {{_ "Authorize"}}
{{#unless Template.subscriptionsReady}}
diff --git a/app/oauth2-server-config/server/oauth/oauth2-server.js b/app/oauth2-server-config/server/oauth/oauth2-server.js
index ff813497bc5..f1c51982c76 100644
--- a/app/oauth2-server-config/server/oauth/oauth2-server.js
+++ b/app/oauth2-server-config/server/oauth/oauth2-server.js
@@ -3,7 +3,7 @@ import { WebApp } from 'meteor/webapp';
import { OAuth2Server } from 'meteor/rocketchat:oauth2-server';
import { OAuthApps, Users } from '../../../models';
-import { API } from '../../../api';
+import { API } from '../../../api/server';
const oauth2server = new OAuth2Server({
accessTokensCollectionName: 'rocketchat_oauth_access_tokens',
diff --git a/app/oembed/client/oembedUrlWidget.js b/app/oembed/client/oembedUrlWidget.js
index 66db81e25af..4304c3db216 100644
--- a/app/oembed/client/oembedUrlWidget.js
+++ b/app/oembed/client/oembedUrlWidget.js
@@ -50,11 +50,7 @@ Template.oembedUrlWidget.helpers({
if (url == null) {
return;
}
- if (url.indexOf('//') === 0) {
- url = `${ this.parsedUrl.protocol }${ url }`;
- } else if (url.indexOf('/') === 0 && (this.parsedUrl && this.parsedUrl.host)) {
- url = `${ this.parsedUrl.protocol }//${ this.parsedUrl.host }${ url }`;
- }
+ url = new URL(url, `${ this.parsedUrl.protocol }//${ this.parsedUrl.host }`).href;
return url;
},
show() {
diff --git a/app/oembed/server/providers.js b/app/oembed/server/providers.js
index 7a25e771032..8a2433834ee 100644
--- a/app/oembed/server/providers.js
+++ b/app/oembed/server/providers.js
@@ -1,7 +1,7 @@
import URL from 'url';
import QueryString from 'querystring';
-import { changeCase } from 'meteor/konecty:change-case';
+import { camelCase } from 'change-case';
import _ from 'underscore';
import { callbacks } from '../../callbacks';
@@ -39,32 +39,32 @@ class Providers {
const providers = new Providers();
providers.registerProvider({
- urls: [new RegExp('https?://soundcloud.com/\\S+')],
+ urls: [new RegExp('https?://soundcloud\\.com/\\S+')],
endPoint: 'https://soundcloud.com/oembed?format=json&maxheight=150',
});
providers.registerProvider({
- urls: [new RegExp('https?://vimeo.com/[^/]+'), new RegExp('https?://vimeo.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo.com/groups/[^/]+/videos/[^/]+')],
+ urls: [new RegExp('https?://vimeo\\.com/[^/]+'), new RegExp('https?://vimeo\\.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo\\.com/groups/[^/]+/videos/[^/]+')],
endPoint: 'https://vimeo.com/api/oembed.json?maxheight=200',
});
providers.registerProvider({
- urls: [new RegExp('https?://www.youtube.com/\\S+'), new RegExp('https?://youtu.be/\\S+')],
+ urls: [new RegExp('https?://www\\.youtube\\.com/\\S+'), new RegExp('https?://youtu\\.be/\\S+')],
endPoint: 'https://www.youtube.com/oembed?maxheight=200',
});
providers.registerProvider({
- urls: [new RegExp('https?://www.rdio.com/\\S+'), new RegExp('https?://rd.io/\\S+')],
+ urls: [new RegExp('https?://www\\.rdio\\.com/\\S+'), new RegExp('https?://rd\\.io/\\S+')],
endPoint: 'https://www.rdio.com/api/oembed/?format=json&maxheight=150',
});
providers.registerProvider({
- urls: [new RegExp('https?://www.slideshare.net/[^/]+/[^/]+')],
+ urls: [new RegExp('https?://www\\.slideshare\\.net/[^/]+/[^/]+')],
endPoint: 'https://www.slideshare.net/api/oembed/2?format=json&maxheight=200',
});
providers.registerProvider({
- urls: [new RegExp('https?://www.dailymotion.com/video/\\S+')],
+ urls: [new RegExp('https?://www\\.dailymotion\\.com/video/\\S+')],
endPoint: 'https://www.dailymotion.com/services/oembed?maxheight=200',
});
@@ -106,7 +106,7 @@ callbacks.add('oembed:afterParseContent', function(data) {
const metas = JSON.parse(data.content.body);
_.each(metas, function(value, key) {
if (_.isString(value)) {
- data.meta[changeCase.camelCase(`oembed_${ key }`)] = value;
+ data.meta[camelCase(`oembed_${ key }`)] = value;
}
});
data.meta.oembedUrl = url;
diff --git a/app/oembed/server/server.js b/app/oembed/server/server.js
index 99023e79a37..9feaabbd7e6 100644
--- a/app/oembed/server/server.js
+++ b/app/oembed/server/server.js
@@ -3,7 +3,7 @@ import querystring from 'querystring';
import { Meteor } from 'meteor/meteor';
import { HTTPInternals } from 'meteor/http';
-import { changeCase } from 'meteor/konecty:change-case';
+import { camelCase } from 'change-case';
import _ from 'underscore';
import iconv from 'iconv-lite';
import ipRangeCheck from 'ip-range-check';
@@ -176,16 +176,16 @@ OEmbed.getUrlMeta = function(url, withFragment) {
return escapeMeta('pageTitle', title);
});
content.body.replace(/ ]*(?:name|property)=[']([^']*)['][^>]*\scontent=[']([^']*)['][^>]*>/gmi, function(meta, name, value) {
- return escapeMeta(changeCase.camelCase(name), value);
+ return escapeMeta(camelCase(name), value);
});
content.body.replace(/ ]*(?:name|property)=["]([^"]*)["][^>]*\scontent=["]([^"]*)["][^>]*>/gmi, function(meta, name, value) {
- return escapeMeta(changeCase.camelCase(name), value);
+ return escapeMeta(camelCase(name), value);
});
content.body.replace(/ ]*\scontent=[']([^']*)['][^>]*(?:name|property)=[']([^']*)['][^>]*>/gmi, function(meta, value, name) {
- return escapeMeta(changeCase.camelCase(name), value);
+ return escapeMeta(camelCase(name), value);
});
content.body.replace(/ ]*\scontent=["]([^"]*)["][^>]*(?:name|property)=["]([^"]*)["][^>]*>/gmi, function(meta, value, name) {
- return escapeMeta(changeCase.camelCase(name), value);
+ return escapeMeta(camelCase(name), value);
});
if (metas.fragment === '!' && (withFragment == null)) {
return OEmbed.getUrlMeta(url, true);
@@ -199,7 +199,7 @@ OEmbed.getUrlMeta = function(url, withFragment) {
headers = {};
const headerObj = content.headers;
Object.keys(headerObj).forEach((header) => {
- headers[changeCase.camelCase(header)] = headerObj[header];
+ headers[camelCase(header)] = headerObj[header];
});
}
if (content && content.statusCode !== 200) {
diff --git a/app/otr/client/rocketchat.otr.room.js b/app/otr/client/rocketchat.otr.room.js
index fcb34184e37..08da47733d7 100644
--- a/app/otr/client/rocketchat.otr.room.js
+++ b/app/otr/client/rocketchat.otr.room.js
@@ -71,13 +71,9 @@ OTR.Room = class {
if (this.established.get()) {
if ($room.length && $title.length && !$('.otr-icon', $title).length) {
$title.prepend(' ');
- $('.input-message-container').addClass('otr');
- $('.inner-right-toolbar').prepend(' ');
}
} else if ($title.length) {
$('.otr-icon', $title).remove();
- $('.input-message-container').removeClass('otr');
- $('.inner-right-toolbar .otr-icon').remove();
}
});
diff --git a/app/otr/client/stylesheets/otr.css b/app/otr/client/stylesheets/otr.css
index 029972d1a1b..2a44f16efe7 100644
--- a/app/otr/client/stylesheets/otr.css
+++ b/app/otr/client/stylesheets/otr.css
@@ -27,21 +27,3 @@
}
}
}
-
-.input-message-container.otr {
- & .input-message {
- padding-right: 48px;
- }
-
- & .inner-right-toolbar {
- & .otr-icon {
- display: inline-block;
-
- margin-top: 2px;
-
- vertical-align: top;
-
- color: green;
- }
- }
-}
diff --git a/app/otr/client/views/otrFlexTab.html b/app/otr/client/views/otrFlexTab.html
index e394b43cba6..6357caf196a 100644
--- a/app/otr/client/views/otrFlexTab.html
+++ b/app/otr/client/views/otrFlexTab.html
@@ -9,15 +9,15 @@
{{#if userIsOnline}}
{{#if established}}
- {{_ "Refresh_keys"}}
+ {{_ "Refresh_keys"}}
- {{_ "End_OTR"}}
+ {{_ "End_OTR"}}
{{else}} {{#if establishing}}
{{_ "Please_wait_while_OTR_is_being_established"}}
{{else}}
- {{_ "Start_OTR"}}
+ {{_ "Start_OTR"}}
{{/if}} {{/if}}
{{else}}
{{_ "OTR_is_only_available_when_both_users_are_online"}}
diff --git a/app/push-notifications/client/views/pushNotificationsFlexTab.html b/app/push-notifications/client/views/pushNotificationsFlexTab.html
index a32f1b6e971..f1e3a28162a 100644
--- a/app/push-notifications/client/views/pushNotificationsFlexTab.html
+++ b/app/push-notifications/client/views/pushNotificationsFlexTab.html
@@ -101,20 +101,6 @@
{{/with}}
-
-
{{_ "Notifications_Duration"}}:
- {{# with "desktopNotificationDuration"}}
-
- {{#if desktopNotificationDuration }}
- {{_ "Duration"}} {{desktopNotificationDuration}} {{_ "seconds"}}
- {{else}}
- {{_ "Use_User_Preferences_or_Global_Settings"}}
- {{/if}}
-
- {{> icon block="rc-user-info__config-content-icon" icon="arrow-down"}}
-
- {{/with}}
-
diff --git a/app/push-notifications/client/views/pushNotificationsFlexTab.js b/app/push-notifications/client/views/pushNotificationsFlexTab.js
index a71609be414..43f026f2aaa 100644
--- a/app/push-notifications/client/views/pushNotificationsFlexTab.js
+++ b/app/push-notifications/client/views/pushNotificationsFlexTab.js
@@ -74,9 +74,6 @@ Template.pushNotificationsFlexTab.helpers({
emailNotifications() {
return Template.instance().form.emailNotifications.get();
},
- desktopNotificationDuration() {
- return Template.instance().form.desktopNotificationDuration.get();
- },
subValue(field) {
const { form } = Template.instance();
if (form[field]) {
@@ -131,7 +128,6 @@ Template.pushNotificationsFlexTab.onCreated(function() {
desktopNotifications: 1,
mobilePushNotifications: 1,
emailNotifications: 1,
- desktopNotificationDuration: 1,
audioNotificationValue: 1,
muteGroupMentions: 1,
},
@@ -144,7 +140,6 @@ Template.pushNotificationsFlexTab.onCreated(function() {
desktopNotifications = 'default',
mobilePushNotifications = 'default',
emailNotifications = 'default',
- desktopNotificationDuration = 0,
muteGroupMentions = false,
} = sub;
@@ -157,7 +152,6 @@ Template.pushNotificationsFlexTab.onCreated(function() {
desktopNotifications: new ReactiveVar(desktopNotifications),
mobilePushNotifications: new ReactiveVar(mobilePushNotifications),
emailNotifications: new ReactiveVar(emailNotifications),
- desktopNotificationDuration: new ReactiveVar(desktopNotificationDuration),
audioNotificationValue: new ReactiveVar(audioNotificationValue),
muteGroupMentions: new ReactiveVar(muteGroupMentions),
};
@@ -169,7 +163,6 @@ Template.pushNotificationsFlexTab.onCreated(function() {
desktopNotifications: new ReactiveVar(desktopNotifications),
mobilePushNotifications: new ReactiveVar(mobilePushNotifications),
emailNotifications: new ReactiveVar(emailNotifications),
- desktopNotificationDuration: new ReactiveVar(desktopNotificationDuration),
audioNotificationValue: new ReactiveVar(audioNotificationValue),
muteGroupMentions: new ReactiveVar(muteGroupMentions),
};
@@ -186,9 +179,6 @@ Template.pushNotificationsFlexTab.onCreated(function() {
}
const rid = Session.get('openedRoom');
switch (field) {
- case 'desktopNotificationDuration':
- await call('saveDesktopNotificationDuration', rid, value);
- break;
case 'audioNotificationValue':
await call('saveAudioNotificationValue', rid, value.split(' ')[0]);
break;
@@ -262,44 +252,6 @@ Template.pushNotificationsFlexTab.events({
...audioAssetsArray,
];
break;
- case 'desktopNotificationDuration':
- options = [{
- id: 'desktopNotificationDuration',
- name: 'desktopNotificationDuration',
- label: 'Default',
- value: 0,
- },
- {
- id: 'desktopNotificationDuration1s',
- name: 'desktopNotificationDuration',
- label: `1 ${ t('seconds') }`,
- value: 1,
- },
- {
- id: 'desktopNotificationDuration2s',
- name: 'desktopNotificationDuration',
- label: `2 ${ t('seconds') }`,
- value: 2,
- },
- {
- id: 'desktopNotificationDuration3s',
- name: 'desktopNotificationDuration',
- label: `3 ${ t('seconds') }`,
- value: 3,
- },
- {
- id: 'desktopNotificationDuration4s',
- name: 'desktopNotificationDuration',
- label: `4 ${ t('seconds') }`,
- value: 4,
- },
- {
- id: 'desktopNotificationDuration5s',
- name: 'desktopNotificationDuration',
- label: `5 ${ t('seconds') }`,
- value: 5,
- }];
- break;
default:
options = [{
id: 'desktopNotificationsDefault',
@@ -331,7 +283,7 @@ Template.pushNotificationsFlexTab.events({
popoverClass: 'notifications-preferences',
template: 'pushNotificationsPopover',
data: {
- change: (value) => instance.form[key].set(key === 'desktopNotificationDuration' ? parseInt(value) : value),
+ change: (value) => instance.form[key].set(value),
value: instance.form[key].get(),
options,
},
diff --git a/app/push-notifications/server/methods/saveNotificationSettings.js b/app/push-notifications/server/methods/saveNotificationSettings.js
index 3ddd80299e6..faf879b7ed9 100644
--- a/app/push-notifications/server/methods/saveNotificationSettings.js
+++ b/app/push-notifications/server/methods/saveNotificationSettings.js
@@ -59,9 +59,6 @@ Meteor.methods({
muteGroupMentions: {
updateMethod: (subscription, value) => Subscriptions.updateMuteGroupMentions(subscription._id, value === '1'),
},
- desktopNotificationDuration: {
- updateMethod: (subscription, value) => Subscriptions.updateDesktopNotificationDurationById(subscription._id, value),
- },
audioNotificationValue: {
updateMethod: (subscription, value) => Subscriptions.updateAudioNotificationValueById(subscription._id, value),
},
@@ -96,13 +93,4 @@ Meteor.methods({
Subscriptions.updateAudioNotificationValueById(subscription._id, value);
return true;
},
-
- saveDesktopNotificationDuration(rid, value) {
- const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, Meteor.userId());
- if (!subscription) {
- throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { method: 'saveDesktopNotificationDuration' });
- }
- Subscriptions.updateDesktopNotificationDurationById(subscription._id, value);
- return true;
- },
});
diff --git a/app/reactions/client/init.js b/app/reactions/client/init.js
index 81d3c44ecb9..81c48cb6aca 100644
--- a/app/reactions/client/init.js
+++ b/app/reactions/client/init.js
@@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
+import { roomTypes } from '../../utils/client';
import { Rooms } from '../../models';
import { MessageAction } from '../../ui-utils';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
@@ -17,13 +18,7 @@ Template.room.events({
const user = Meteor.user();
const room = Rooms.findOne({ _id: rid });
- if (room.ro && !room.reactWhenReadOnly) {
- if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) {
- return false;
- }
- }
-
- if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) {
+ if (roomTypes.readOnly(room._id, user._id)) {
return false;
}
@@ -73,21 +68,15 @@ Meteor.startup(function() {
return false;
}
- if (room.ro && !room.reactWhenReadOnly) {
- if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) {
- return false;
- }
- }
-
- if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) {
+ if (!subscription) {
return false;
}
- if (!subscription) {
+ if (message.private) {
return false;
}
- if (message.private) {
+ if (roomTypes.readOnly(room._id, user._id)) {
return false;
}
diff --git a/app/reactions/client/methods/setReaction.js b/app/reactions/client/methods/setReaction.js
index db58a8b4ea6..14ec5010f7f 100644
--- a/app/reactions/client/methods/setReaction.js
+++ b/app/reactions/client/methods/setReaction.js
@@ -4,6 +4,7 @@ import _ from 'underscore';
import { Messages, Rooms, Subscriptions } from '../../../models';
import { callbacks } from '../../../callbacks';
import { emoji } from '../../../emoji';
+import { roomTypes } from '../../../utils/client';
Meteor.methods({
setReaction(reaction, messageId) {
@@ -16,25 +17,19 @@ Meteor.methods({
const message = Messages.findOne({ _id: messageId });
const room = Rooms.findOne({ _id: message.rid });
- if (room.ro && !room.reactWhenReadOnly) {
- if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) {
- return false;
- }
- }
-
- if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) {
+ if (message.private) {
return false;
}
- if (!Subscriptions.findOne({ rid: message.rid })) {
+ if (!emoji.list[reaction]) {
return false;
}
- if (message.private) {
+ if (roomTypes.readOnly(room._id, user._id)) {
return false;
}
- if (!emoji.list[reaction]) {
+ if (!Subscriptions.findOne({ rid: message.rid })) {
return false;
}
diff --git a/app/reactions/server/setReaction.js b/app/reactions/server/setReaction.js
index f9a6751e613..52adec6d8c2 100644
--- a/app/reactions/server/setReaction.js
+++ b/app/reactions/server/setReaction.js
@@ -3,11 +3,12 @@ import { Random } from 'meteor/random';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import _ from 'underscore';
-import { Messages, EmojiCustom, Subscriptions, Rooms } from '../../models';
+import { Messages, EmojiCustom, Rooms } from '../../models';
import { Notifications } from '../../notifications';
import { callbacks } from '../../callbacks';
import { emoji } from '../../emoji';
import { isTheLastMessage, msgStream } from '../../lib';
+import { hasPermission } from '../../authorization/server/functions/hasPermission';
const removeUserReaction = (message, reaction, username) => {
message.reactions[reaction].usernames.splice(message.reactions[reaction].usernames.indexOf(username), 1);
@@ -17,16 +18,17 @@ const removeUserReaction = (message, reaction, username) => {
return message;
};
-export function setReaction(room, user, message, reaction, shouldReact) {
+async function setReaction(room, user, message, reaction, shouldReact) {
reaction = `:${ reaction.replace(/:/g, '') }:`;
if (!emoji.list[reaction] && EmojiCustom.findByNameOrAlias(reaction).count() === 0) {
throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', { method: 'setReaction' });
}
- if (room.ro && !room.reactWhenReadOnly) {
- if (!Array.isArray(room.unmuted) || room.unmuted.indexOf(user.username) === -1) {
- return false;
+ if (room.ro === true && (!room.reactWhenReadOnly && !hasPermission(user._id, 'post-readonly', room._id))) {
+ // Unless the user was manually unmuted
+ if (!(room.unmuted || []).includes(user.username)) {
+ throw new Error('You can\'t send messages because the room is readonly.');
}
}
@@ -38,8 +40,6 @@ export function setReaction(room, user, message, reaction, shouldReact) {
msg: TAPi18n.__('You_have_been_muted', {}, user.language),
});
return false;
- } if (!Subscriptions.findOne({ rid: message.rid })) {
- return false;
}
const userAlreadyReacted = Boolean(message.reactions) && Boolean(message.reactions[reaction]) && message.reactions[reaction].usernames.indexOf(user.username) !== -1;
@@ -92,18 +92,18 @@ Meteor.methods({
setReaction(reaction, messageId, shouldReact) {
const user = Meteor.user();
- const message = Messages.findOneById(messageId);
-
- const room = Meteor.call('canAccessRoom', message.rid, Meteor.userId());
-
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setReaction' });
}
+ const message = Messages.findOneById(messageId);
+
if (!message) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' });
}
+ const room = Meteor.call('canAccessRoom', message.rid, Meteor.userId());
+
if (!room) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' });
}
diff --git a/app/search/server/events/events.js b/app/search/server/events/events.js
index 79e3c8aa814..7f0f5032a1d 100644
--- a/app/search/server/events/events.js
+++ b/app/search/server/events/events.js
@@ -24,10 +24,12 @@ const eventService = new EventService();
*/
callbacks.add('afterSaveMessage', function(m) {
eventService.promoteEvent('message.save', m._id, m);
+ return m;
}, callbacks.priority.MEDIUM, 'search-events');
callbacks.add('afterDeleteMessage', function(m) {
eventService.promoteEvent('message.delete', m._id);
+ return m;
}, callbacks.priority.MEDIUM, 'search-events-delete');
/**
diff --git a/app/settings/client/index.js b/app/settings/client/index.ts
similarity index 100%
rename from app/settings/client/index.js
rename to app/settings/client/index.ts
diff --git a/app/settings/client/lib/settings.js b/app/settings/client/lib/settings.js
deleted file mode 100644
index bd824705bae..00000000000
--- a/app/settings/client/lib/settings.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveDict } from 'meteor/reactive-dict';
-
-import { CachedCollection } from '../../../ui-cached-collection';
-import { settings } from '../../lib/settings';
-
-settings.cachedCollection = new CachedCollection({
- name: 'public-settings',
- eventType: 'onAll',
- userRelated: false,
- listenChangesForLoggedUsersOnly: true,
-});
-
-settings.collection = settings.cachedCollection.collection;
-
-settings.dict = new ReactiveDict('settings');
-
-settings.get = function(_id) {
- return settings.dict.get(_id);
-};
-
-settings.init = function() {
- let initialLoad = true;
- settings.collection.find().observe({
- added(record) {
- Meteor.settings[record._id] = record.value;
- settings.dict.set(record._id, record.value);
- settings.load(record._id, record.value, initialLoad);
- },
- changed(record) {
- Meteor.settings[record._id] = record.value;
- settings.dict.set(record._id, record.value);
- settings.load(record._id, record.value, initialLoad);
- },
- removed(record) {
- delete Meteor.settings[record._id];
- settings.dict.set(record._id, null);
- settings.load(record._id, null, initialLoad);
- },
- });
- initialLoad = false;
-};
-
-settings.init();
-
-export { settings };
diff --git a/app/settings/client/lib/settings.ts b/app/settings/client/lib/settings.ts
new file mode 100644
index 00000000000..712eadc6c37
--- /dev/null
+++ b/app/settings/client/lib/settings.ts
@@ -0,0 +1,50 @@
+import { Meteor } from 'meteor/meteor';
+import { ReactiveDict } from 'meteor/reactive-dict';
+
+import { CachedCollection } from '../../../ui-cached-collection';
+import { SettingsBase, SettingValue } from '../../lib/settings';
+
+const cachedCollection = new CachedCollection({
+ name: 'public-settings',
+ eventType: 'onAll',
+ userRelated: false,
+ listenChangesForLoggedUsersOnly: true,
+});
+
+class Settings extends SettingsBase {
+ cachedCollection = cachedCollection
+
+ collection = cachedCollection.collection;
+
+ dict = new ReactiveDict
('settings');
+
+ get(_id: string): any {
+ return this.dict.get(_id);
+ }
+
+ init(): void {
+ let initialLoad = true;
+ this.collection.find().observe({
+ added: (record: {_id: string; value: SettingValue}) => {
+ Meteor.settings[record._id] = record.value;
+ this.dict.set(record._id, record.value);
+ this.load(record._id, record.value, initialLoad);
+ },
+ changed: (record: {_id: string; value: SettingValue}) => {
+ Meteor.settings[record._id] = record.value;
+ this.dict.set(record._id, record.value);
+ this.load(record._id, record.value, initialLoad);
+ },
+ removed: (record: {_id: string}) => {
+ delete Meteor.settings[record._id];
+ this.dict.set(record._id, null);
+ this.load(record._id, undefined, initialLoad);
+ },
+ });
+ initialLoad = false;
+ }
+}
+
+export const settings = new Settings();
+
+settings.init();
diff --git a/app/settings/index.js b/app/settings/index.js
index a67eca871ef..1f3257e4542 100644
--- a/app/settings/index.js
+++ b/app/settings/index.js
@@ -1,8 +1,8 @@
import { Meteor } from 'meteor/meteor';
if (Meteor.isClient) {
- module.exports = require('./client/index.js');
+ module.exports = require('./client/index.ts');
}
if (Meteor.isServer) {
- module.exports = require('./server/index.js');
+ module.exports = require('./server/index.ts');
}
diff --git a/app/settings/lib/settings.js b/app/settings/lib/settings.js
deleted file mode 100644
index ab5595f90ac..00000000000
--- a/app/settings/lib/settings.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import _ from 'underscore';
-
-export const settings = {
- callbacks: {},
- regexCallbacks: {},
- ts: new Date(),
- get(_id, callback) {
- if (callback != null) {
- settings.onload(_id, callback);
- if (!Meteor.settings) {
- return;
- }
- if (_id === '*') {
- return Object.keys(Meteor.settings).forEach((key) => {
- const value = Meteor.settings[key];
- callback(key, value);
- });
- }
- if (_.isRegExp(_id) && Meteor.settings) {
- return Object.keys(Meteor.settings).forEach((key) => {
- if (!_id.test(key)) {
- return;
- }
- const value = Meteor.settings[key];
- callback(key, value);
- });
- }
- return Meteor.settings[_id] != null && callback(_id, Meteor.settings[_id]);
- }
- if (!Meteor.settings) {
- return;
- }
- if (_.isRegExp(_id)) {
- return Object.keys(Meteor.settings).reduce((items, key) => {
- const value = Meteor.settings[key];
- if (_id.test(key)) {
- items.push({
- key,
- value,
- });
- }
- return items;
- }, []);
- }
- return Meteor.settings && Meteor.settings[_id];
- },
- set(_id, value, callback) {
- return Meteor.call('saveSetting', _id, value, callback);
- },
- batchSet(settings, callback) {
- return Meteor.call('saveSettings', settings, callback);
- },
- load(key, value, initialLoad) {
- ['*', key].forEach((item) => {
- if (settings.callbacks[item]) {
- settings.callbacks[item].forEach((callback) => callback(key, value, initialLoad));
- }
- });
- Object.keys(settings.regexCallbacks).forEach((cbKey) => {
- const cbValue = settings.regexCallbacks[cbKey];
- if (!cbValue.regex.test(key)) {
- return;
- }
- cbValue.callbacks.forEach((callback) => callback(key, value, initialLoad));
- });
- },
- onload(key, callback) {
- // if key is '*'
- // for key, value in Meteor.settings
- // callback key, value, false
- // else if Meteor.settings?[_id]?
- // callback key, Meteor.settings[_id], false
- const keys = [].concat(key);
- keys.forEach((k) => {
- if (_.isRegExp(k)) {
- settings.regexCallbacks[name = k.source] = settings.regexCallbacks[name = k.source] || {
- regex: k,
- callbacks: [],
- };
- settings.regexCallbacks[k.source].callbacks.push(callback);
- } else {
- settings.callbacks[k] = settings.callbacks[k] || [];
- settings.callbacks[k].push(callback);
- }
- });
- },
-};
diff --git a/app/settings/lib/settings.ts b/app/settings/lib/settings.ts
new file mode 100644
index 00000000000..4bc180c9941
--- /dev/null
+++ b/app/settings/lib/settings.ts
@@ -0,0 +1,116 @@
+import { Meteor } from 'meteor/meteor';
+import _ from 'underscore';
+
+export type SettingValueMultiSelect = Array<{key: string; i18nLabel: string}>
+export type SettingValueRoomPick = Array<{_id: string; name: string}> | string
+export type SettingValue = string | boolean | number | SettingValueMultiSelect | undefined;
+export type SettingComposedValue = {key: string; value: SettingValue};
+export type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void;
+
+interface ISettingRegexCallbacks {
+ regex: RegExp;
+ callbacks: SettingCallback[];
+}
+
+export class SettingsBase {
+ private callbacks = new Map();
+
+ private regexCallbacks = new Map();
+
+ // private ts = new Date()
+
+ public get(_id: string | RegExp, callback?: SettingCallback): SettingValue | SettingComposedValue[] | void {
+ if (callback != null) {
+ this.onload(_id, callback);
+ if (!Meteor.settings) {
+ return;
+ }
+ if (_id === '*') {
+ return Object.keys(Meteor.settings).forEach((key) => {
+ const value = Meteor.settings[key];
+ callback(key, value);
+ });
+ }
+ if (_.isRegExp(_id) && Meteor.settings) {
+ return Object.keys(Meteor.settings).forEach((key) => {
+ if (!_id.test(key)) {
+ return;
+ }
+ const value = Meteor.settings[key];
+ callback(key, value);
+ });
+ }
+
+ if (typeof _id === 'string') {
+ return Meteor.settings[_id] != null && callback(_id, Meteor.settings[_id]);
+ }
+ }
+
+ if (!Meteor.settings) {
+ return;
+ }
+
+ if (_.isRegExp(_id)) {
+ return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => {
+ const value = Meteor.settings[key];
+ if (_id.test(key)) {
+ items.push({
+ key,
+ value,
+ });
+ }
+ return items;
+ }, []);
+ }
+
+ return Meteor.settings && Meteor.settings[_id];
+ }
+
+ set(_id: string, value: SettingValue, callback: () => void): void {
+ Meteor.call('saveSetting', _id, value, callback);
+ }
+
+ batchSet(settings: Array<{_id: string; value: SettingValue}>, callback: () => void): void {
+ Meteor.call('saveSettings', settings, callback);
+ }
+
+ load(key: string, value: SettingValue, initialLoad: boolean): void {
+ ['*', key].forEach((item) => {
+ const callbacks = this.callbacks.get(item);
+ if (callbacks) {
+ callbacks.forEach((callback) => callback(key, value, initialLoad));
+ }
+ });
+ this.regexCallbacks.forEach((cbValue) => {
+ if (!cbValue?.regex.test(key)) {
+ return;
+ }
+ cbValue.callbacks.forEach((callback) => callback(key, value, initialLoad));
+ });
+ }
+
+ onload(key: string | string[] | RegExp | RegExp[], callback: SettingCallback): void {
+ // if key is '*'
+ // for key, value in Meteor.settings
+ // callback key, value, false
+ // else if Meteor.settings?[_id]?
+ // callback key, Meteor.settings[_id], false
+ const keys: Array = Array.isArray(key) ? key : [key];
+ keys.forEach((k) => {
+ if (_.isRegExp(k)) {
+ if (!this.regexCallbacks.has(k.source)) {
+ this.regexCallbacks.set(k.source, {
+ regex: k,
+ callbacks: [],
+ });
+ }
+ this.regexCallbacks.get(k.source)?.callbacks.push(callback);
+ } else {
+ if (!this.callbacks.has(k)) {
+ this.callbacks.set(k, []);
+ }
+ this.callbacks.get(k)?.push(callback);
+ }
+ });
+ }
+}
diff --git a/app/settings/server/functions/settings.d.ts b/app/settings/server/functions/settings.d.ts
deleted file mode 100644
index aab7040d6cc..00000000000
--- a/app/settings/server/functions/settings.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export namespace settings {
- export function get(name: string, callback?: (key: string, value: any) => void): string;
- export function updateById(_id: string, value: any, editor?: string): number;
-}
diff --git a/app/settings/server/functions/settings.js b/app/settings/server/functions/settings.js
deleted file mode 100644
index 7a7ae4a47c1..00000000000
--- a/app/settings/server/functions/settings.js
+++ /dev/null
@@ -1,309 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import _ from 'underscore';
-
-import { settings } from '../../lib/settings';
-import Settings from '../../../models/server/models/Settings';
-
-const blockedSettings = {};
-
-if (process.env.SETTINGS_BLOCKED) {
- process.env.SETTINGS_BLOCKED.split(',').forEach((settingId) => { blockedSettings[settingId] = 1; });
-}
-
-const hiddenSettings = {};
-if (process.env.SETTINGS_HIDDEN) {
- process.env.SETTINGS_HIDDEN.split(',').forEach((settingId) => { hiddenSettings[settingId] = 1; });
-}
-
-settings._sorter = {};
-
-const overrideSetting = (_id, value, options) => {
- if (typeof process !== 'undefined' && process.env && process.env[_id]) {
- value = process.env[_id];
- if (value.toLowerCase() === 'true') {
- value = true;
- } else if (value.toLowerCase() === 'false') {
- value = false;
- } else if (options.type === 'int') {
- value = parseInt(value);
- }
- options.processEnvValue = value;
- options.valueSource = 'processEnvValue';
- } else if (Meteor.settings && typeof Meteor.settings[_id] !== 'undefined') {
- if (Meteor.settings[_id] == null) {
- return false;
- }
-
- value = Meteor.settings[_id];
- options.meteorSettingsValue = value;
- options.valueSource = 'meteorSettingsValue';
- }
-
- if (typeof process !== 'undefined' && process.env && process.env[`OVERWRITE_SETTING_${ _id }`]) {
- let value = process.env[`OVERWRITE_SETTING_${ _id }`];
- if (value.toLowerCase() === 'true') {
- value = true;
- } else if (value.toLowerCase() === 'false') {
- value = false;
- } else if (options.type === 'int') {
- value = parseInt(value);
- }
- options.value = value;
- options.processEnvValue = value;
- options.valueSource = 'processEnvValue';
- }
-
- return value;
-};
-
-
-/*
-* Add a setting
-* @param {String} _id
-* @param {Mixed} value
-* @param {Object} setting
-*/
-
-settings.add = function(_id, value, { editor, ...options } = {}) {
- if (!_id || value == null) {
- return false;
- }
- if (settings._sorter[options.group] == null) {
- settings._sorter[options.group] = 0;
- }
- options.packageValue = value;
- options.valueSource = 'packageValue';
- options.hidden = options.hidden || false;
- options.blocked = options.blocked || false;
- options.secret = options.secret || false;
- if (options.sorter == null) {
- options.sorter = settings._sorter[options.group]++;
- }
- if (options.enableQuery != null) {
- options.enableQuery = JSON.stringify(options.enableQuery);
- }
- if (options.i18nDefaultQuery != null) {
- options.i18nDefaultQuery = JSON.stringify(options.i18nDefaultQuery);
- }
- if (options.i18nLabel == null) {
- options.i18nLabel = _id;
- }
- if (options.i18nDescription == null) {
- options.i18nDescription = `${ _id }_Description`;
- }
- if (blockedSettings[_id] != null) {
- options.blocked = true;
- }
- if (hiddenSettings[_id] != null) {
- options.hidden = true;
- }
- if (options.autocomplete == null) {
- options.autocomplete = true;
- }
-
- value = overrideSetting(_id, value, options);
-
- const updateOperations = {
- $set: options,
- $setOnInsert: {
- createdAt: new Date(),
- },
- };
- if (editor != null) {
- updateOperations.$setOnInsert.editor = editor;
- updateOperations.$setOnInsert.packageEditor = editor;
- }
- if (options.value == null) {
- if (options.force === true) {
- updateOperations.$set.value = options.packageValue;
- } else {
- updateOperations.$setOnInsert.value = value;
- }
- }
- const query = _.extend({
- _id,
- }, updateOperations.$set);
- if (options.section == null) {
- updateOperations.$unset = {
- section: 1,
- };
- query.section = {
- $exists: false,
- };
- }
- const existentSetting = Settings.db.findOne(query);
- if (existentSetting != null) {
- if (existentSetting.editor == null && updateOperations.$setOnInsert.editor != null) {
- updateOperations.$set.editor = updateOperations.$setOnInsert.editor;
- delete updateOperations.$setOnInsert.editor;
- }
- } else {
- updateOperations.$set.ts = new Date();
- }
- return Settings.upsert({
- _id,
- }, updateOperations);
-};
-
-
-/*
-* Add a setting group
-* @param {String} _id
-*/
-
-settings.addGroup = function(_id, options = {}, cb) {
- if (!_id) {
- return false;
- }
- if (_.isFunction(options)) {
- cb = options;
- options = {};
- }
- if (options.i18nLabel == null) {
- options.i18nLabel = _id;
- }
- if (options.i18nDescription == null) {
- options.i18nDescription = `${ _id }_Description`;
- }
- options.ts = new Date();
- options.blocked = false;
- options.hidden = false;
- if (blockedSettings[_id] != null) {
- options.blocked = true;
- }
- if (hiddenSettings[_id] != null) {
- options.hidden = true;
- }
- Settings.upsert({
- _id,
- }, {
- $set: options,
- $setOnInsert: {
- type: 'group',
- createdAt: new Date(),
- },
- });
- if (cb != null) {
- cb.call({
- add(id, value, options) {
- if (options == null) {
- options = {};
- }
- options.group = _id;
- return settings.add(id, value, options);
- },
- section(section, cb) {
- return cb.call({
- add(id, value, options) {
- if (options == null) {
- options = {};
- }
- options.group = _id;
- options.section = section;
- return settings.add(id, value, options);
- },
- });
- },
- });
- }
-};
-
-
-/*
-* Remove a setting by id
-* @param {String} _id
-*/
-
-settings.removeById = function(_id) {
- if (!_id) {
- return false;
- }
- return Settings.removeById(_id);
-};
-
-
-/*
-* Update a setting by id
-* @param {String} _id
-*/
-
-settings.updateById = function(_id, value, editor) {
- if (!_id || value == null) {
- return false;
- }
- if (editor != null) {
- return Settings.updateValueAndEditorById(_id, value, editor);
- }
- return Settings.updateValueById(_id, value);
-};
-
-
-/*
-* Update options of a setting by id
-* @param {String} _id
-*/
-
-settings.updateOptionsById = function(_id, options) {
- if (!_id || options == null) {
- return false;
- }
- return Settings.updateOptionsById(_id, options);
-};
-
-
-/*
-* Update a setting by id
-* @param {String} _id
-*/
-
-settings.clearById = function(_id) {
- if (_id == null) {
- return false;
- }
- return Settings.updateValueById(_id, undefined);
-};
-
-
-/*
-* Update a setting by id
-*/
-
-settings.init = function() {
- settings.initialLoad = true;
- Settings.find().observe({
- added(record) {
- Meteor.settings[record._id] = record.value;
- if (record.env === true) {
- process.env[record._id] = record.value;
- }
- return settings.load(record._id, record.value, settings.initialLoad);
- },
- changed(record) {
- Meteor.settings[record._id] = record.value;
- if (record.env === true) {
- process.env[record._id] = record.value;
- }
- return settings.load(record._id, record.value, settings.initialLoad);
- },
- removed(record) {
- delete Meteor.settings[record._id];
- if (record.env === true) {
- delete process.env[record._id];
- }
- return settings.load(record._id, undefined, settings.initialLoad);
- },
- });
- settings.initialLoad = false;
- settings.afterInitialLoad.forEach((fn) => fn(Meteor.settings));
-};
-
-settings.afterInitialLoad = [];
-
-settings.onAfterInitialLoad = function(fn) {
- settings.afterInitialLoad.push(fn);
- if (settings.initialLoad === false) {
- return fn(Meteor.settings);
- }
-};
-
-export { settings };
diff --git a/app/settings/server/functions/settings.mocks.ts b/app/settings/server/functions/settings.mocks.ts
new file mode 100644
index 00000000000..d28383b1425
--- /dev/null
+++ b/app/settings/server/functions/settings.mocks.ts
@@ -0,0 +1,51 @@
+import { Meteor } from 'meteor/meteor';
+import mock from 'mock-require';
+
+type Dictionary = {
+ [index: string]: any;
+}
+
+class SettingsClass {
+ public data = new Map()
+
+ public upsertCalls = 0;
+
+ findOne(query: Dictionary): any {
+ return [...this.data.values()].find((data) => Object.entries(query).every(([key, value]) => data[key] === value));
+ }
+
+ upsert(query: any, update: any): void {
+ const existent = this.findOne(query);
+
+ const data = { ...existent, ...query, ...update.$set };
+
+ if (!existent) {
+ Object.assign(data, update.$setOnInsert);
+ }
+
+ // console.log(query, data);
+ this.data.set(query._id, data);
+ Meteor.settings[query._id] = data.value;
+
+ // Can't import before the mock command on end of this file!
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const { settings } = require('./settings');
+ settings.load(query._id, data.value, !existent);
+
+ this.upsertCalls++;
+ }
+
+ updateValueById(id: string, value: any): void {
+ this.data.set(id, { ...this.data.get(id), value });
+
+ // Can't import before the mock command on end of this file!
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const { settings } = require('./settings');
+ Meteor.settings[id] = value;
+ settings.load(id, value, false);
+ }
+}
+
+export const Settings = new SettingsClass();
+
+mock('../../../models/server/models/Settings', Settings);
diff --git a/app/settings/server/functions/settings.tests.ts b/app/settings/server/functions/settings.tests.ts
new file mode 100644
index 00000000000..5132e1c7d2d
--- /dev/null
+++ b/app/settings/server/functions/settings.tests.ts
@@ -0,0 +1,465 @@
+/* eslint-disable @typescript-eslint/camelcase */
+/* eslint-env mocha */
+import { Meteor } from 'meteor/meteor';
+import chai, { expect } from 'chai';
+import spies from 'chai-spies';
+
+import { Settings } from './settings.mocks';
+import { settings } from './settings';
+
+chai.use(spies);
+
+describe('Settings', () => {
+ beforeEach(() => {
+ Settings.upsertCalls = 0;
+ Settings.data.clear();
+ Meteor.settings = { public: {} };
+ process.env = {};
+ });
+
+ it('should not insert the same setting twice', () => {
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', true, {
+ type: 'boolean',
+ sorter: 0,
+ });
+ });
+ });
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.be.include({
+ type: 'boolean',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: true,
+ value: true,
+ valueSource: 'packageValue',
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting',
+ i18nDescription: 'my_setting_Description',
+ autocomplete: true,
+ });
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', true, {
+ type: 'boolean',
+ sorter: 0,
+ });
+ });
+ });
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting' }).value).to.be.equal(true);
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting2', false, {
+ type: 'boolean',
+ sorter: 0,
+ });
+ });
+ });
+
+ expect(Settings.data.size).to.be.equal(3);
+ expect(Settings.upsertCalls).to.be.equal(3);
+ expect(Settings.findOne({ _id: 'my_setting' }).value).to.be.equal(true);
+ expect(Settings.findOne({ _id: 'my_setting2' }).value).to.be.equal(false);
+ });
+
+ it('should respect override via environment as int', () => {
+ process.env.OVERWRITE_SETTING_my_setting = '1';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ const expectedSetting = {
+ value: 1,
+ processEnvValue: 1,
+ valueSource: 'processEnvValue',
+ type: 'int',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: 0,
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting',
+ i18nDescription: 'my_setting_Description',
+ autocomplete: true,
+ };
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+
+ process.env.OVERWRITE_SETTING_my_setting = '2';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ expectedSetting.value = 2;
+ expectedSetting.processEnvValue = 2;
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(3);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+ });
+
+ it('should respect override via environment as boolean', () => {
+ process.env.OVERWRITE_SETTING_my_setting_bool = 'true';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting_bool', false, {
+ type: 'boolean',
+ sorter: 0,
+ });
+ });
+ });
+
+ const expectedSetting = {
+ value: true,
+ processEnvValue: true,
+ valueSource: 'processEnvValue',
+ type: 'boolean',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: false,
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting_bool',
+ i18nDescription: 'my_setting_bool_Description',
+ autocomplete: true,
+ };
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting_bool' })).to.include(expectedSetting);
+
+ process.env.OVERWRITE_SETTING_my_setting_bool = 'false';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting_bool', false, {
+ type: 'boolean',
+ sorter: 0,
+ });
+ });
+ });
+
+ expectedSetting.value = false;
+ expectedSetting.processEnvValue = false;
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(3);
+ expect(Settings.findOne({ _id: 'my_setting_bool' })).to.include(expectedSetting);
+ });
+
+ it('should respect override via environment as string', () => {
+ process.env.OVERWRITE_SETTING_my_setting_str = 'hey';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting_str', '', {
+ type: 'string',
+ sorter: 0,
+ });
+ });
+ });
+
+ const expectedSetting = {
+ value: 'hey',
+ processEnvValue: 'hey',
+ valueSource: 'processEnvValue',
+ type: 'string',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: '',
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting_str',
+ i18nDescription: 'my_setting_str_Description',
+ autocomplete: true,
+ };
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting_str' })).to.include(expectedSetting);
+
+ process.env.OVERWRITE_SETTING_my_setting_str = 'hey ho';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting_str', 'hey', {
+ type: 'string',
+ sorter: 0,
+ });
+ });
+ });
+
+ expectedSetting.value = 'hey ho';
+ expectedSetting.processEnvValue = 'hey ho';
+ expectedSetting.packageValue = 'hey';
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(3);
+ expect(Settings.findOne({ _id: 'my_setting_str' })).to.include(expectedSetting);
+ });
+
+ it('should respect initial value via environment', () => {
+ process.env.my_setting = '1';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ const expectedSetting = {
+ value: 1,
+ processEnvValue: 1,
+ valueSource: 'processEnvValue',
+ type: 'int',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: 0,
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting',
+ i18nDescription: 'my_setting_Description',
+ autocomplete: true,
+ };
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+
+ process.env.my_setting = '2';
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ expectedSetting.processEnvValue = 2;
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(3);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+ });
+
+ it('should respect initial value via Meteor.settings', () => {
+ Meteor.settings.my_setting = 1;
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ const expectedSetting = {
+ value: 1,
+ meteorSettingsValue: 1,
+ valueSource: 'meteorSettingsValue',
+ type: 'int',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: 0,
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting',
+ i18nDescription: 'my_setting_Description',
+ autocomplete: true,
+ };
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+
+ Meteor.settings.my_setting = 2;
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ expectedSetting.meteorSettingsValue = 2;
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(3);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+ });
+
+ it('should keep original value if value on code was changed', () => {
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ const expectedSetting = {
+ value: 0,
+ valueSource: 'packageValue',
+ type: 'int',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: 0,
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting',
+ i18nDescription: 'my_setting_Description',
+ autocomplete: true,
+ };
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+
+ // Can't reset setting because the Meteor.setting will have the first value and will act to enforce his value
+ // settings.addGroup('group', function() {
+ // this.section('section', function() {
+ // this.add('my_setting', 1, {
+ // type: 'int',
+ // sorter: 0,
+ // });
+ // });
+ // });
+
+ // expectedSetting.packageValue = 1;
+
+ // expect(Settings.data.size).to.be.equal(2);
+ // expect(Settings.upsertCalls).to.be.equal(3);
+ // expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+ });
+
+ it('should change group and section', () => {
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ const expectedSetting = {
+ value: 0,
+ valueSource: 'packageValue',
+ type: 'int',
+ sorter: 0,
+ group: 'group',
+ section: 'section',
+ packageValue: 0,
+ hidden: false,
+ blocked: false,
+ secret: false,
+ i18nLabel: 'my_setting',
+ i18nDescription: 'my_setting_Description',
+ autocomplete: true,
+ };
+
+ expect(Settings.data.size).to.be.equal(2);
+ expect(Settings.upsertCalls).to.be.equal(2);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+
+ settings.addGroup('group2', function() {
+ this.section('section2', function() {
+ this.add('my_setting', 0, {
+ type: 'int',
+ sorter: 0,
+ });
+ });
+ });
+
+ expectedSetting.group = 'group2';
+ expectedSetting.section = 'section2';
+
+ expect(Settings.data.size).to.be.equal(3);
+ expect(Settings.upsertCalls).to.be.equal(4);
+ expect(Settings.findOne({ _id: 'my_setting' })).to.include(expectedSetting);
+ });
+
+ it('should call `settings.get` callback on setting added', () => {
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('setting_callback', 'value1', {
+ type: 'string',
+ });
+ });
+ });
+
+ const spy = chai.spy();
+ settings.get('setting_callback', spy);
+ settings.get(/setting_callback/, spy);
+
+ expect(spy).to.have.been.called.exactly(2);
+ expect(spy).to.have.been.called.always.with('setting_callback', 'value1');
+ });
+
+ it('should call `settings.get` callback on setting changed', () => {
+ const spy = chai.spy();
+ settings.get('setting_callback', spy);
+ settings.get(/setting_callback/, spy);
+
+ settings.addGroup('group', function() {
+ this.section('section', function() {
+ this.add('setting_callback', 'value2', {
+ type: 'string',
+ });
+ });
+ });
+
+ settings.updateById('setting_callback', 'value3');
+
+ expect(spy).to.have.been.called.exactly(4);
+ expect(spy).to.have.been.called.with('setting_callback', 'value2');
+ expect(spy).to.have.been.called.with('setting_callback', 'value3');
+ });
+});
diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts
new file mode 100644
index 00000000000..3f466ccd459
--- /dev/null
+++ b/app/settings/server/functions/settings.ts
@@ -0,0 +1,364 @@
+import { Meteor } from 'meteor/meteor';
+import _ from 'underscore';
+
+import { SettingsBase, SettingValue } from '../../lib/settings';
+import SettingsModel from '../../../models/server/models/Settings';
+
+const blockedSettings = new Set();
+const hiddenSettings = new Set();
+
+if (process.env.SETTINGS_BLOCKED) {
+ process.env.SETTINGS_BLOCKED.split(',').forEach((settingId) => blockedSettings.add(settingId.trim()));
+}
+
+if (process.env.SETTINGS_HIDDEN) {
+ process.env.SETTINGS_HIDDEN.split(',').forEach((settingId) => hiddenSettings.add(settingId.trim()));
+}
+
+const overrideSetting = (_id: string, value: SettingValue, options: ISettingAddOptions): SettingValue => {
+ const envValue = process.env[_id];
+ if (envValue) {
+ if (envValue.toLowerCase() === 'true') {
+ value = true;
+ } else if (envValue.toLowerCase() === 'false') {
+ value = false;
+ } else if (options.type === 'int') {
+ value = parseInt(envValue);
+ } else {
+ value = envValue;
+ }
+ options.processEnvValue = value;
+ options.valueSource = 'processEnvValue';
+ } else if (Meteor.settings[_id] != null && Meteor.settings[_id] !== value) {
+ value = Meteor.settings[_id];
+ options.meteorSettingsValue = value;
+ options.valueSource = 'meteorSettingsValue';
+ }
+
+ const overwriteValue = process.env[`OVERWRITE_SETTING_${ _id }`];
+ if (overwriteValue) {
+ if (overwriteValue.toLowerCase() === 'true') {
+ value = true;
+ } else if (overwriteValue.toLowerCase() === 'false') {
+ value = false;
+ } else if (options.type === 'int') {
+ value = parseInt(overwriteValue);
+ } else {
+ value = overwriteValue;
+ }
+ options.value = value;
+ options.processEnvValue = value;
+ options.valueSource = 'processEnvValue';
+ }
+
+ return value;
+};
+
+export interface ISettingAddOptions {
+ _id?: string;
+ type?: 'group' | 'boolean' | 'int' | 'string' | 'asset' | 'code' | 'select' | 'password' | 'action' | 'relativeUrl' | 'language' | 'date' | 'color' | 'font' | 'roomPick' | 'multiSelect';
+ editor?: string;
+ packageEditor?: string;
+ packageValue?: SettingValue;
+ valueSource?: string;
+ hidden?: boolean;
+ blocked?: boolean;
+ secret?: boolean;
+ sorter?: number;
+ i18nLabel?: string;
+ i18nDescription?: string;
+ autocomplete?: boolean;
+ force?: boolean;
+ group?: string;
+ section?: string;
+ enableQuery?: any;
+ processEnvValue?: SettingValue;
+ meteorSettingsValue?: SettingValue;
+ value?: SettingValue;
+ ts?: Date;
+}
+
+export interface ISettingAddGroupOptions {
+ hidden?: boolean;
+ blocked?: boolean;
+ ts?: Date;
+ i18nLabel?: string;
+ i18nDescription?: string;
+}
+
+interface IUpdateOperator {
+ $set: ISettingAddOptions;
+ $setOnInsert: ISettingAddOptions & {
+ createdAt: Date;
+ };
+ $unset?: {
+ section?: 1;
+ };
+}
+
+type QueryExpression = {
+ $exists: boolean;
+}
+
+type Query = {
+ [P in keyof T]?: T[P] | QueryExpression;
+}
+
+type addSectionCallback = (this: {
+ add(id: string, value: SettingValue, options: ISettingAddOptions): void;
+}) => void;
+
+type addGroupCallback = (this: {
+ add(id: string, value: SettingValue, options: ISettingAddOptions): void;
+ section(section: string, cb: addSectionCallback): void;
+}) => void;
+
+class Settings extends SettingsBase {
+ private afterInitialLoad: Array<(settings: Meteor.Settings) => void> = [];
+
+ private _sorter: {[key: string]: number} = {};
+
+ private initialLoad = false;
+
+ /*
+ * Add a setting
+ */
+ add(_id: string, value: SettingValue, { editor, ...options }: ISettingAddOptions = {}): boolean {
+ if (!_id || value == null) {
+ return false;
+ }
+ if (options.group && this._sorter[options.group] == null) {
+ this._sorter[options.group] = 0;
+ }
+ options.packageValue = value;
+ options.valueSource = 'packageValue';
+ options.hidden = options.hidden || false;
+ options.blocked = options.blocked || false;
+ options.secret = options.secret || false;
+ if (options.group && options.sorter == null) {
+ options.sorter = this._sorter[options.group]++;
+ }
+ if (options.enableQuery != null) {
+ options.enableQuery = JSON.stringify(options.enableQuery);
+ }
+ if (options.i18nLabel == null) {
+ options.i18nLabel = _id;
+ }
+ if (options.i18nDescription == null) {
+ options.i18nDescription = `${ _id }_Description`;
+ }
+ if (blockedSettings.has(_id)) {
+ options.blocked = true;
+ }
+ if (hiddenSettings.has(_id)) {
+ options.hidden = true;
+ }
+ if (options.autocomplete == null) {
+ options.autocomplete = true;
+ }
+
+ value = overrideSetting(_id, value, options);
+
+ const updateOperations: IUpdateOperator = {
+ $set: options,
+ $setOnInsert: {
+ createdAt: new Date(),
+ },
+ };
+ if (editor != null) {
+ updateOperations.$setOnInsert.editor = editor;
+ updateOperations.$setOnInsert.packageEditor = editor;
+ }
+
+ if (options.value == null) {
+ if (options.force === true) {
+ updateOperations.$set.value = options.packageValue;
+ } else {
+ updateOperations.$setOnInsert.value = value;
+ }
+ }
+
+ const query: Query = {
+ _id,
+ ...updateOperations.$set,
+ };
+
+ if (options.section == null) {
+ updateOperations.$unset = {
+ section: 1,
+ };
+ query.section = {
+ $exists: false,
+ };
+ }
+
+ const existentSetting = SettingsModel.findOne(query);
+ if (existentSetting) {
+ if (existentSetting.editor || !updateOperations.$setOnInsert.editor) {
+ return true;
+ }
+
+ updateOperations.$set.editor = updateOperations.$setOnInsert.editor;
+ delete updateOperations.$setOnInsert.editor;
+ }
+
+ updateOperations.$set.ts = new Date();
+
+ SettingsModel.upsert({
+ _id,
+ }, updateOperations);
+ return true;
+ }
+
+ /*
+ * Add a setting group
+ */
+ addGroup(_id: string, cb: addGroupCallback): boolean;
+
+ // eslint-disable-next-line no-dupe-class-members
+ addGroup(_id: string, options: ISettingAddGroupOptions | addGroupCallback = {}, cb?: addGroupCallback): boolean {
+ if (!_id) {
+ return false;
+ }
+ if (_.isFunction(options)) {
+ cb = options;
+ options = {};
+ }
+ if (options.i18nLabel == null) {
+ options.i18nLabel = _id;
+ }
+ if (options.i18nDescription == null) {
+ options.i18nDescription = `${ _id }_Description`;
+ }
+
+ options.blocked = false;
+ options.hidden = false;
+ if (blockedSettings.has(_id)) {
+ options.blocked = true;
+ }
+ if (hiddenSettings.has(_id)) {
+ options.hidden = true;
+ }
+
+ const existentGroup = SettingsModel.findOne({
+ _id,
+ type: 'group',
+ ...options,
+ });
+
+ if (!existentGroup) {
+ options.ts = new Date();
+
+ SettingsModel.upsert({
+ _id,
+ }, {
+ $set: options,
+ $setOnInsert: {
+ type: 'group',
+ createdAt: new Date(),
+ },
+ });
+ }
+
+ if (cb != null) {
+ cb.call({
+ add: (id: string, value: SettingValue, options: ISettingAddOptions = {}) => {
+ options.group = _id;
+ return this.add(id, value, options);
+ },
+ section: (section: string, cb: addSectionCallback) => cb.call({
+ add: (id: string, value: SettingValue, options: ISettingAddOptions = {}) => {
+ options.group = _id;
+ options.section = section;
+ return this.add(id, value, options);
+ },
+ }),
+ });
+ }
+ return true;
+ }
+
+ /*
+ * Remove a setting by id
+ */
+ removeById(_id: string): boolean {
+ if (!_id) {
+ return false;
+ }
+ return SettingsModel.removeById(_id);
+ }
+
+ /*
+ * Update a setting by id
+ */
+ updateById(_id: string, value: SettingValue, editor?: string): boolean {
+ if (!_id || value == null) {
+ return false;
+ }
+ if (editor != null) {
+ return SettingsModel.updateValueAndEditorById(_id, value, editor);
+ }
+ return SettingsModel.updateValueById(_id, value);
+ }
+
+ /*
+ * Update options of a setting by id
+ */
+ updateOptionsById(_id: string, options: object): boolean {
+ if (!_id || options == null) {
+ return false;
+ }
+ return SettingsModel.updateOptionsById(_id, options);
+ }
+
+ /*
+ * Update a setting by id
+ */
+ clearById(_id: string): boolean {
+ if (_id == null) {
+ return false;
+ }
+ return SettingsModel.updateValueById(_id, undefined);
+ }
+
+ /*
+ * Update a setting by id
+ */
+ init(): void {
+ this.initialLoad = true;
+ SettingsModel.find().observe({
+ added: (record: {_id: string; env: boolean; value: SettingValue}) => {
+ Meteor.settings[record._id] = record.value;
+ if (record.env === true) {
+ process.env[record._id] = String(record.value);
+ }
+ return this.load(record._id, record.value, this.initialLoad);
+ },
+ changed: (record: {_id: string; env: boolean; value: SettingValue}) => {
+ Meteor.settings[record._id] = record.value;
+ if (record.env === true) {
+ process.env[record._id] = String(record.value);
+ }
+ return this.load(record._id, record.value, this.initialLoad);
+ },
+ removed: (record: {_id: string; env: boolean}) => {
+ delete Meteor.settings[record._id];
+ if (record.env === true) {
+ delete process.env[record._id];
+ }
+ return this.load(record._id, undefined, this.initialLoad);
+ },
+ });
+ this.initialLoad = false;
+ this.afterInitialLoad.forEach((fn) => fn(Meteor.settings));
+ }
+
+ onAfterInitialLoad(fn: (settings: Meteor.Settings) => void): void {
+ this.afterInitialLoad.push(fn);
+ if (this.initialLoad === false) {
+ fn(Meteor.settings);
+ }
+ }
+}
+
+export const settings = new Settings();
diff --git a/app/settings/server/index.js b/app/settings/server/index.ts
similarity index 100%
rename from app/settings/server/index.js
rename to app/settings/server/index.ts
diff --git a/app/theme/client/imports/components/emojiPicker.css b/app/theme/client/imports/components/emojiPicker.css
index bd2d72a0b9c..f8601a798a4 100644
--- a/app/theme/client/imports/components/emojiPicker.css
+++ b/app/theme/client/imports/components/emojiPicker.css
@@ -122,6 +122,12 @@
font-size: 12px;
font-weight: 500;
+
+ & .add-custom {
+ display: inline-block;
+
+ width: 100%;
+ }
}
.emoji-top {
diff --git a/app/theme/client/imports/components/header.css b/app/theme/client/imports/components/header.css
index d5b0a05cf49..3dfa4f44149 100644
--- a/app/theme/client/imports/components/header.css
+++ b/app/theme/client/imports/components/header.css
@@ -225,23 +225,23 @@
border-radius: var(--header-title-status-bullet-radius);
&--online {
- background-color: var(--status-online);
+ background-color: var(--rc-status-online);
}
&--away {
- background-color: var(--status-away);
+ background-color: var(--rc-status-away);
}
&--busy {
- background-color: var(--status-busy);
+ background-color: var(--rc-status-busy);
}
&--invisible {
- background-color: var(--status-invisible);
+ background-color: var(--rc-status-invisible);
}
&--offline {
- background-color: var(--status-invisible);
+ background-color: var(--rc-status-invisible);
}
}
}
diff --git a/app/theme/client/imports/components/main-content.css b/app/theme/client/imports/components/main-content.css
index bda5cf955e3..04f7cb05d97 100644
--- a/app/theme/client/imports/components/main-content.css
+++ b/app/theme/client/imports/components/main-content.css
@@ -15,18 +15,18 @@
.messages-container .room-icon {
&.online {
- color: var(--status-online);
+ color: var(--rc-status-online);
}
&.away {
- color: var(--status-away);
+ color: var(--rc-status-away);
}
&.busy {
- color: var(--status-busy);
+ color: var(--rc-status-busy);
}
&.offline {
- color: var(--status-invisible);
+ color: var(--rc-status-invisible);
}
}
diff --git a/app/theme/client/imports/components/memberlist.css b/app/theme/client/imports/components/memberlist.css
index 6d78136b841..0da2ebec46e 100644
--- a/app/theme/client/imports/components/memberlist.css
+++ b/app/theme/client/imports/components/memberlist.css
@@ -61,19 +61,19 @@
border-radius: var(--sidebar-item-user-status-radius);
&--online {
- background-color: var(--status-online);
+ background-color: var(--rc-status-online);
}
&--away {
- background-color: var(--status-away);
+ background-color: var(--rc-status-away);
}
&--busy {
- background-color: var(--status-busy);
+ background-color: var(--rc-status-busy);
}
&--offline {
- background-color: var(--status-invisible-sidebar);
+ background-color: var(--rc-status-invisible-sidebar);
}
}
diff --git a/app/theme/client/imports/components/popover.css b/app/theme/client/imports/components/popover.css
index 551c02e9dd4..807bdd7c88d 100644
--- a/app/theme/client/imports/components/popover.css
+++ b/app/theme/client/imports/components/popover.css
@@ -127,25 +127,25 @@
&--online {
& .rc-popover__icon {
- color: var(--status-online);
+ color: var(--rc-status-online);
}
}
&--away {
& .rc-popover__icon {
- color: var(--status-away);
+ color: var(--rc-status-away);
}
}
&--busy {
& .rc-popover__icon {
- color: var(--status-busy);
+ color: var(--rc-status-busy);
}
}
&--offline {
& .rc-popover__icon {
- color: var(--status-invisible);
+ color: var(--rc-status-invisible);
}
}
}
diff --git a/app/theme/client/imports/components/sidebar/rooms-list.css b/app/theme/client/imports/components/sidebar/rooms-list.css
index b6625f0abe9..ca912108bad 100644
--- a/app/theme/client/imports/components/sidebar/rooms-list.css
+++ b/app/theme/client/imports/components/sidebar/rooms-list.css
@@ -44,7 +44,7 @@
&__toolbar-search {
position: absolute;
- z-index: 1;
+ z-index: 10;
left: 10px;
diff --git a/app/theme/client/imports/components/sidebar/sidebar-header.css b/app/theme/client/imports/components/sidebar/sidebar-header.css
index de54cba02ae..fc963c5eda0 100644
--- a/app/theme/client/imports/components/sidebar/sidebar-header.css
+++ b/app/theme/client/imports/components/sidebar/sidebar-header.css
@@ -40,23 +40,23 @@
border-radius: var(--sidebar-account-status-bullet-radius);
&--online {
- background-color: var(--status-online);
+ background-color: var(--rc-status-online);
}
&--away {
- background-color: var(--status-away);
+ background-color: var(--rc-status-away);
}
&--busy {
- background-color: var(--status-busy);
+ background-color: var(--rc-status-busy);
}
&--invisible {
- background-color: var(--status-invisible);
+ background-color: var(--rc-status-invisible);
}
&--offline {
- background-color: var(--status-invisible);
+ background-color: var(--rc-status-invisible);
}
}
}
@@ -109,25 +109,25 @@
& .rc-popover__item {
&--online {
& .rc-icon {
- color: var(--status-online);
+ color: var(--rc-status-online);
}
}
&--away {
& .rc-icon {
- color: var(--status-away);
+ color: var(--rc-status-away);
}
}
&--busy {
& .rc-icon {
- color: var(--status-busy);
+ color: var(--rc-status-busy);
}
}
&--offline {
& .rc-icon {
- color: var(--status-invisible);
+ color: var(--rc-status-invisible);
}
}
}
diff --git a/app/theme/client/imports/components/sidebar/sidebar-item.css b/app/theme/client/imports/components/sidebar/sidebar-item.css
index 125ed7283f4..17771d9a308 100644
--- a/app/theme/client/imports/components/sidebar/sidebar-item.css
+++ b/app/theme/client/imports/components/sidebar/sidebar-item.css
@@ -137,15 +137,15 @@
&-status {
&--online {
- color: var(--status-online);
+ color: var(--rc-status-online);
}
&--away {
- color: var(--status-away);
+ color: var(--rc-status-away);
}
&--busy {
- color: var(--status-busy);
+ color: var(--rc-status-busy);
}
}
}
@@ -175,19 +175,19 @@
border-radius: var(--sidebar-item-user-status-radius);
&--online {
- background-color: var(--status-online);
+ background-color: var(--rc-status-online);
}
&--away {
- background-color: var(--status-away);
+ background-color: var(--rc-status-away);
}
&--busy {
- background-color: var(--status-busy);
+ background-color: var(--rc-status-busy);
}
&--offline {
- background-color: var(--status-invisible-sidebar);
+ background-color: var(--rc-status-invisible-sidebar);
}
}
diff --git a/app/theme/client/imports/components/sidebar/sidebar.css b/app/theme/client/imports/components/sidebar/sidebar.css
index 6a273c8fa64..d7896386bce 100644
--- a/app/theme/client/imports/components/sidebar/sidebar.css
+++ b/app/theme/client/imports/components/sidebar/sidebar.css
@@ -63,8 +63,52 @@
}
}
- & .unread-rooms {
- display: none;
+ & .wrapper-unread {
+ position: relative;
+ z-index: 2;
+
+ & .unread-rooms {
+ position: absolute;
+ left: 50%;
+
+ overflow: hidden;
+
+ min-width: 120px;
+ max-width: 100%;
+
+ padding: 8px var(--sidebar-small-default-padding);
+
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
+
+ animation: fade 0.3s;
+
+ text-align: center;
+
+ white-space: nowrap;
+
+ text-overflow: ellipsis;
+
+ border-radius: 25px;
+
+ &.bottom-unread-rooms {
+ bottom: 0;
+ }
+
+ &.top-unread-rooms {
+ top: 0;
+ }
+ }
+ }
+}
+
+@keyframes fade {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
}
}
diff --git a/app/theme/client/imports/general/base.css b/app/theme/client/imports/general/base.css
index 701478ff684..e35353061bc 100644
--- a/app/theme/client/imports/general/base.css
+++ b/app/theme/client/imports/general/base.css
@@ -29,6 +29,7 @@ body {
background-color: var(--rc-color-primary);
+ font-family: var(--body-font-family);
font-size: var(--text-small-size);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
diff --git a/app/theme/client/imports/general/base_old.css b/app/theme/client/imports/general/base_old.css
index dc786964fff..c5be5ae73bd 100644
--- a/app/theme/client/imports/general/base_old.css
+++ b/app/theme/client/imports/general/base_old.css
@@ -1,7 +1,3 @@
-.rc-old .text-right {
- text-align: right;
-}
-
.rc-old .no-scroll {
overflow: hidden !important;
}
@@ -180,18 +176,6 @@
display: none !important;
}
-.rc-old .scrollable {
- position: absolute;
- top: 0;
- left: 0;
-
- overflow-y: scroll;
-
- width: 100%;
- height: 100%;
- -webkit-overflow-scrolling: touch;
-}
-
.rc-old .page-container {
position: absolute;
top: 0;
@@ -249,33 +233,11 @@
}
}
- & .logoutOthers {
- text-align: right;
- }
-
& .submit {
margin-top: 20px;
text-align: right;
}
-
- &.request-password {
- margin: 0 auto;
-
- & fieldset {
- margin-top: 20px;
-
- & label {
- display: block;
-
- margin-top: 20px;
- }
- }
-
- & .submit {
- text-align: center;
- }
- }
}
}
}
@@ -383,38 +345,6 @@
}
}
-/* input & form styles */
-
-.rc-old :not(.rcx-input-control):focus {
- outline: none;
- box-shadow: 0 0 0;
-}
-
-/* .rc-old textarea, */
-
-/*
-rc-old select,
-.rc-old input[type='text'],
-.rc-old input[type='number'],
-.rc-old input[type='email'],
-.rc-old input[type='url'],
-.rc-old input[type='password'] {
- position: relative;
-
- width: 100%;
- height: 35px;
- padding: 2px 8px;
-
- border-width: 1px;
- border-style: solid;
- border-radius: 5px;
- outline: none;
-
- line-height: normal;
- appearance: none;
-}
-*/
-
.rc-old input {
& .input-forward {
visibility: hidden;
@@ -459,16 +389,6 @@ rc-old select,
position: relative;
}
-.rc-old .form-group .input-group {
- padding: 2px 0;
-}
-
-.rc-old .form-horizontal .control-label {
- padding-top: 12px;
-
- font-weight: bold;
-}
-
.rc-old .-autocomplete-container {
top: auto;
@@ -616,26 +536,6 @@ rc-old select,
flex-grow: 0;
}
-.rc-old .sec-header {
- margin: 16px 0;
-
- text-align: center;
-
- & > * {
- display: inline-table;
-
- width: auto;
-
- vertical-align: middle;
-
- line-height: 35px;
- }
-
- & label {
- margin-left: 20px;
- }
-}
-
.rc-old.burger {
display: none;
visibility: hidden;
@@ -817,10 +717,6 @@ rc-old select,
border-radius: var(--border-radius);
}
-
- & .avatar-initials {
- line-height: 44px;
- }
}
& .data {
@@ -1072,21 +968,8 @@ rc-old select,
direction: rtl;
-webkit-overflow-scrolling: touch;
- &.no-shadow {
- box-shadow: 0 0 0;
- }
-
& > .wrapper {
direction: ltr;
-
- & .flex-control {
- margin-bottom: 30px;
-
- & .search {
- width: 100%;
- margin-bottom: 10px;
- }
- }
}
& h4 {
@@ -1156,10 +1039,6 @@ rc-old select,
}
}
- & .input-submit {
- margin: 35px 0 0 -4px;
- }
-
& .selected-users {
padding: 20px 0 0;
@@ -1186,67 +1065,8 @@ rc-old select,
height: var(--toolbar-height);
}
-.rc-old .toolbar-wrapper {
- display: flex;
-
- margin: 10px 8px;
-}
-
-.rc-old .toolbar-search {
- position: relative;
-
- width: 100%;
-}
-
-.rc-old .toolbar-search__icon {
- position: absolute;
- top: 0;
- left: 0;
-
- width: 35px;
-
- text-align: center;
-
- line-height: 35px;
-}
-
-.rc-old .toolbar-search__icon--cancel {
- right: 0;
- left: auto;
-
- cursor: pointer;
- transition: opacity 0.3s;
-
- opacity: 0;
-}
-
-.rc-old .toolbar-search__input {
- padding: 6px 35px !important;
-
- &:focus + .toolbar-search__icon--cancel {
- opacity: 1;
- }
-}
-
-.rc-old .toolbar-search__buttons {
- display: flex;
-
- margin-left: 5px;
- align-items: center;
-
- & i {
- width: 25px;
- height: 25px;
-
- cursor: pointer;
- text-align: center;
-
- line-height: 25px;
- }
-}
-
.rc-old .new-room-highlight a {
- animation: highlight 2s infinite;
+ animation: highlight 6s infinite;
}
.rc-old .fixed-title {
@@ -1342,11 +1162,20 @@ rc-old select,
text-align: center;
color: var(--rc-color-content);
+ background-color: var(--primary-background-color);
font-size: 1.2em;
line-height: 40px;
flex-flow: row nowrap;
+ &.warning {
+ background-color: var(--rc-color-alert);
+ }
+
+ &.error {
+ background-color: var(--rc-color-alert-message-warning);
+ }
+
& ~ .container-bars {
top: 45px;
}
@@ -1400,16 +1229,6 @@ rc-old select,
/* MAIN CONTENT + MAIN PAGES */
-.rc-old.main-content {
- & .container-fluid {
- padding-top: 0;
- }
-
- & .history-date {
- margin-bottom: 20px;
- }
-}
-
.rc-old .page-settings {
& .content {
& h2 {
@@ -1495,6 +1314,15 @@ rc-old select,
border-bottom: none;
}
+ & .add-token {
+ display: flex;
+
+ & .rc-select {
+ width: 40%;
+ margin: 0 0 0 10px;
+ }
+ }
+
&:first-child {
padding-top: 0;
}
@@ -1544,12 +1372,6 @@ rc-old select,
line-height: 1.2rem;
}
- & .color-editor {
- position: relative;
-
- width: 150px;
- }
-
& .selected-rooms .remove-room {
cursor: pointer;
}
@@ -1746,12 +1568,6 @@ rc-old select,
margin-bottom: 5px;
}
}
-
- & .user-image {
- float: right;
-
- margin-left: 12px;
- }
}
}
@@ -1970,14 +1786,6 @@ rc-old select,
width: 50%;
}
- & .room-topic {
- margin-left: 10px;
-
- opacity: 0.4;
-
- font-size: 14px;
- }
-
& .wrapper {
position: absolute;
top: 0;
@@ -1997,460 +1805,130 @@ rc-old select,
flex-shrink: 0;
}
- & .message-form {
- margin-bottom: 18px;
+ &.admin {
+ & .message:hover:not(.system) .message-action {
+ display: inline-block;
+ }
+ }
+}
- & > .message-input {
- position: relative;
+.rc-old .message-popup-position {
+ position: relative;
- display: flex;
- overflow: hidden;
+ .message-popup {
+ display: flex;
+ flex-direction: column;
- border-width: 1px;
- border-radius: 5px;
+ max-height: 20rem;
+ align-items: stretch;
- & > .share-items {
- position: relative;
+ .message-popup-items {
+ overflow-y: auto;
+ .popup-slash-command {
display: flex;
+ flex-flow: row wrap;
- & input {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
-
- overflow: hidden;
-
- cursor: pointer;
-
- opacity: 0;
-
- &::-webkit-file-upload-button {
- cursor: pointer;
- }
+ &-format {
+ flex-grow: 1;
}
- & i {
- font-size: 18px;
+ &-description {
+ text-align: right;
+
+ font-style: italic;
+ flex-grow: 2;
}
+ }
+ }
+ }
+}
- & > .message-buttons {
- position: relative;
+.rc-old .message-popup-items.preview-items {
+ display: flex;
+ overflow-y: auto;
+}
- display: flex;
- overflow: hidden;
+.rc-old .preview-items .popup-item {
+ padding: 5px;
- width: 35px;
- height: 35px;
+ line-height: unset;
+}
- cursor: pointer;
- text-align: center;
+.rc-old .rc-message-box .reply-preview__wrap {
+ position: relative;
+}
- border: 0;
- align-items: center;
- justify-content: center;
+.rc-old .rc-message-box .reply-preview {
- & input {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
+ position: relative;
- overflow: hidden;
+ display: flex;
- cursor: pointer;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
- opacity: 0;
+ padding-left: 15px;
- &::-webkit-file-upload-button {
- cursor: pointer;
- }
- }
+ background-color: #ffffff;
+ align-items: center;
+ justify-content: space-between;
- & i {
- font-size: 18px;
- }
- }
- }
+ .message {
+ flex: 1;
- & .input-message-container {
- position: relative;
+ padding: 0;
- width: 100%;
+ background-color: transparent;
+ }
+}
- & .inner-left-toolbar {
- position: absolute;
- top: 8px;
- left: 13px;
- }
- }
+.rc-old .rc-message-box .reply-preview:not(:last-child)::before {
- & > .message-buttons {
- position: relative;
+ position: absolute;
+ right: 15px;
- display: flex;
- flex: 0 0 35px;
+ bottom: 0;
+ left: 15px;
- cursor: pointer;
- transition: background-color 0.1s linear, color 0.1s linear;
- text-align: center;
+ height: 2px;
- border: 0;
- align-items: center;
- justify-content: center;
+ content: "";
- & i {
- transition: transform 0.3s ease-out;
- transform: rotate(0deg);
+ background: rgba(31, 35, 41, 0.08);
+}
- font-size: 18px;
- }
+.rc-old .rc-message-box .reply-preview-with-popup {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 5px;
+}
- & input {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
+.rc-old .reply-preview .cancel-reply {
+ padding: 10px;
+}
- overflow: hidden;
+.rc-message-box__icon.cancel-reply .rc-input__icon-svg--cross {
+ font-size: 1em;
+}
- width: 100%;
+.rc-old .message-popup.popup-with-reply-preview {
+ border-radius: 5px 5px 0 0;
+}
- cursor: pointer;
+.rc-old .message-popup {
+ position: absolute;
+ z-index: 101;
+ right: 0;
+ bottom: 0;
+ left: 0;
- opacity: 0;
-
- &::-webkit-file-upload-button {
- cursor: pointer;
- }
- }
- }
- }
-
- & textarea {
- display: block;
- overflow-y: auto;
-
- max-height: 155px;
- margin: 0;
- padding-top: 9px;
- padding-bottom: 9px;
- padding-left: 49px;
-
- resize: none;
-
- border-width: 0 1px 0 0;
- border-radius: 0;
-
- line-height: 16px;
- }
-
- & .users-typing {
- position: relative;
-
- display: inline-block;
- float: left;
- overflow: hidden;
-
- max-width: 100%;
- height: 23px;
- padding: 3px;
-
- white-space: nowrap;
- text-overflow: ellipsis;
-
- background-color: var(--color-white);
-
- font-size: 12px;
- }
-
- & .formatting-tips {
- display: flex;
- float: right;
- overflow: hidden;
-
- height: 25px;
- padding: 3px;
-
- transition: opacity 0.2s linear;
- white-space: nowrap;
-
- opacity: 0.5;
-
- font-size: 11px;
- align-items: center;
-
- & > * {
- margin: 0 3px;
- }
-
- &:hover {
- opacity: 1;
- }
-
- & q {
- padding: 0 0 0 3px;
-
- border-width: 0 0 0 3px;
-
- &::before {
- content: none !important;
- }
- }
-
- & code {
- overflow: hidden;
-
- vertical-align: top;
- white-space: nowrap;
-
- font-size: 10px;
- line-height: 13px;
- }
-
- & .hidden-br {
- display: inline-block;
- }
-
- & .icon-level-down::before {
- transform: rotate(90deg);
- }
- }
-
- & .editing-commands {
- display: none;
-
- text-transform: lowercase;
-
- & .editing-commands-cancel {
- float: left;
-
- height: 23px;
- padding: 3px;
-
- font-size: 11px;
- }
-
- & .editing-commands-save {
- float: right;
-
- height: 23px;
- padding: 3px;
-
- font-size: 11px;
- }
- }
-
- &.editing {
- & .formatting-tips,
- & .users-typing {
- display: none;
- }
-
- & .editing-commands {
- display: block;
- }
- }
- }
-
- & .add-user-search {
- display: inline-block;
- overflow: hidden;
-
- width: 100%;
- height: 100%;
-
- vertical-align: top;
- }
-
- &.admin {
- & .message:hover:not(.system) .message-action {
- display: inline-block;
- }
- }
-
- & .secondary-name {
- color: #666666;
-
- font-size: 15px;
- }
-}
-
-.rc-old .message-popup-position {
- position: relative;
-
- .message-popup {
- display: flex;
- flex-direction: column;
-
- max-height: 20rem;
- align-items: stretch;
-
- .message-popup-items {
- overflow-y: auto;
-
- .popup-slash-command {
- display: flex;
- flex-flow: row wrap;
-
- &-format {
- flex-grow: 1;
- }
-
- &-description {
- text-align: right;
-
- font-style: italic;
- flex-grow: 2;
- }
- }
- }
- }
-}
-
-.rc-old .message-popup-items.preview-items {
- display: flex;
- overflow-y: auto;
-}
-
-.rc-old .preview-items .popup-item {
- padding: 5px;
-
- line-height: unset;
-}
-
-.rc-old .rc-message-box .reply-preview__wrap {
- position: relative;
-}
-
-.rc-old .rc-message-box .reply-preview {
-
- position: relative;
-
- display: flex;
-
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-
- padding-left: 15px;
-
- background-color: #ffffff;
- align-items: center;
- justify-content: space-between;
-
- .message {
- flex: 1;
-
- padding: 0;
-
- background-color: transparent;
- }
-}
-
-.rc-old .rc-message-box .reply-preview:not(:last-child)::before {
-
- position: absolute;
- right: 15px;
-
- bottom: 0;
- left: 15px;
-
- height: 2px;
-
- content: "";
-
- background: rgba(31, 35, 41, 0.08);
-}
-
-.rc-old .rc-message-box .reply-preview-with-popup {
- border-top-left-radius: 0;
- border-top-right-radius: 0;
- border-bottom-left-radius: 5px;
-}
-
-.rc-old .reply-preview .cancel-reply {
- padding: 10px;
-}
-
-.rc-message-box__icon.cancel-reply .rc-input__icon-svg--cross {
- font-size: 1em;
-}
-
-.rc-old .message-popup.popup-with-reply-preview {
- border-radius: 5px 5px 0 0;
-}
-
-.rc-old .message-popup {
- position: absolute;
- z-index: 101;
- right: 0;
- bottom: 0;
- left: 0;
-
- overflow: hidden;
+ overflow: hidden;
border-radius: 5px;
box-shadow:
0 -1px 10px 0 rgba(0, 0, 0, 0.2),
0 1px 1px rgba(0, 0, 0, 0.16);
-
- &.popup-down {
- top: 0;
- bottom: auto;
- }
-
- &.search-results-list {
- top: 0;
-
- overflow-y: auto;
-
- height: calc(100vh - calc(var(--header-min-height) + calc(var(--toolbar-height) + var(--footer-min-height))));
- padding: 25px 0 0 8px;
-
- text-align: left;
-
- border-radius: 0;
- box-shadow: none;
- direction: rtl;
-
- & .popup-item {
- position: relative;
-
- overflow: hidden;
-
- height: 30px;
- padding-left: 6px;
-
- white-space: nowrap;
- text-overflow: ellipsis;
- direction: ltr;
-
- & i::before {
- margin-right: 0;
- }
- }
-
- & .room-title {
- font-size: 15px;
- line-height: 30px;
- }
-
- & .bold {
- font-weight: 700;
- }
-
- & .unread {
- top: 8px;
- }
-
- & .loading-animation {
- position: relative;
- }
- }
}
.rc-old .message-popup-title {
@@ -2489,7 +1967,6 @@ rc-old select,
width: 10px;
height: 10px;
- border-width: 1px;
border-radius: 10px;
}
@@ -2649,7 +2126,7 @@ rc-old select,
}
&.highlight {
- animation: highlight 3s;
+ animation: highlight 6s;
}
&:nth-child(1) {
@@ -2657,6 +2134,9 @@ rc-old select,
}
& .time {
+ padding-right: 2px;
+ padding-left: 2px;
+
white-space: nowrap;
}
@@ -2802,6 +2282,10 @@ rc-old select,
display: none;
}
+ .message-custom-status {
+ display: none;
+ }
+
& .title {
position: absolute;
left: 5px;
@@ -2844,10 +2328,6 @@ rc-old select,
}
}
- & .avatar-initials {
- line-height: 40px;
- }
-
& .body {
transition: opacity 0.3s linear;
@@ -2928,6 +2408,7 @@ rc-old select,
}
& .message-alias {
+ padding-right: 2px;
padding-left: 2px;
font-weight: 400;
@@ -3292,49 +2773,19 @@ rc-old select,
& .share {
min-height: 40px;
- border-radius: 50%;
-
- line-height: 20px;
-
- &::before {
- border-radius: 50%;
- }
-
- & span {
- display: none;
- }
- }
- }
-
- /*
- & .close-flex-tab {
- position: absolute;
- z-index: 20;
- top: 3px;
- right: 5px;
-
- width: 30px;
- height: 30px;
-
- text-align: center;
-
- & span {
- display: inline-block;
-
- width: 21px;
- height: 20px;
+ border-radius: 50%;
- & i {
- display: inline-block;
+ line-height: 20px;
- width: 18px;
- height: 18px;
+ &::before {
+ border-radius: 50%;
+ }
- font-size: 10px;
+ & span {
+ display: none;
}
}
}
- */
}
& .flex-tab-bar {
@@ -3615,12 +3066,6 @@ rc-old select,
}
}
- & .contact-code {
- margin: -5px 0 10px;
-
- font-size: 12px;
- }
-
& .channels {
& h3 {
margin-bottom: 8px;
@@ -3669,10 +3114,6 @@ rc-old select,
}
}
}
-
- & .room-info-content > div {
- margin: 0 0 20px;
- }
}
.rc-old .edit-form {
@@ -3725,156 +3166,6 @@ rc-old select,
}
}
-.rc-old .user-image {
- position: relative;
-
- display: inline-block;
-
- width: var(--user-image-square);
- height: var(--user-image-square);
- margin: 4px;
-
- cursor: pointer;
-
- font-size: 12px;
-
- &:hover,
- &.selected .avatar::after {
- transform: scaleX(1);
- }
-
- & .avatar {
- overflow: visible;
-
- &::before {
- font-size: 10px;
- }
-
- &::after {
- position: absolute;
- z-index: 1;
- top: 8px;
- left: -12px;
-
- width: 6px;
- height: 6px;
-
- content: " ";
-
- border-radius: 3px;
- }
-
- & .avatar-initials {
- line-height: var(--user-image-square);
- }
- }
-
- & p {
- display: none;
- }
-
- & button {
- display: block;
-
- width: 100%;
- height: 100%;
- }
-}
-
-.rc-old .lines .user-image {
- width: 100%;
- margin: 0;
-
- &::after {
- display: none;
- }
-
- & button {
- display: block;
- clear: both;
-
- height: 30px;
- padding: 5px 0;
-
- &::after {
- display: table;
- clear: both;
-
- content: "";
- }
-
- & > div {
- float: left;
-
- width: var(--user-image-square);
- height: var(--user-image-square);
-
- margin-left: 1rem;
- }
- }
-
- & p {
- position: relative;
-
- display: block;
- float: left;
- overflow: hidden;
-
- width: calc(100% - 45px);
- padding-left: 10px;
-
- text-overflow: ellipsis;
-
- font-size: 15px;
- font-weight: 400;
- line-height: var(--user-image-square);
- }
-}
-
-.rc-old .user-profile {
- overflow: hidden;
-
- white-space: normal;
-
- & .thumb {
- float: left;
-
- width: 75px;
-
- & img {
- width: 60px;
- height: 60px;
- }
- }
-
- & .info {
- display: block;
-
- margin-left: 75px;
-
- & h3 {
- margin-bottom: 8px;
-
- font-size: 14px;
- font-weight: 600;
- }
-
- & p {
- margin-bottom: 6px;
-
- font-size: 12px;
- }
-
- & a:hover {
- text-decoration: none;
- }
- }
-}
-
-.rc-old .profile-buttons {
- margin-top: 1em;
-}
-
.rc-old .avatarPrompt {
& header p {
font-size: 14px;
@@ -3887,14 +3178,6 @@ rc-old select,
}
}
-.rc-old .select-arrow {
- position: absolute;
- right: 4px;
- bottom: 11px;
-
- color: #a9a9a9;
-}
-
.rc-old #login-card {
position: relative;
z-index: 1;
@@ -4027,9 +3310,15 @@ rc-old select,
}
.rc-old .oauth-login {
+ display: flex;
+
margin-bottom: 16px;
+
margin-left: -4px;
- flex-wrap: wrap;
+
+ flex-flow: row wrap;
+
+ justify-content: space-around;
& h3 {
margin-top: 0;
@@ -4042,10 +3331,17 @@ rc-old select,
font-weight: 300;
}
+ button {
+ margin: 0 5px;
+
+ flex-grow: 1;
+ }
+
& .button {
margin-bottom: 4px;
font-size: 18px;
+
line-height: 22px;
flex-grow: 1;
@@ -4226,15 +3522,6 @@ rc-old select,
}
}
-.rc-old #particles-js {
- position: fixed;
- top: 0;
- left: 0;
-
- width: 100%;
- height: 100%;
-}
-
.rc-old .highlight-text {
padding: 2px;
@@ -4339,59 +3626,6 @@ rc-old select,
}
}
-.rc-old .statistics-table {
- width: 100%;
- margin-bottom: 30px;
-
- & th,
- & td {
- padding: 6px 8px;
-
- text-align: left;
- word-break: break-word;
- }
-
- & th {
- width: 30%;
-
- text-align: right;
- }
-
- & td {
- width: 70%;
- }
-}
-
-.rc-old .rocket-team {
- display: block;
-
- & li {
- display: inline-block;
- }
-
- & a {
- display: inline-block;
-
- width: 50px;
- height: 50px;
- margin-right: 5px;
-
- border-radius: 50%;
- background-position: 50% 50%;
- background-size: 100%;
- }
-}
-
-.rc-old #fullscreendiv:-webkit-full-screen {
- position: fixed;
- top: 0;
-
- width: 100%;
- height: 100%;
-
- background: none;
-}
-
.rc-old .dropzone {
& .dropzone-overlay {
@@ -4445,25 +3679,6 @@ rc-old select,
}
}
-.zoomIn {
- -webkit-animation-name: zoomIn;
- animation-name: zoomIn;
-}
-
-.rc-old .is-cordova {
- & .flex-tab {
- & .control {
- padding-left: 50px;
- }
-
- & button.more {
- width: 60px;
-
- transform: translateX(-57px);
- }
- }
-}
-
.rc-old .touch .footer {
padding-right: 10px;
padding-left: 10px;
@@ -4625,24 +3840,6 @@ rc-old select,
font-size: 80px;
}
-.rc-old .colorpicker-input {
- text-indent: 34px;
-}
-
-.rc-old .colorpicker-swatch {
- position: absolute;
- top: 4px;
- left: 4px;
-
- display: block;
- overflow: hidden;
-
- width: 32px;
- height: 32px;
-
- border-radius: 2px;
-}
-
.rc-old .inline-video {
width: 100%;
max-width: 480px;
@@ -4704,30 +3901,6 @@ rc-old select,
background-color: #f8f8f8;
}
-.rc-old .new-logs {
- position: absolute;
- bottom: 8px;
- left: 50%;
-
- width: 130px;
- height: 30px;
- margin: 0 -65px;
-
- cursor: pointer;
- transition: transform 0.3s ease-out;
- transform: translateY(0);
- text-align: center;
-
- border-radius: 20px;
-
- font-size: 0.8em;
- line-height: 30px;
-
- &.not {
- transform: translateY(150%);
- }
-}
-
.rc-old .powered-by {
margin-top: 1em;
}
@@ -4824,10 +3997,6 @@ rc-old select,
}
}
-.sweet-alert .sa-input-error {
- top: 19px;
-}
-
.rc-old .collapse-switch {
cursor: pointer;
}
@@ -4919,14 +4088,6 @@ rc-old select,
min-height: 36px;
padding: 0;
- & .message-form {
- margin-bottom: 0;
- }
-
- & .message-input {
- border-width: 0;
- }
-
& .users-typing {
display: none;
}
@@ -4961,10 +4122,6 @@ rc-old select,
.rc-old.main-content {
transform: translateX(0) !important;
}
-
- .sweet-alert {
- margin-left: -239px !important;
- }
}
@media (width <= 780px) {
@@ -4988,20 +4145,6 @@ rc-old select,
}
}
- .rc-old .sweet-alert {
- & h2 {
- margin: 10px 0;
-
- font-size: 20px;
- line-height: 30px;
- }
-
- & button {
- margin-top: 6px;
- padding: 10px 22px;
- }
- }
-
.rc-old .code-mirror-box.code-mirror-box-fullscreen {
left: 0;
}
@@ -5025,13 +4168,18 @@ rc-old select,
display: none;
}
}
-}
-@media (width <= 500px) {
- .rc-old .messages-container .message-form > .formatting-tips {
- display: none;
+ .add-token {
+ display: block !important;
+
+ & .rc-select {
+ width: auto !important;
+ margin: 10px 0 !important;
+ }
}
+}
+@media (width <= 500px) {
.cms-page {
padding: 0;
@@ -5063,10 +4211,6 @@ rc-old select,
.rc-old .oauth-login {
margin-bottom: 6px;
}
-
- .rc-old .message-form textarea {
- max-height: 100px !important;
- }
}
@media (width <= 440px) {
@@ -5087,12 +4231,6 @@ rc-old select,
}
}
-@media (height <= 260px) {
- .rc-old .message-form textarea {
- max-height: 50px !important;
- }
-}
-
.room-leader .chat-now {
position: absolute;
right: 25px;
diff --git a/app/theme/client/imports/general/forms.css b/app/theme/client/imports/general/forms.css
index fe059b34ad2..9516f91a207 100644
--- a/app/theme/client/imports/general/forms.css
+++ b/app/theme/client/imports/general/forms.css
@@ -1,63 +1,4 @@
.input {
- &.radio {
- position: relative;
-
- min-height: 13px;
-
- & input {
- position: absolute;
- z-index: -1;
- top: 0;
- left: 0;
-
- width: 0;
- height: 0;
-
- opacity: 0;
- outline: 0;
-
- &:checked + label::after {
- opacity: 1;
- }
- }
-
- & label {
- padding-left: 20px;
-
- cursor: pointer;
- user-select: none;
-
- &::before {
- position: absolute;
- top: 0;
- left: 0;
-
- width: 15px;
- height: 15px;
-
- content: '';
-
- border-width: 1px;
- border-radius: 50%;
- }
-
- &::after {
- position: absolute;
- top: 4px;
- left: 4px;
-
- width: 7px;
- height: 7px;
-
- content: '';
- transition: opacity 0.2s ease-out;
-
- opacity: 0;
- border-radius: 50%;
- }
- }
- }
-
&.checkbox.toggle {
position: relative;
@@ -197,7 +138,7 @@
.rc-button-group {
display: flex;
- margin: 0 calc(- var(--default-small-padding) / 2);
+ margin: 0 calc(var(--default-small-padding) / -2);
& .rc-button {
margin: 0 calc(var(--default-small-padding) / 2);
diff --git a/app/theme/client/imports/general/rtl.css b/app/theme/client/imports/general/rtl.css
index 8af3e201c54..dc230d84010 100644
--- a/app/theme/client/imports/general/rtl.css
+++ b/app/theme/client/imports/general/rtl.css
@@ -18,10 +18,6 @@
text-align: right;
}
- & .text-right {
- text-align: left;
- }
-
& .main-content {
left: 0;
@@ -102,37 +98,6 @@
right: 0;
left: auto;
}
-
- & .message-form {
- & > div .input-message-container .inner-left-toolbar {
- right: 13px;
- left: auto;
- }
-
- & textarea {
- padding-right: 49px;
- padding-left: 8px;
-
- text-align: right;
-
- border-width: 0 0 0 1px;
- border-right-width: 0;
- }
-
- & > .formatting-tips {
- position: relative;
- right: auto;
-
- float: left;
-
- & q {
- padding: 0 3px 0 0;
-
- border-right: 3px solid;
- border-left: 0 none;
- }
- }
- }
}
& .account-box .options {
@@ -250,24 +215,6 @@
}
}
- & .user-image .avatar::after {
- right: -12px;
- left: auto;
- }
-
- & .lines .user-image {
- & button > div {
- float: right;
- }
-
- & p {
- float: right;
-
- padding-right: 10px;
- padding-left: auto;
- }
- }
-
& .user-view {
& nav {
margin-right: -4px;
@@ -454,25 +401,11 @@
margin-left: auto;
}
- & .user-image {
- float: left;
-
- margin-right: 12px;
- margin-left: auto;
- }
-
& table thead th {
text-align: right;
}
}
- & .statistics-table {
- & th,
- & td {
- text-align: right;
- }
- }
-
& .code-mirror-box {
direction: ltr;
@@ -497,10 +430,6 @@
right: 0;
}
- & .logoutOthers {
- text-align: left;
- }
-
& .submit {
text-align: left;
}
@@ -512,26 +441,6 @@
left: 12px;
}
- & .toolbar-search__icon {
- right: 0;
- }
-
- & .toolbar-search__icon--cancel {
- right: auto;
- left: 0;
- }
-
- & .message-popup.search-results-list {
- padding: 25px 8px 0 0;
-
- text-align: right;
- direction: ltr;
-
- & .popup-item {
- direction: rtl;
- }
- }
-
@media (width <= 1100px) {
& #rocket-chat .flex-opened {
left: 0;
diff --git a/app/theme/client/imports/general/theme_old.css b/app/theme/client/imports/general/theme_old.css
new file mode 100644
index 00000000000..268ea9cdc63
--- /dev/null
+++ b/app/theme/client/imports/general/theme_old.css
@@ -0,0 +1,553 @@
+.content-background-color {
+ background-color: var(--content-background-color);
+}
+
+.color-content-background-color {
+ color: var(--content-background-color);
+}
+
+.primary-background-color {
+ background-color: var(--primary-background-color);
+}
+
+.color-primary-font-color {
+ color: var(--primary-font-color);
+}
+
+.color-primary-action-color {
+ color: var(--primary-action-color);
+}
+
+.background-primary-action-color {
+ background-color: var(--primary-action-color);
+}
+
+.secondary-background-color {
+ background-color: var(--secondary-background-color);
+}
+
+.border-secondary-background-color {
+ border-color: var(--secondary-background-color);
+}
+
+.secondary-font-color {
+ color: var(--secondary-font-color);
+}
+
+.border-component-color {
+ border-color: var(--component-color);
+}
+
+.background-component-color {
+ background-color: var(--component-color);
+}
+
+.color-component-color {
+ color: var(--component-color);
+}
+
+.success-color {
+ color: var(--success-color);
+}
+
+.pending-color {
+ color: var(--pending-color);
+}
+
+.pending-background {
+ background-color: var(--pending-background);
+}
+
+.pending-border {
+ border-color: var(--pending-border);
+}
+
+.error-color {
+ color: var(--error-color);
+}
+
+.background-error-color {
+ background-color: var(--error-color);
+}
+
+.color-info-font-color {
+ color: var(--info-font-color);
+}
+
+.background-info-font-color {
+ background-color: var(--info-font-color);
+}
+
+.background-attention-color {
+ background-color: var(--attention-color);
+}
+
+.tertiary-background-color {
+ background-color: var(--tertiary-background-color);
+}
+
+.border-tertiary-background-color {
+ border-color: var(--tertiary-background-color);
+}
+
+.color-tertiary-font-color {
+ color: var(--tertiary-font-color);
+}
+
+.error-background {
+ background-color: var(--error-background);
+}
+
+.error-border {
+ border-color: var(--error-border);
+}
+
+.color-error-contrast {
+ color: var(--error-contrast);
+}
+
+.background-transparent-darkest {
+ background-color: var(--transparent-darkest);
+}
+
+.background-transparent-darker {
+ background-color: var(--transparent-darker);
+}
+
+.background-transparent-darker-hover:hover {
+ background-color: var(--transparent-darker);
+}
+
+.background-transparent-darker-before::before {
+ background-color: var(--transparent-darker);
+}
+
+.background-transparent-dark {
+ background-color: var(--transparent-dark);
+}
+
+.background-transparent-dark-hover:hover {
+ background-color: var(--transparent-dark);
+}
+
+.border-transparent-dark {
+ border-color: var(--transparent-dark);
+}
+
+.background-transparent-light {
+ background-color: var(--transparent-light);
+}
+
+.border-transparent-lighter {
+ border-color: var(--transparent-lighter);
+}
+
+.background-transparent-lightest {
+ background-color: var(--transparent-lightest);
+}
+
+.color-primary-action-contrast {
+ color: var(--primary-action-contrast);
+}
+
+* {
+ -webkit-overflow-scrolling: touch;
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+
+ background: var(--transparent-dark);
+ }
+
+ &::-webkit-scrollbar-thumb {
+ border-radius: 50px;
+ background-color: var(--custom-scrollbar-color);
+ }
+
+ &::-webkit-scrollbar-corner {
+ background-color: var(--transparent-dark);
+ }
+}
+
+.filter-item {
+ &:hover {
+ border-color: var(--info-font-color);
+ }
+
+ &.active {
+ border-color: var(--primary-background-color);
+ }
+}
+
+.burger i {
+ background-color: var(--primary-font-color);
+}
+
+.input-line {
+ &.setting-changed > label {
+ color: var(--selection-color);
+ }
+}
+
+input:-webkit-autofill {
+ color: var(--primary-font-color) !important;
+ background-color: transparent !important;
+}
+
+input,
+select,
+textarea {
+ color: var(--primary-font-color);
+ border-style: solid;
+ border-color: var(--input-border-color);
+ background-color: transparent;
+
+ &::placeholder {
+ color: var(--input-placeholder-color);
+ }
+
+ &[disabled] {
+ background-color: var(--button-disabled-background);
+ }
+}
+
+.disabled label,
+[disabled] label {
+ color: var(--input-placeholder-color);
+}
+
+.-autocomplete-container {
+ background-color: var(--popup-list-background);
+}
+
+.-autocomplete-item.selected {
+ background-color: var(--popup-list-selected-background);
+}
+
+.rc-old input[type="button"],
+.rc-old input[type="submit"] {
+ color: var(--button-secondary-text-color);
+ border-color: var(--button-secondary-background);
+ background: var(--button-secondary-background);
+}
+
+.input {
+ &.checkbox.toggle {
+ input:checked + label::before {
+ background-color: var(--button-primary-background);
+ }
+
+ input:disabled + label::before {
+ background-color: var(--button-disabled-background) !important;
+ }
+
+ label {
+ &::before {
+ background-color: var(--button-secondary-background);
+ }
+
+ &::after {
+ background-color: var(--button-primary-text-color);
+ }
+
+ &:hover {
+ &::before {
+ opacity: 0.6;
+ }
+ }
+ }
+ }
+}
+
+.message,
+.flex-tab {
+ a i,
+ a[class^="icon-"] {
+ color: var(--primary-font-color);
+
+ &:hover {
+ opacity: 0.6;
+ }
+ }
+}
+
+.error {
+ border-color: var(--error-color);
+}
+
+.page-list,
+.page-settings {
+ a:not(.rc-button) {
+ color: var(--primary-font-color);
+
+ &:hover {
+ color: var(--primary-action-color);
+ }
+ }
+}
+
+.admin-table-row {
+ background-color: var(--transparent-light);
+
+ &:nth-of-type(even) {
+ background-color: var(--transparent-lightest);
+ }
+}
+
+.avatar-suggestion-item {
+ .question-mark::before {
+ color: var(--secondary-font-color);
+ }
+}
+
+.full-page,
+.page-loading {
+ a {
+ color: var(--tertiary-font-color);
+ }
+
+ a:hover {
+ color: var(--primary-background-contrast);
+ }
+}
+
+#login-card {
+ .input-text {
+ input:-webkit-autofill {
+ box-shadow: 0 0 0 20px var(--content-background-color) inset;
+ }
+ }
+}
+
+.toggle-favorite {
+ color: var(--component-color);
+}
+
+.upload-progress-progress {
+ background-color: var(--success-background);
+}
+
+.messages-container {
+ .footer {
+ background: var(--content-background-color);
+ }
+}
+
+.message.editing {
+ background-color: var(--message-box-editing-color);
+}
+
+.rc-old {
+ & .popup-item {
+ &.selected {
+ color: var(--primary-action-contrast);
+ background-color: var(--primary-action-color);
+ }
+ }
+}
+
+.messages-box {
+ &.selectable .selected {
+ background-color: var(--selection-background);
+ }
+}
+
+.message {
+ &.new-day::before {
+ background-color: var(--content-background-color);
+ }
+
+ &.new-day::after {
+ border-color: var(--component-color);
+ }
+
+ a {
+ color: var(--link-font-color);
+
+ &:hover {
+ opacity: 0.6;
+ }
+ }
+
+ .highlight-text {
+ background-color: var(--selection-background);
+ }
+}
+
+.sidebar-item__last-message {
+ a:not(.mention-link) {
+ color: var(--link-font-color);
+
+ &:hover {
+ opacity: 0.6;
+ }
+ }
+}
+
+.flex-tab-bar {
+ .tab-button {
+ &:hover {
+ background-color: var(--secondary-background-color);
+ }
+
+ &.active {
+ border-right-color: var(--selection-color);
+ background-color: var(--secondary-background-color);
+ }
+
+ &.attention {
+ animation-name: blink;
+ animation-duration: 1000ms;
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+ }
+ }
+
+ .counter {
+ color: white;
+ background: var(--secondary-font-color);
+ }
+}
+
+i.status-online {
+ color: var(--rc-status-online);
+}
+
+.status-bg-online {
+ background-color: var(--rc-status-online);
+}
+
+.account-box .status-online .thumb::after,
+.account-box .status.online::after,
+.popup-user-status-online,
+.status-online::after {
+ background-color: var(--rc-status-online);
+}
+
+.account-box .status-offline .thumb::after,
+.account-box .status.offline::after {
+ background-color: var(--transparent-lighter);
+}
+
+i.status-away {
+ color: var(--rc-status-away);
+}
+
+.status-bg-away {
+ background-color: var(--rc-status-away);
+}
+
+.account-box .status-away .thumb::after,
+.account-box .status.away::after,
+.popup-user-status-away,
+.status-away::after,
+.status-pending::after {
+ background-color: var(--rc-status-away);
+}
+
+i.status-busy {
+ color: var(--rc-status-busy);
+}
+
+.status-bg-busy {
+ background-color: var(--rc-status-busy);
+}
+
+.account-box .status-busy .thumb::after,
+.account-box .status.busy::after,
+.popup-user-status-busy,
+.status-busy::after {
+ background-color: var(--rc-status-busy);
+}
+
+i.status-offline {
+ color: var(--rc-status-offline);
+}
+
+.status-bg-offline {
+ background-color: var(--rc-status-offline);
+}
+
+.popup-user-status-offline,
+.status-offline::after {
+ background-color: var(--rc-status-offline);
+}
+
+.alert-warning {
+ color: var(--primary-font-color);
+ border-color: var(--rc-color-alert);
+ background-color: var(--message-box-editing-color);
+}
+
+.alert-link {
+ color: var(--link-font-color);
+
+ &:hover {
+ opacity: 0.6;
+ }
+}
+
+label.required::after {
+ color: var(--error-color);
+}
+
+.main-content,
+.flex-tab {
+ .loading-animation > .bounce {
+ background-color: var(--primary-font-color);
+ }
+}
+
+.loading-animation.loading-animation--primary > .bounce {
+ background-color: var(--primary-font-color);
+}
+
+@keyframes blink {
+ from {
+ color: var(--selection-color);
+ }
+
+ to {
+ opacity: inherit;
+ }
+}
+
+.range-slider-range::-webkit-slider-thumb {
+ background-color: var(--button-primary-background);
+}
+
+.range-slider-range::-webkit-slider-thumb:hover {
+ opacity: 0.6;
+}
+
+.range-slider-range:active::-webkit-slider-thumb {
+ opacity: 0.9;
+}
+
+.range-slider-range::-moz-range-thumb {
+ background-color: var(--button-primary-background);
+}
+
+.range-slider-range::-moz-range-thumb:hover {
+ opacity: 0.6;
+}
+
+.range-slider-range:active::-moz-range-thumb {
+ opacity: 0.9;
+}
+
+.range-slider-range::-moz-range-track {
+ background-color: var(--tertiary-background-color);
+}
+
+.range-slider-value {
+ color: var(--button-primary-text-color);
+ background-color: var(--button-primary-background);
+}
+
+.range-slider-value::after {
+ border-top-color: transparent;
+ border-right-color: var(--rc-color-button-primary);
+ border-bottom-color: transparent;
+}
diff --git a/app/theme/client/imports/general/variables.css b/app/theme/client/imports/general/variables.css
index 2cfb61fd812..fb317250592 100644
--- a/app/theme/client/imports/general/variables.css
+++ b/app/theme/client/imports/general/variables.css
@@ -1,7 +1,7 @@
:root {
/*
- * Color palette
- */
+ * Color palette
+ */
--color-dark-100: #0c0d0f;
--color-dark-90: #1e232a;
--color-dark-80: #2e343e;
@@ -17,7 +17,7 @@
--color-purple: #861da8;
--color-red: #f5455c;
--color-dark-red: #e0364d;
- --color-orange: #f59547;
+ --color-orange: #f38c39;
--color-yellow: #ffd21f;
--color-dark-yellow: #f6c502;
--color-green: #2de0a5;
@@ -36,9 +36,11 @@
--color-gray-lightest: #f2f3f5;
--color-black: #000000;
--color-white: #ffffff;
+
+ /* #region colors Colors */
--rc-color-error: var(--color-red);
--rc-color-error-light: #e1364c;
- --rc-color-alert: var(--color-yellow);
+ --rc-color-alert: var(--color-orange);
--rc-color-alert-light: var(--color-dark-yellow);
--rc-color-success: var(--color-green);
--rc-color-success-light: #25d198;
@@ -60,6 +62,39 @@
--rc-color-content: var(--color-white);
--rc-color-link-active: var(--rc-color-button-primary);
+ /* #endregion */
+
+ /* #region colors Old Colors */
+ --content-background-color: #ffffff;
+ --primary-background-color: #04436a;
+ --primary-font-color: #444444;
+ --primary-action-color: #1d74f5;
+ --secondary-background-color: #f4f4f4;
+ --secondary-font-color: #a0a0a0;
+ --secondary-action-color: #dddddd;
+ --component-color: #f2f3f5;
+ --pending-color: #fcb316;
+ --error-color: #bc2031;
+ --success-color: #2de0a5;
+ --selection-color: #02acec;
+ --attention-color: #9c27b0;
+
+ /* #endregion */
+
+ /* #region less-colors Old Colors (minor) */
+ --tertiary-background-color: var(--component-color);
+ --tertiary-font-color: var(--transparent-lightest);
+ --link-font-color: var(--primary-action-color);
+ --info-font-color: var(--secondary-font-color);
+ --custom-scrollbar-color: var(--transparent-darker);
+
+ /* #endregion */
+
+ /* #region fonts Fonts */
+ --body-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Meiryo UI', Arial, sans-serif;
+
+ /* #endregion */
+
/*
* General
*/
@@ -71,14 +106,14 @@
--flex-tab-width: 400px;
--flex-tab-webrtc-width: 400px;
--flex-tab-webrtc-2-width: 850px;
- --user-image-square: 20px;
--border: 2px;
--border-radius: 2px;
- --status-online: var(--rc-color-success);
- --status-away: var(--rc-color-alert);
- --status-busy: var(--rc-color-error);
- --status-invisible: var(--color-gray-medium);
- --status-invisible-sidebar: var(--rc-color-primary-darkest);
+ --rc-status-online: var(--rc-color-success);
+ --rc-status-away: var(--rc-color-alert);
+ --rc-status-busy: var(--rc-color-error);
+ --rc-status-invisible: var(--color-gray-medium);
+ --rc-status-offline: var(--transparent-darker);
+ --rc-status-invisible-sidebar: var(--rc-color-primary-darkest);
--default-padding: 1.5rem;
--default-small-padding: 1rem;
--status-bullet-size: 10px;
diff --git a/app/theme/client/main.css b/app/theme/client/main.css
index 56d02e89df8..fa98f893547 100644
--- a/app/theme/client/main.css
+++ b/app/theme/client/main.css
@@ -58,3 +58,6 @@
/* RTL */
@import 'imports/general/rtl.css';
+
+/* Legacy theming */
+@import 'imports/general/theme_old.css';
diff --git a/app/theme/server/server.js b/app/theme/server/server.js
index 9cde1fc6753..1b3ee5ed743 100644
--- a/app/theme/server/server.js
+++ b/app/theme/server/server.js
@@ -26,7 +26,6 @@ export const theme = new class {
constructor() {
this.variables = {};
this.packageCallbacks = [];
- this.files = ['server/colors.less'];
this.customCSS = '';
settings.add('css', '');
settings.addGroup('Layout');
@@ -59,9 +58,7 @@ export const theme = new class {
}
compile() {
- let content = [this.getVariablesAsLess()];
-
- content.push(...this.files.map((name) => Assets.getText(name)));
+ let content = [];
content.push(...this.packageCallbacks.map((name) => name()));
@@ -122,14 +119,6 @@ export const theme = new class {
}
}
- addPublicColor(name, value, section, editor = 'color', property) {
- return this.addVariable('color', name, value, section, true, editor, ['color', 'expression'], property);
- }
-
- addPublicFont(name, value) {
- return this.addVariable('font', name, value, 'Fonts', true);
- }
-
getVariablesAsObject() {
return Object.keys(this.variables).reduce((obj, name) => {
obj[name] = this.variables[name].value;
@@ -137,13 +126,6 @@ export const theme = new class {
}, {});
}
- getVariablesAsLess() {
- return Object.keys(this.variables).map((name) => {
- const variable = this.variables[name];
- return `@${ name }: ${ variable.value };`;
- }).join('\n');
- }
-
addPackageAsset(cb) {
this.packageCallbacks.push(cb);
return this.compileDelayed();
diff --git a/app/theme/server/variables.js b/app/theme/server/variables.js
index cf4d7bd587f..c95c243ffeb 100644
--- a/app/theme/server/variables.js
+++ b/app/theme/server/variables.js
@@ -10,62 +10,43 @@ import { settings } from '../../settings';
// Major colors form the core of the scheme
// Names changed to reflect usage, comments show pre-refactor names
-const reg = /--(rc-color-.*?): (.*?);/igm;
-
-const colors = [...Assets.getText('client/imports/general/variables.css').match(reg)].map((color) => {
- const [name, value] = color.split(': ');
- return [name.replace('--', ''), value.replace(';', '')];
-});
-
-colors.forEach(([key, color]) => {
- if (/var/.test(color)) {
- const [, value] = color.match(/var\(--(.*?)\)/i);
- return theme.addPublicColor(key, value, 'Colors', 'expression');
- }
- theme.addPublicColor(key, color, 'Colors');
-});
-
-const majorColors = {
- 'content-background-color': '#FFFFFF',
- 'primary-background-color': '#04436A',
- 'primary-font-color': '#444444',
- 'primary-action-color': '#1d74f5', // was action-buttons-color
- 'secondary-background-color': '#F4F4F4',
- 'secondary-font-color': '#A0A0A0',
- 'secondary-action-color': '#DDDDDD',
- 'component-color': '#f2f3f5',
- 'success-color': '#4dff4d',
- 'pending-color': '#FCB316',
- 'error-color': '#BC2031',
- 'selection-color': '#02ACEC',
- 'attention-color': '#9C27B0',
-};
-
-// Minor colours implement major colours by default, but can be overruled
-const minorColors = {
- 'tertiary-background-color': '@component-color',
- 'tertiary-font-color': '@transparent-lightest',
- 'link-font-color': '@primary-action-color',
- 'info-font-color': '@secondary-font-color',
- 'custom-scrollbar-color': '@transparent-darker',
- 'status-online': '@success-color',
- 'status-away': '@pending-color',
- 'status-busy': '@error-color',
- 'status-offline': '@transparent-darker',
-};
-
-// Bulk-add settings for color scheme
-Object.keys(majorColors).forEach((key) => {
- const value = majorColors[key];
- theme.addPublicColor(key, value, 'Old Colors');
-});
-
-Object.keys(minorColors).forEach((key) => {
- const value = minorColors[key];
- theme.addPublicColor(key, value, 'Old Colors (minor)', 'expression');
-});
-
-theme.addPublicFont('body-font-family', '-apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, Cantarell, \'Helvetica Neue\', \'Apple Color Emoji\', \'Segoe UI Emoji\', \'Segoe UI Symbol\', \'Meiryo UI\', Arial, sans-serif');
+const variablesContent = Assets.getText('client/imports/general/variables.css');
+
+const regionRegex = /\/\*\s*#region\s+([^ ]*?)\s+(.*?)\s*\*\/((.|\s)*?)\/\*\s*#endregion\s*\*\//igm;
+
+for (let matches = regionRegex.exec(variablesContent); matches; matches = regionRegex.exec(variablesContent)) {
+ const [, type, section, content] = matches;
+ [...content.match(/--(.*?):\s*(.*?);/igm)].forEach((entry) => {
+ const matches = /--(.*?):\s*(.*?);/im.exec(entry);
+ const [, name, value] = matches;
+
+ if (type === 'fonts') {
+ theme.addVariable('font', name, value, 'Fonts', true);
+ return;
+ }
+
+ if (type === 'colors') {
+ if (/var/.test(value)) {
+ const [, variableName] = value.match(/var\(--(.*?)\)/i);
+ theme.addVariable('color', name, variableName, section, true, 'expression', ['color', 'expression']);
+ return;
+ }
+
+ theme.addVariable('color', name, value, section, true, 'color', ['color', 'expression']);
+ return;
+ }
+
+ if (type === 'less-colors') {
+ if (/var/.test(value)) {
+ const [, variableName] = value.match(/var\(--(.*?)\)/i);
+ theme.addVariable('color', name, `@${ variableName }`, section, true, 'expression', ['color', 'expression']);
+ return;
+ }
+
+ theme.addVariable('color', name, value, section, true, 'color', ['color', 'expression']);
+ }
+ });
+}
settings.add('theme-custom-css', '', {
group: 'Layout',
diff --git a/app/threads/client/flextab/threads.js b/app/threads/client/flextab/threads.js
index a4fa8d9faee..032dde9b302 100644
--- a/app/threads/client/flextab/threads.js
+++ b/app/threads/client/flextab/threads.js
@@ -27,7 +27,7 @@ Template.threads.events({
return false;
},
'scroll .js-scroll-threads': _.throttle(({ currentTarget: e }, { incLimit }) => {
- if (e.offsetHeight + e.scrollTop <= e.scrollHeight - 50) {
+ if (e.offsetHeight + e.scrollTop >= e.scrollHeight - 50) {
incLimit && incLimit();
}
}, 500),
diff --git a/app/threads/client/threads.css b/app/threads/client/threads.css
index 5d7bf76c4c8..fe8ec38b40c 100644
--- a/app/threads/client/threads.css
+++ b/app/threads/client/threads.css
@@ -63,7 +63,7 @@
left: 40px;
width: 20px;
- height: 20px;
+ height: 16px;
color: var(--rc-color-alert-message-primary);
}
@@ -82,7 +82,7 @@
display: flex;
- margin: calc((var(--default-padding) /2) - 6px) 0 7px 0;
+ margin: calc((var(--default-padding) /2) - 2px) 0 2px 0;
align-items: center;
}
diff --git a/app/threads/server/hooks/aftersavemessage.js b/app/threads/server/hooks/aftersavemessage.js
index a9348619cae..8de27cafba6 100644
--- a/app/threads/server/hooks/aftersavemessage.js
+++ b/app/threads/server/hooks/aftersavemessage.js
@@ -38,12 +38,12 @@ const notification = (message, room, replies) => {
const processThreads = (message, room) => {
if (!message.tmid) {
- return;
+ return message;
}
const parentMessage = Messages.findOneById(message.tmid);
if (!parentMessage) {
- return;
+ return message;
}
const replies = [
@@ -53,6 +53,8 @@ const processThreads = (message, room) => {
notifyUsersOnReply(message, replies, room);
metaData(message, parentMessage);
notification(message, room, replies);
+
+ return message;
};
Meteor.startup(function() {
diff --git a/app/tokenpass/client/tokenpassChannelSettings.html b/app/tokenpass/client/tokenpassChannelSettings.html
index 4f9ab15e9f3..f0e5490ab43 100644
--- a/app/tokenpass/client/tokenpassChannelSettings.html
+++ b/app/tokenpass/client/tokenpassChannelSettings.html
@@ -1,7 +1,7 @@
diff --git a/app/ui-account/client/accountPreferences.html b/app/ui-account/client/accountPreferences.html
index ade63fece09..0da55e54fdf 100644
--- a/app/ui-account/client/accountPreferences.html
+++ b/app/ui-account/client/accountPreferences.html
@@ -92,16 +92,6 @@
{{/if}}
-
@@ -43,11 +43,11 @@
@@ -57,7 +57,7 @@
{{/with}}
@@ -100,7 +100,7 @@
{{#if username.ready}}
- {{_ "Use_this_username"}}
+ {{_ "Use_this_username"}}
{{/if}}
diff --git a/app/ui-admin/client/components/NotAuthorizedPage.js b/app/ui-admin/client/components/NotAuthorizedPage.js
deleted file mode 100644
index e7606e0fbdf..00000000000
--- a/app/ui-admin/client/components/NotAuthorizedPage.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Box } from '@rocket.chat/fuselage';
-import React from 'react';
-
-import { useTranslation } from '../../../../client/contexts/TranslationContext';
-import { Page } from '../../../../client/components/basic/Page';
-
-function NotAuthorizedPage() {
- const t = useTranslation();
-
- return
-
- {t('You_are_not_authorized_to_view_this_page')}
-
- ;
-}
-
-export default NotAuthorizedPage;
diff --git a/app/ui-admin/client/components/info/BuildEnvironmentSection.js b/app/ui-admin/client/components/info/BuildEnvironmentSection.js
deleted file mode 100644
index 25e0a702284..00000000000
--- a/app/ui-admin/client/components/info/BuildEnvironmentSection.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Subtitle } from '@rocket.chat/fuselage';
-import React from 'react';
-
-import { useTranslation } from '../../../../../client/contexts/TranslationContext';
-import { DescriptionList } from './DescriptionList';
-import { formatDate } from './formatters';
-
-export function BuildEnvironmentSection({ info }) {
- const t = useTranslation();
- const build = info && (info.compile || info.build);
-
- return <>
- {t('Build_Environment')}
-
- {build.platform}
- {build.arch}
- {build.osRelease}
- {build.nodeVersion}
- {formatDate(build.date)}
-
- >;
-}
diff --git a/app/ui-admin/client/components/info/CommitSection.js b/app/ui-admin/client/components/info/CommitSection.js
deleted file mode 100644
index 90ea501c17f..00000000000
--- a/app/ui-admin/client/components/info/CommitSection.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Subtitle } from '@rocket.chat/fuselage';
-import React from 'react';
-
-import { useTranslation } from '../../../../../client/contexts/TranslationContext';
-import { DescriptionList } from './DescriptionList';
-
-export function CommitSection({ info }) {
- const t = useTranslation();
- const { commit = {} } = info;
-
- return <>
- {t('Commit')}
-
- {commit.hash}
- {commit.date}
- {commit.branch}
- {commit.tag}
- {commit.author}
- {commit.subject}
-
- >;
-}
diff --git a/app/ui-admin/client/components/info/DescriptionList.js b/app/ui-admin/client/components/info/DescriptionList.js
deleted file mode 100644
index f8f9113af71..00000000000
--- a/app/ui-admin/client/components/info/DescriptionList.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-
-export const DescriptionList = ({ children, ...props }) =>
- ;
-
-const Entry = ({ children, label, ...props }) =>
-
- {label}
- {children}
- ;
-
-DescriptionList.Entry = Entry;
diff --git a/app/ui-admin/client/components/info/RocketChatSection.js b/app/ui-admin/client/components/info/RocketChatSection.js
deleted file mode 100644
index bf618db5997..00000000000
--- a/app/ui-admin/client/components/info/RocketChatSection.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Skeleton, Subtitle } from '@rocket.chat/fuselage';
-import React from 'react';
-
-import { useTranslation } from '../../../../../client/contexts/TranslationContext';
-import { formatDate, formatHumanReadableTime } from './formatters';
-import { DescriptionList } from './DescriptionList';
-
-export function RocketChatSection({ info, statistics, isLoading }) {
- const s = (fn) => (isLoading ? : fn());
- const t = useTranslation();
-
- const appsEngineVersion = info && info.marketplaceApiVersion;
-
- return <>
- {t('Rocket.Chat')}
-
- {s(() => statistics.version)}
- {appsEngineVersion && {appsEngineVersion} }
- {s(() => statistics.migration.version)}
- {s(() => formatDate(statistics.migration.lockedAt))}
- {s(() => formatDate(statistics.installedAt))}
- {s(() => formatHumanReadableTime(statistics.process.uptime, t))}
- {s(() => statistics.uniqueId)}
- {s(() => statistics.process.pid)}
- {s(() => statistics.instanceCount)}
- {s(() => (statistics.oplogEnabled ? t('Enabled') : t('Disabled')))}
-
- >;
-}
diff --git a/app/ui-admin/client/components/info/RuntimeEnvironmentSection.js b/app/ui-admin/client/components/info/RuntimeEnvironmentSection.js
deleted file mode 100644
index 11d9b53eba3..00000000000
--- a/app/ui-admin/client/components/info/RuntimeEnvironmentSection.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Skeleton, Subtitle } from '@rocket.chat/fuselage';
-import React from 'react';
-
-import { useTranslation } from '../../../../../client/contexts/TranslationContext';
-import { useFormatMemorySize } from '../../../../ui/client/views/app/components/hooks';
-import { DescriptionList } from './DescriptionList';
-import { formatHumanReadableTime, formatCPULoad } from './formatters';
-
-export function RuntimeEnvironmentSection({ statistics, isLoading }) {
- const s = (fn) => (isLoading ? : fn());
- const t = useTranslation();
- const formatMemorySize = useFormatMemorySize();
-
- return <>
- {t('Runtime_Environment')}
-
- {s(() => statistics.os.type)}
- {s(() => statistics.os.platform)}
- {s(() => statistics.os.arch)}
- {s(() => statistics.os.release)}
- {s(() => statistics.process.nodeVersion)}
- {s(() => statistics.mongoVersion)}
- {s(() => statistics.mongoStorageEngine)}
- {s(() => formatHumanReadableTime(statistics.os.uptime, t))}
- {s(() => formatCPULoad(statistics.os.loadavg))}
- {s(() => formatMemorySize(statistics.os.totalmem))}
- {s(() => formatMemorySize(statistics.os.freemem))}
- {s(() => statistics.os.cpus.length)}
-
- >;
-}
diff --git a/app/ui-admin/client/components/info/UsageSection.js b/app/ui-admin/client/components/info/UsageSection.js
deleted file mode 100644
index b79c83b9f2b..00000000000
--- a/app/ui-admin/client/components/info/UsageSection.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Subtitle, Skeleton } from '@rocket.chat/fuselage';
-import React from 'react';
-
-import { useTranslation } from '../../../../../client/contexts/TranslationContext';
-import { useFormatMemorySize } from '../../../../ui/client/views/app/components/hooks';
-import { DescriptionList } from './DescriptionList';
-
-export function UsageSection({ statistics, isLoading }) {
- const s = (fn) => (isLoading ? : fn());
- const formatMemorySize = useFormatMemorySize();
- const t = useTranslation();
-
- return <>
- {t('Usage')}
-
- {s(() => statistics.totalUsers)}
- {s(() => statistics.activeUsers)}
- {s(() => statistics.activeGuests)}
- {s(() => statistics.appUsers)}
- {s(() => statistics.nonActiveUsers)}
- {s(() => statistics.totalConnectedUsers)}
- {s(() => statistics.onlineUsers)}
- {s(() => statistics.awayUsers)}
- {s(() => statistics.offlineUsers)}
- {s(() => statistics.totalRooms)}
- {s(() => statistics.totalChannels)}
- {s(() => statistics.totalPrivateGroups)}
- {s(() => statistics.totalDirect)}
- {s(() => statistics.totalLivechat)}
- {s(() => statistics.totalDiscussions)}
- {s(() => statistics.totalThreads)}
- {s(() => statistics.totalMessages)}
- {s(() => statistics.totalChannelMessages)}
- {s(() => statistics.totalPrivateGroupMessages)}
- {s(() => statistics.totalDirectMessages)}
- {s(() => statistics.totalLivechatMessages)}
- {s(() => statistics.uploadsTotal)}
- {s(() => formatMemorySize(statistics.uploadsTotalSize))}
- {statistics && statistics.apps && <>
- {statistics.apps.totalInstalled}
- {statistics.apps.totalActive}
- >}
- {s(() => statistics.integrations.totalIntegrations)}
- {s(() => statistics.integrations.totalIncoming)}
- {s(() => statistics.integrations.totalIncomingActive)}
- {s(() => statistics.integrations.totalOutgoing)}
- {s(() => statistics.integrations.totalOutgoingActive)}
- {s(() => statistics.integrations.totalWithScriptEnabled)}
-
- >;
-}
diff --git a/app/ui-admin/client/components/info/formatters.js b/app/ui-admin/client/components/info/formatters.js
deleted file mode 100644
index ed1b906e453..00000000000
--- a/app/ui-admin/client/components/info/formatters.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import moment from 'moment';
-import s from 'underscore.string';
-
-export const formatNumber = (number) => s.numberFormat(number, 2);
-
-export const formatDate = (date) => {
- if (!date) {
- return null;
- }
-
- return moment(date).format('LLL');
-};
-
-export const formatHumanReadableTime = (time, t) => {
- const days = Math.floor(time / 86400);
- const hours = Math.floor((time % 86400) / 3600);
- const minutes = Math.floor(((time % 86400) % 3600) / 60);
- const seconds = Math.floor(((time % 86400) % 3600) % 60);
- let out = '';
- if (days > 0) {
- out += `${ days } ${ t('days') }, `;
- }
- if (hours > 0) {
- out += `${ hours } ${ t('hours') }, `;
- }
- if (minutes > 0) {
- out += `${ minutes } ${ t('minutes') }, `;
- }
- if (seconds > 0) {
- out += `${ seconds } ${ t('seconds') }`;
- }
- return out;
-};
-
-export const formatCPULoad = (load) => {
- if (!load) {
- return null;
- }
-
- const [oneMinute, fiveMinutes, fifteenMinutes] = load;
-
- return `${ formatNumber(oneMinute) }, ${ formatNumber(fiveMinutes) }, ${ formatNumber(fifteenMinutes) }`;
-};
diff --git a/app/ui-admin/client/hooks/useAdminSideNav.js b/app/ui-admin/client/hooks/useAdminSideNav.js
deleted file mode 100644
index 25d3f293dc7..00000000000
--- a/app/ui-admin/client/hooks/useAdminSideNav.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { useEffect } from 'react';
-
-import { SideNav } from '../../../ui-utils/client/lib/SideNav';
-
-export const useAdminSideNav = () => {
- useEffect(() => {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
- }, []);
-};
diff --git a/app/ui-admin/client/rooms/adminRoomInfo.html b/app/ui-admin/client/rooms/adminRoomInfo.html
deleted file mode 100644
index 6478b8dd118..00000000000
--- a/app/ui-admin/client/rooms/adminRoomInfo.html
+++ /dev/null
@@ -1,102 +0,0 @@
-
- {{#with selectedRoom}}
-
-
-
-
{{_ "Room_Info"}}
-
-
- {{#if canDeleteRoom}}
-
- {{_ "Delete"}}
-
- {{/if}}
-
-
- {{/with}}
-
diff --git a/app/ui-admin/client/rooms/adminRoomInfo.js b/app/ui-admin/client/rooms/adminRoomInfo.js
deleted file mode 100644
index d32685367e9..00000000000
--- a/app/ui-admin/client/rooms/adminRoomInfo.js
+++ /dev/null
@@ -1,304 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Session } from 'meteor/session';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import toastr from 'toastr';
-import s from 'underscore.string';
-
-import { t, handleError } from '../../../utils';
-import { call, modal } from '../../../ui-utils';
-import { hasAllPermission, hasAtLeastOnePermission } from '../../../authorization';
-import { ChannelSettings } from '../../../channel-settings';
-import { settings } from '../../../settings';
-import { callbacks } from '../../../callbacks';
-
-Template.adminRoomInfo.helpers({
- selectedRoom() {
- return Session.get('adminRoomsSelected');
- },
- canEdit() {
- return hasAllPermission('edit-room', this.rid);
- },
- editing(field) {
- return Template.instance().editing.get() === field;
- },
- notDirect() {
- const room = Template.instance().room.get();
- return room && room.t !== 'd';
- },
- roomType() {
- const room = Template.instance().room.get();
- return room && room.t;
- },
- channelSettings() {
- return ChannelSettings.getOptions(undefined, 'admin-room');
- },
- roomTypeDescription() {
- const room = Template.instance().room.get();
- const roomType = room && room.t;
- if (roomType === 'c') {
- return t('Channel');
- } if (roomType === 'p') {
- return t('Private_Group');
- }
- },
- roomName() {
- const room = Template.instance().room.get();
- return room && room.name;
- },
- roomOwner() {
- const roomOwner = Template.instance().roomOwner.get();
- return roomOwner && (roomOwner.name || roomOwner.username);
- },
- roomTopic() {
- const room = Template.instance().room.get();
- return room && room.topic;
- },
- archivationState() {
- const room = Template.instance().room.get();
- return room && room.archived;
- },
- archivationStateDescription() {
- const room = Template.instance().room.get();
- const archivationState = room && room.archived;
- if (archivationState === true) {
- return t('Room_archivation_state_true');
- }
- return t('Room_archivation_state_false');
- },
- canDeleteRoom() {
- const room = Template.instance().room.get();
- const roomType = room && room.t;
- return (roomType != null) && hasAtLeastOnePermission(`delete-${ roomType }`);
- },
- readOnly() {
- const room = Template.instance().room.get();
- return room && room.ro;
- },
- readOnlyDescription() {
- const room = Template.instance().room.get();
- const readOnly = room && room.ro;
-
- if (readOnly === true) {
- return t('True');
- }
- return t('False');
- },
-});
-
-Template.adminRoomInfo.events({
- 'click .delete'(event, instance) {
- modal.open({
- title: t('Are_you_sure'),
- text: t('Delete_Room_Warning'),
- type: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#DD6B55',
- confirmButtonText: t('Yes_delete_it'),
- cancelButtonText: t('Cancel'),
- closeOnConfirm: false,
- html: false,
- }, () => {
- Meteor.call('eraseRoom', this.rid, function(error) {
- if (error) {
- handleError(error);
- } else {
- modal.open({
- title: t('Deleted'),
- text: t('Room_has_been_deleted'),
- type: 'success',
- timer: 2000,
- showConfirmButton: false,
- });
- instance.onSuccess();
- instance.data.tabBar.close();
- }
- });
- });
- },
- 'keydown input[type=text]'(e, t) {
- if (e.keyCode === 13) {
- e.preventDefault();
- t.saveSetting(this.rid);
- }
- },
- 'click [data-edit]'(e, t) {
- e.preventDefault();
- t.editing.set($(e.currentTarget).data('edit'));
- return setTimeout(function() {
- t.$('input.editing').focus().select();
- }, 100);
- },
- 'click .cancel'(e, t) {
- e.preventDefault();
- t.editing.set();
- },
- 'click .save'(e, t) {
- e.preventDefault();
- t.saveSetting(this.rid);
- },
-});
-
-Template.adminRoomInfo.onCreated(function() {
- const instance = this;
- const currentData = Template.currentData();
- this.editing = new ReactiveVar();
- this.room = new ReactiveVar();
- this.roomOwner = new ReactiveVar();
- this.onSuccess = Template.currentData().onSuccess;
-
- this.autorun(() => {
- const { room } = Template.currentData();
- this.room.set(room);
- });
-
- this.validateRoomType = () => {
- const type = this.$('input[name=roomType]:checked').val();
- if (type !== 'c' && type !== 'p') {
- toastr.error(t('error-invalid-room-type', { type }));
- }
- return true;
- };
- this.validateRoomName = (rid) => {
- const { room } = currentData;
- let nameValidation;
- if (!hasAllPermission('edit-room', rid) || (room.t !== 'c' && room.t !== 'p')) {
- toastr.error(t('error-not-allowed'));
- return false;
- }
- name = $('input[name=roomName]').val();
- try {
- nameValidation = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`);
- } catch (_error) {
- nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
- }
- if (!nameValidation.test(name)) {
- toastr.error(t('error-invalid-room-name', {
- room_name: s.escapeHTML(name),
- }));
- return false;
- }
- return true;
- };
- this.validateRoomTopic = () => true;
- this.saveSetting = (rid) => {
- switch (this.editing.get()) {
- case 'roomName':
- if (this.validateRoomName(rid)) {
- callbacks.run('roomNameChanged', currentData.room);
- Meteor.call('saveRoomSettings', rid, 'roomName', this.$('input[name=roomName]').val(), function(err) {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_name_changed_successfully'));
- instance.onSuccess();
- instance.data.tabBar.close();
- });
- }
- break;
- case 'roomTopic':
- if (this.validateRoomTopic(rid)) {
- Meteor.call('saveRoomSettings', rid, 'roomTopic', this.$('input[name=roomTopic]').val(), function(err) {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_topic_changed_successfully'));
- callbacks.run('roomTopicChanged', currentData.room);
- instance.onSuccess();
- instance.data.tabBar.close();
- });
- }
- break;
- case 'roomAnnouncement':
- if (this.validateRoomTopic(rid)) {
- Meteor.call('saveRoomSettings', rid, 'roomAnnouncement', this.$('input[name=roomAnnouncement]').val(), function(err) {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_announcement_changed_successfully'));
- callbacks.run('roomAnnouncementChanged', currentData.room);
- instance.onSuccess();
- instance.data.tabBar.close();
- });
- }
- break;
- case 'roomType':
- const val = this.$('input[name=roomType]:checked').val();
- if (this.validateRoomType(rid)) {
- callbacks.run('roomTypeChanged', currentData.room);
- const saveRoomSettings = function() {
- Meteor.call('saveRoomSettings', rid, 'roomType', val, function(err) {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_type_changed_successfully'));
- instance.onSuccess();
- instance.data.tabBar.close();
- });
- };
- if (!currentData.room.default) {
- return saveRoomSettings();
- }
- modal.open({
- title: t('Room_default_change_to_private_will_be_default_no_more'),
- type: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#DD6B55',
- confirmButtonText: t('Yes'),
- cancelButtonText: t('Cancel'),
- closeOnConfirm: true,
- html: false,
- }, function(confirmed) {
- return !confirmed || saveRoomSettings();
- });
- }
- break;
- case 'archivationState':
- const { room } = currentData;
- if (this.$('input[name=archivationState]:checked').val() === 'true') {
- if (room && room.archived !== true) {
- Meteor.call('archiveRoom', rid, function(err) {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_archived'));
- callbacks.run('archiveRoom', currentData.room);
- instance.onSuccess();
- instance.data.tabBar.close();
- });
- }
- } else if ((room && room.archived) === true) {
- Meteor.call('unarchiveRoom', rid, function(err) {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_unarchived'));
- callbacks.run('unarchiveRoom', currentData.room);
- instance.onSuccess();
- instance.data.tabBar.close();
- });
- }
- break;
- case 'readOnly':
- Meteor.call('saveRoomSettings', rid, 'readOnly', this.$('input[name=readOnly]:checked').val() === 'true', function(err) {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Read_only_changed_successfully'));
- instance.onSuccess();
- instance.data.tabBar.close();
- });
- }
- this.editing.set();
- };
-
- this.autorun(async () => {
- this.roomOwner.set(null);
- for (const { roles, u } of await call('getRoomRoles', Session.get('adminRoomsSelected').rid)) {
- if (roles.includes('owner')) {
- this.roomOwner.set(u);
- }
- }
- });
-});
diff --git a/app/ui-admin/client/rooms/adminRooms.html b/app/ui-admin/client/rooms/adminRooms.html
deleted file mode 100644
index 9dc9595917d..00000000000
--- a/app/ui-admin/client/rooms/adminRooms.html
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
- {{> header sectionName="Rooms" fixedHeight="true" fullpage="true"}}
-
- {{#unless hasPermission 'view-room-administration'}}
-
{{_ "You_are_not_authorized_to_view_this_page"}}
- {{else}}
-
-
- {{{_ "Showing_results" roomCount}}}
-
- {{#table fixed='true' onItemClick=onTableItemClick onScroll=onTableScroll onResize=onTableResize}}
-
-
- {{_ "Name"}}
- {{_ "Type"}}
- {{_ "Users"}}
- {{_ "Msgs"}}
- {{_ "Default"}}
- {{_ "Featured"}}
-
-
-
- {{#each rooms}}
-
-
-
{{> avatar url=url roomIcon="true"}}
-
-
- {{>icon icon=getIcon block="rc-table-icon"}} {{roomName}}
-
-
-
- {{type}}
- {{usersCount}}
- {{msgs}}
- {{default}}
- {{#if featured}}True{{else}}False{{/if}}
-
- {{else}} {{# with searchText}}
-
- {{_ "No_results_found_for"}} {{.}}
-
- {{/with}}
- {{/each}}
- {{#if isLoading}}
-
- {{> loading}}
-
- {{/if}}
-
- {{/table}}
- {{/unless}}
-
-
- {{#with flexData}}
- {{> flexTabBar}}
- {{/with}}
-
-
diff --git a/app/ui-admin/client/rooms/adminRooms.js b/app/ui-admin/client/rooms/adminRooms.js
deleted file mode 100644
index 9bf642ab72e..00000000000
--- a/app/ui-admin/client/rooms/adminRooms.js
+++ /dev/null
@@ -1,210 +0,0 @@
-import { Tracker } from 'meteor/tracker';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-import { Session } from 'meteor/session';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import _ from 'underscore';
-
-import { SideNav, RocketChatTabBar, TabBar } from '../../../ui-utils';
-import { t, roomTypes } from '../../../utils';
-import { hasAllPermission } from '../../../authorization';
-import { ChannelSettings } from '../../../channel-settings';
-import { getAvatarURL } from '../../../utils/lib/getAvatarURL';
-import { APIClient } from '../../../utils/client';
-
-const LIST_SIZE = 50;
-const DEBOUNCE_TIME_TO_SEARCH_IN_MS = 500;
-
-Template.adminRooms.helpers({
- url() {
- return this.t === 'd' ? getAvatarURL({ username: `@${ this.usernames[0] }` }) : roomTypes.getConfig(this.t).getAvatarPath(this);
- },
- getIcon() {
- return roomTypes.getIcon(this);
- },
- roomName() {
- return this.t === 'd' ? this.usernames.join(' x ') : roomTypes.getRoomName(this.t, this);
- },
- searchText() {
- const instance = Template.instance();
- return instance.filter && instance.filter.get();
- },
- rooms() {
- return Template.instance().rooms.get();
- },
- isLoading() {
- return Template.instance().isLoading.get();
- },
- roomCount() {
- return Template.instance().rooms.get().length;
- },
- type() {
- return TAPi18n.__(roomTypes.getConfig(this.t).label);
- },
- 'default'() {
- if (this.default) {
- return t('True');
- }
- return t('False');
- },
- flexData() {
- return {
- tabBar: Template.instance().tabBar,
- data: Template.instance().tabBarData.get(),
- };
- },
- onTableScroll() {
- const instance = Template.instance();
- return function(currentTarget) {
- if (currentTarget.offsetHeight + currentTarget.scrollTop < currentTarget.scrollHeight - 100) {
- return;
- }
- const rooms = instance.rooms.get();
- if (instance.total.get() > rooms.length) {
- instance.offset.set(instance.offset.get() + LIST_SIZE);
- }
- };
- },
- onTableItemClick() {
- const instance = Template.instance();
- return function(item) {
- Session.set('adminRoomsSelected', {
- rid: item._id,
- });
- instance.tabBarData.set({
- room: instance.rooms.get().find((room) => room._id === item._id),
- onSuccess: instance.onSuccessCallback,
- });
- instance.tabBar.open('admin-room');
- };
- },
-
-});
-
-Template.adminRooms.onCreated(function() {
- const instance = this;
- this.offset = new ReactiveVar(0);
- this.total = new ReactiveVar(0);
- this.filter = new ReactiveVar('');
- this.types = new ReactiveVar([]);
- this.rooms = new ReactiveVar([]);
- this.ready = new ReactiveVar(true);
- this.isLoading = new ReactiveVar(false);
- this.tabBar = new RocketChatTabBar();
- this.tabBarData = new ReactiveVar();
- this.tabBar.showGroup(FlowRouter.current().route.name);
- TabBar.addButton({
- groups: ['admin-rooms'],
- id: 'admin-room',
- i18nTitle: 'Room_Info',
- icon: 'info-circled',
- template: 'adminRoomInfo',
- order: 7,
- });
-
- ChannelSettings.addOption({
- group: ['admin-room'],
- id: 'make-default',
- template: 'channelSettingsDefault',
- data() {
- return {
- room: instance.tabBarData.get().room,
- onSuccess: instance.tabBarData.get().onSuccess,
- };
- },
- validation() {
- return hasAllPermission('view-room-administration');
- },
- });
-
- ChannelSettings.addOption({
- group: ['admin-room'],
- id: 'make-featured',
- template: 'channelSettingsFeatured',
- data() {
- return {
- room: instance.tabBarData.get().room,
- onSuccess: instance.tabBarData.get().onSuccess,
- };
- },
- validation() {
- return hasAllPermission('view-room-administration');
- },
- });
-
- this.onSuccessCallback = () => {
- instance.offset.set(0);
- return instance.loadRooms(instance.types.get(), instance.filter.get(), instance.offset.get());
- };
-
- this.tabBarData.set({
- onSuccess: instance.onSuccessCallback,
- });
-
- const mountTypesQueryParameter = (types) => types.reduce((acc, item) => {
- acc += `types[]=${ item }&`;
- return acc;
- }, '');
-
- this.loadRooms = _.debounce(async (types = [], filter, offset) => {
- this.isLoading.set(true);
- let url = `rooms.adminRooms?count=${ LIST_SIZE }&offset=${ offset }&${ mountTypesQueryParameter(types) }`;
- if (filter) {
- url += `filter=${ encodeURIComponent(filter) }`;
- }
- const { rooms, total } = await APIClient.v1.get(url);
- this.total.set(total);
- if (offset === 0) {
- this.rooms.set(rooms);
- } else {
- this.rooms.set(this.rooms.get().concat(rooms));
- }
- this.isLoading.set(false);
- }, DEBOUNCE_TIME_TO_SEARCH_IN_MS);
-
- const allowedTypes = ['c', 'd', 'p'];
- this.autorun(() => {
- instance.filter.get();
- instance.types.get();
- instance.offset.set(0);
- });
- this.autorun(() => {
- const filter = instance.filter.get();
- const offset = instance.offset.get();
- let types = instance.types.get();
- if (types.length === 0) {
- types = allowedTypes;
- }
- return this.loadRooms(types, filter, offset);
- });
- this.getSearchTypes = function() {
- return _.map($('[name=room-type]:checked'), function(input) {
- return $(input).val();
- });
- };
-});
-
-Template.adminRooms.onRendered(function() {
- Tracker.afterFlush(function() {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
- });
-});
-
-Template.adminRooms.events({
- 'keydown #rooms-filter'(e) {
- if (e.which === 13) {
- e.stopPropagation();
- e.preventDefault();
- }
- },
- 'keyup #rooms-filter'(e, t) {
- e.stopPropagation();
- e.preventDefault();
- t.filter.set(e.currentTarget.value);
- },
- 'change [name=room-type]'(e, t) {
- t.types.set(t.getSearchTypes());
- },
-});
diff --git a/app/ui-admin/client/rooms/channelSettingsDefault.html b/app/ui-admin/client/rooms/channelSettingsDefault.html
deleted file mode 100644
index 8360662079b..00000000000
--- a/app/ui-admin/client/rooms/channelSettingsDefault.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
- {{#if canMakeDefault}}
-
- {{_ "Default"}}
-
- {{#if editing 'default'}}
- {{_ "True"}}
- {{_ "False"}}
- {{#if roomDefault}}
- {{_ "Set_as_favorite"}}
- {{/if}}
- {{_ "Cancel"}}
- {{_ "Save"}}
- {{else}}
- {{defaultDescription}}
- {{/if}}
-
-
- {{/if}}
-
diff --git a/app/ui-admin/client/rooms/channelSettingsDefault.js b/app/ui-admin/client/rooms/channelSettingsDefault.js
deleted file mode 100644
index 2b57af949eb..00000000000
--- a/app/ui-admin/client/rooms/channelSettingsDefault.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import toastr from 'toastr';
-
-import { t, handleError } from '../../../utils';
-
-Template.channelSettingsDefault.helpers({
- canMakeDefault() {
- const room = Template.instance().room.get();
- return room && room.t === 'c';
- },
- editing(field) {
- return Template.instance().editing.get() === field;
- },
- roomDefault() {
- return Template.instance().isDefault.get();
- },
- isFavorite() {
- return Template.instance().isFavorite.get();
- },
- defaultDescription() {
- const { room, isDefault, isFavorite } = { room: Template.instance().room.get(), isDefault: Template.instance().isDefault.get(), isFavorite: Template.instance().isFavorite.get() };
- let description = t('False');
-
- if (room && isDefault) {
- description = t('True');
-
- if (isFavorite) {
- description += `, ${ t('Set_as_favorite') }`;
- }
- return description;
- }
- return description;
- },
-});
-
-Template.channelSettingsDefault.events({
- 'change input[name=default]'(e, t) {
- t.isDefault.set(e.currentTarget.value === 'true');
- },
- 'change input[name=favorite]'(e, t) {
- t.isFavorite.set(e.currentTarget.checked);
- },
- 'click [data-edit]'(e, t) {
- e.preventDefault();
- t.editing.set($(e.currentTarget).data('edit'));
- setTimeout(() => {
- t.$('input.editing').focus().select();
- }, 100);
- },
- 'click .cancel'(e, t) {
- e.preventDefault();
- t.editing.set();
- },
- 'click .save'(e, t) {
- e.preventDefault();
-
- Meteor.call('saveRoomSettings', t.room.get()._id, { default: t.isDefault.get(), favorite: { defaultValue: t.isDefault.get(), favorite: t.isFavorite.get() } }, (err/* , result*/) => {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_type_changed_successfully'));
- });
- t.onSuccess();
- t.editing.set();
- },
-});
-
-Template.channelSettingsDefault.onCreated(function() {
- this.editing = new ReactiveVar();
- this.isDefault = new ReactiveVar();
- this.isFavorite = new ReactiveVar();
- this.room = new ReactiveVar();
- this.onSuccess = Template.currentData().onSuccess;
-
- this.autorun(() => {
- const { room } = Template.currentData();
- this.isDefault.set(room && room.default);
- this.isFavorite.set(room && room.favorite && room.default);
- this.room.set(room);
- });
-});
diff --git a/app/ui-admin/client/rooms/channelSettingsFeatured.html b/app/ui-admin/client/rooms/channelSettingsFeatured.html
deleted file mode 100644
index a6b00328673..00000000000
--- a/app/ui-admin/client/rooms/channelSettingsFeatured.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- {{_ "Featured"}}
-
- {{#if editing 'featured'}}
- {{_ "True"}}
- {{_ "False"}}
- {{_ "Cancel"}}
- {{_ "Save"}}
- {{else}}
- {{featuredDescription}}
- {{/if}}
-
-
-
diff --git a/app/ui-admin/client/rooms/channelSettingsFeatured.js b/app/ui-admin/client/rooms/channelSettingsFeatured.js
deleted file mode 100644
index a0ffb03881e..00000000000
--- a/app/ui-admin/client/rooms/channelSettingsFeatured.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import toastr from 'toastr';
-import './channelSettingsFeatured.html';
-
-import { t, handleError } from '../../../utils';
-
-Template.channelSettingsFeatured.helpers({
- editing(field) {
- return Template.instance().editing.get() === field;
- },
- roomFeatured() {
- const room = Template.instance().room.get();
-
- if (room) {
- return room.featured;
- }
- },
- featuredDescription() {
- const room = Template.instance().room.get();
- if (room && room.featured) {
- return t('True');
- }
- return t('False');
- },
-});
-
-Template.channelSettingsFeatured.events({
- 'click [data-edit]'(e, t) {
- e.preventDefault();
- t.editing.set($(e.currentTarget).data('edit'));
- setTimeout(() => {
- t.$('input.editing').focus().select();
- }, 100);
- },
- 'click .cancel'(e, t) {
- e.preventDefault();
- t.editing.set();
- },
- 'click .save'(e, t) {
- e.preventDefault();
-
- Meteor.call('saveRoomSettings', Template.instance().room.get()._id, 'featured', $('input[name=featured]:checked').val(), (err/* , result*/) => {
- if (err) {
- return handleError(err);
- }
- toastr.success(TAPi18n.__('Room_changed_successfully'));
- });
- t.onSuccess();
- t.editing.set();
- },
-});
-
-Template.channelSettingsFeatured.onCreated(function() {
- this.editing = new ReactiveVar();
- this.room = new ReactiveVar();
- this.onSuccess = Template.currentData().onSuccess;
-
- this.autorun(() => {
- const { room } = Template.currentData();
- this.room.set(room);
- });
-});
diff --git a/app/ui-admin/client/rooms/views.js b/app/ui-admin/client/rooms/views.js
deleted file mode 100644
index 9bfc9dcf830..00000000000
--- a/app/ui-admin/client/rooms/views.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import './adminRooms.html';
-import './adminRoomInfo.html';
-import './adminRoomInfo';
-import './channelSettingsDefault.html';
-import './channelSettingsDefault';
-import './channelSettingsFeatured';
-import './adminRooms';
diff --git a/app/ui-admin/client/routes.js b/app/ui-admin/client/routes.js
deleted file mode 100644
index b30d9cb3a40..00000000000
--- a/app/ui-admin/client/routes.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import { FlowRouter } from 'meteor/kadira:flow-router';
-import { BlazeLayout } from 'meteor/kadira:blaze-layout';
-import { Meteor } from 'meteor/meteor';
-
-import { renderRouteComponent } from '../../../client/reactAdapters';
-
-const routeGroup = FlowRouter.group({
- name: 'admin',
- prefix: '/admin',
-});
-
-export const registerAdminRoute = (path, { lazyRouteComponent, props, action, ...options } = {}) => {
- routeGroup.route(path, {
- ...options,
- action: (params, queryParams) => {
- if (action) {
- action(params, queryParams);
- return;
- }
-
- renderRouteComponent(() => import('./components/AdministrationRouter'), {
- template: 'main',
- region: 'center',
- propsFn: () => ({ lazyRouteComponent, ...options, params, queryParams, ...props }),
- });
- },
- });
-};
-
-registerAdminRoute('/', {
- triggersEnter: [(context, redirect) => {
- redirect('admin-info');
- }],
-});
-
-registerAdminRoute('/info', {
- name: 'admin-info',
- lazyRouteComponent: () => import('./components/info/InformationRoute'),
-});
-
-registerAdminRoute('/mailer', {
- name: 'admin-mailer',
- lazyRouteComponent: () => import('./components/mailer/MailerRoute'),
-});
-
-registerAdminRoute('/users', {
- name: 'admin-users',
- action: async () => {
- await import('./users/views');
- BlazeLayout.render('main', { center: 'adminUsers' });
- },
-});
-
-registerAdminRoute('/rooms', {
- name: 'admin-rooms',
- action: async () => {
- await import('./rooms/views');
- BlazeLayout.render('main', { center: 'adminRooms' });
- },
-});
-
-Meteor.startup(() => {
- registerAdminRoute('/:group+', {
- name: 'admin',
- lazyRouteComponent: () => import('./components/settings/SettingsRoute'),
- });
-});
diff --git a/app/ui-admin/client/sidebarItems.js b/app/ui-admin/client/sidebarItems.js
deleted file mode 100644
index f060c00e21d..00000000000
--- a/app/ui-admin/client/sidebarItems.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Tracker } from 'meteor/tracker';
-
-const items = new ReactiveVar([]);
-
-export const registerAdminSidebarItem = (itemOptions) => {
- Tracker.nonreactive(() => items.set([...items.get(), itemOptions]));
-};
-
-export const getSidebarItems = () => items.get().filter((option) => !option.permissionGranted || option.permissionGranted());
diff --git a/app/ui-admin/client/users/adminInviteUser.html b/app/ui-admin/client/users/adminInviteUser.html
deleted file mode 100644
index 836c7bd8861..00000000000
--- a/app/ui-admin/client/users/adminInviteUser.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
- {{#if isAllowed}}
-
-
-
-
- {{_ "Cancel"}}
- {{_ "Send"}}
-
- {{#if inviteEmails.length}}
-
-
{{_ "Send_invitation_email_success"}}
-
- {{#each inviteEmails}}
- {{.}}
- {{/each}}
-
-
- {{/if}}
-
-
- {{/if}}
-
diff --git a/app/ui-admin/client/users/adminInviteUser.js b/app/ui-admin/client/users/adminInviteUser.js
deleted file mode 100644
index d765d45e9c9..00000000000
--- a/app/ui-admin/client/users/adminInviteUser.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-import _ from 'underscore';
-import toastr from 'toastr';
-
-import { hasAtLeastOnePermission } from '../../../authorization';
-import { t, handleError } from '../../../utils';
-
-Template.adminInviteUser.helpers({
- isAllowed() {
- return hasAtLeastOnePermission('bulk-register-user');
- },
- inviteEmails() {
- return Template.instance().inviteEmails.get();
- },
-});
-
-Template.adminInviteUser.events({
- 'click .send'(e, instance) {
- const emails = $('#inviteEmails').val().split(/[\s,;]/);
- const rfcMailPattern = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
- const validEmails = _.compact(_.map(emails, function(email) {
- if (rfcMailPattern.test(email)) {
- return email;
- }
- }));
- if (validEmails.length) {
- Meteor.call('sendInvitationEmail', validEmails, function(error, result) {
- if (result) {
- instance.clearForm();
- instance.inviteEmails.set(validEmails);
- }
- if (error) {
- handleError(error);
- }
- });
- } else {
- toastr.error(t('Send_invitation_email_error'));
- }
- },
- 'click .cancel'(e, instance) {
- instance.clearForm();
- instance.inviteEmails.set([]);
- Template.currentData().tabBar.close();
- },
-});
-
-Template.adminInviteUser.onCreated(function() {
- this.inviteEmails = new ReactiveVar([]);
- this.clearForm = function() {
- $('#inviteEmails').val('');
- };
-});
diff --git a/app/ui-admin/client/users/adminUserEdit.html b/app/ui-admin/client/users/adminUserEdit.html
deleted file mode 100644
index 48a621bd34c..00000000000
--- a/app/ui-admin/client/users/adminUserEdit.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
- {{> userEdit .}}
-
diff --git a/app/ui-admin/client/users/adminUserInfo.html b/app/ui-admin/client/users/adminUserInfo.html
deleted file mode 100644
index 9b52cae393b..00000000000
--- a/app/ui-admin/client/users/adminUserInfo.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
- {{#if _id}}
- {{> userInfo .}}
- {{else}}
-
- {{_ "Please_select_an_user"}}
-
- {{/if}}
-
diff --git a/app/ui-admin/client/users/adminUsers.html b/app/ui-admin/client/users/adminUsers.html
deleted file mode 100644
index becdb5d67e3..00000000000
--- a/app/ui-admin/client/users/adminUsers.html
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
- {{> header sectionName="Users"}}
-
- {{#unless hasPermission 'view-user-administration'}}
-
{{_ "You_are_not_authorized_to_view_this_page"}}
- {{else}}
-
-
-
-
- {{{_ "Showing_results" users.length}}}
-
- {{#table fixed='true' onItemClick=onTableItemClick onScroll=onTableScroll onResize=onTableResize}}
-
-
-
- {{_ "Name"}}
-
-
- {{_ "Username"}}
-
-
- {{_ "Email"}}
-
-
- {{_ "Roles"}}
-
-
- {{_ "Status"}}
-
-
-
-
- {{#each users}}
-
-
-
-
- {{> avatar username=username}}
-
-
- {{name}}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{#if $not active}}{{_"deactivated"}}{{else}}{{status}}{{/if}}
-
-
- {{else}} {{# with searchText}}
-
- {{_ "No_results_found_for"}} {{.}}
-
- {{/with}} {{/each}} {{#unless isReady}}
-
- {{> loading}}
-
- {{/unless}}
-
- {{/table}}
- {{/unless}}
-
-
- {{#with flexData}}
- {{> flexTabBar}}
- {{/with}}
-
-
diff --git a/app/ui-admin/client/users/adminUsers.js b/app/ui-admin/client/users/adminUsers.js
deleted file mode 100644
index eddb440a1b7..00000000000
--- a/app/ui-admin/client/users/adminUsers.js
+++ /dev/null
@@ -1,183 +0,0 @@
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Tracker } from 'meteor/tracker';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-import { Template } from 'meteor/templating';
-import _ from 'underscore';
-
-import { SideNav, TabBar, RocketChatTabBar } from '../../../ui-utils';
-import { APIClient } from '../../../utils/client';
-
-const USERS_COUNT = 50;
-
-Template.adminUsers.helpers({
- searchText() {
- const instance = Template.instance();
- return instance.filter && instance.filter.get();
- },
- isReady() {
- const instance = Template.instance();
- return instance.ready && instance.ready.get();
- },
- users() {
- return Template.instance().users.get();
- },
- isLoading() {
- const instance = Template.instance();
- if (!(instance.ready && instance.ready.get())) {
- return 'btn-loading';
- }
- },
- hasMore() {
- const instance = Template.instance();
- const users = instance.users();
- if (instance.offset && instance.offset.get() && users && users.length) {
- return instance.offset.get() === users.length;
- }
- },
- emailAddress() {
- return _.map(this.emails, function(e) { return e.address; }).join(', ');
- },
- flexData() {
- return {
- tabBar: Template.instance().tabBar,
- data: Template.instance().tabBarData.get(),
- };
- },
- onTableScroll() {
- const instance = Template.instance();
- return function(currentTarget) {
- if (
- currentTarget.offsetHeight + currentTarget.scrollTop
- >= currentTarget.scrollHeight - 100
- ) {
- return instance.offset.set(instance.offset.get() + USERS_COUNT);
- }
- };
- },
- // onTableItemClick() {
- // const instance = Template.instance();
- // return function(item) {
- // Session.set('adminRoomsSelected', {
- // rid: item._id,
- // });
- // instance.tabBar.open('admin-room');
- // };
- // },
-});
-
-Template.adminUsers.onCreated(function() {
- const instance = this;
- this.offset = new ReactiveVar(0);
- this.filter = new ReactiveVar('');
- this.ready = new ReactiveVar(true);
- this.tabBar = new RocketChatTabBar();
- this.tabBar.showGroup(FlowRouter.current().route.name);
- this.tabBarData = new ReactiveVar();
- this.users = new ReactiveVar([]);
-
- TabBar.addButton({
- groups: ['admin-users'],
- id: 'admin-user-info',
- i18nTitle: 'User_Info',
- icon: 'user',
- template: 'adminUserInfo',
- order: 3,
- });
-
- TabBar.addButton({
- groups: ['admin-users'],
- id: 'invite-user',
- i18nTitle: 'Invite_Users',
- icon: 'send',
- template: 'adminInviteUser',
- order: 1,
- });
-
- TabBar.addButton({
- groups: ['admin-users'],
- id: 'add-user',
- i18nTitle: 'Add_User',
- icon: 'plus',
- template: 'adminUserEdit',
- order: 2,
- });
-
- this.loadUsers = async (filter, offset) => {
- this.ready.set(false);
-
- const query = {
- $or: [
- { 'emails.address': { $regex: filter, $options: 'i' } },
- { username: { $regex: filter, $options: 'i' } },
- { name: { $regex: filter, $options: 'i' } },
- ],
- };
- let url = `users.list?count=${ USERS_COUNT }&offset=${ offset }`;
- if (filter) {
- url += `&query=${ JSON.stringify(query) }`;
- }
- const { users } = await APIClient.v1.get(url);
- if (offset === 0) {
- this.users.set(users);
- } else {
- this.users.set(this.users.get().concat(users));
- }
- this.ready.set(true);
- };
-
- this.autorun(async () => {
- const filter = instance.filter.get();
- const offset = instance.offset.get();
-
- this.loadUsers(filter, offset);
- });
-});
-
-Template.adminUsers.onRendered(function() {
- Tracker.afterFlush(function() {
- SideNav.setFlex('adminFlex');
- SideNav.openFlex();
- });
-});
-
-const DEBOUNCE_TIME_FOR_SEARCH_USERS_IN_MS = 300;
-
-Template.adminUsers.events({
- 'keydown #users-filter'(e) {
- if (e.which === 13) {
- e.stopPropagation();
- e.preventDefault();
- }
- },
- 'keyup #users-filter': _.debounce((e, t) => {
- e.stopPropagation();
- e.preventDefault();
- t.filter.set(e.currentTarget.value);
- t.offset.set(0);
- }, DEBOUNCE_TIME_FOR_SEARCH_USERS_IN_MS),
- 'click .user-info'(e, instance) {
- e.preventDefault();
- instance.tabBarData.set({
- ...instance.users.get().find((user) => user._id === this._id),
- onChange() {
- const filter = instance.filter.get();
- const offset = instance.offset.get();
-
- instance.loadUsers(filter, offset);
- },
- });
- instance.tabBar.open('admin-user-info');
- },
- 'click .info-tabs button'(e) {
- e.preventDefault();
- $('.info-tabs button').removeClass('active');
- $(e.currentTarget).addClass('active');
- $('.user-info-content').hide();
- $($(e.currentTarget).attr('href')).show();
- },
- 'click .load-more'(e, t) {
- e.preventDefault();
- e.stopPropagation();
- t.offset.set(t.offset.get() + USERS_COUNT);
- },
-});
diff --git a/app/ui-admin/client/users/views.js b/app/ui-admin/client/users/views.js
deleted file mode 100644
index da3c6d5414b..00000000000
--- a/app/ui-admin/client/users/views.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import './adminInviteUser.html';
-import './adminUserEdit.html';
-import './adminUserInfo.html';
-import './adminUsers.html';
-import './adminInviteUser';
-import './adminUsers';
diff --git a/app/ui-cached-collection/client/models/CachedCollection.js b/app/ui-cached-collection/client/models/CachedCollection.js
index 8450ee6b879..26dece4ccc9 100644
--- a/app/ui-cached-collection/client/models/CachedCollection.js
+++ b/app/ui-cached-collection/client/models/CachedCollection.js
@@ -120,27 +120,27 @@ const log = (...args) => console.log(`CachedCollection ${ this.name } =>`, ...ar
export class CachedCollection extends EventEmitter {
constructor({
- collection,
+ collection = new Mongo.Collection(null),
name,
- methodName,
- syncMethodName,
- eventName,
+ methodName = `${ name }/get`,
+ syncMethodName = `${ name }/get`,
+ eventName = `${ name }-changed`,
eventType = 'onUser',
userRelated = true,
listenChangesForLoggedUsersOnly = false,
useSync = true,
- version = 10,
+ version = 11,
maxCacheTime = 60 * 60 * 24 * 30,
onSyncData = (/* action, record */) => {},
}) {
super();
- this.collection = collection || new Mongo.Collection(null);
+ this.collection = collection;
this.ready = new ReactiveVar(false);
this.name = name;
- this.methodName = methodName || `${ name }/get`;
- this.syncMethodName = syncMethodName || `${ name }/get`;
- this.eventName = eventName || `${ name }-changed`;
+ this.methodName = methodName;
+ this.syncMethodName = syncMethodName;
+ this.eventName = eventName;
this.eventType = eventType;
this.useSync = useSync;
this.listenChangesForLoggedUsersOnly = listenChangesForLoggedUsersOnly;
diff --git a/app/ui-flextab/client/tabs/inviteUsers.js b/app/ui-flextab/client/tabs/inviteUsers.js
index 784a5757ab2..e8e0783f068 100644
--- a/app/ui-flextab/client/tabs/inviteUsers.js
+++ b/app/ui-flextab/client/tabs/inviteUsers.js
@@ -7,7 +7,7 @@ import { Deps } from 'meteor/deps';
import toastr from 'toastr';
import { settings } from '../../../settings';
-import { t } from '../../../utils';
+import { t, handleError } from '../../../utils/client';
import { AutoComplete } from '../../../meteor-autocomplete/client';
const acEvents = {
@@ -106,7 +106,7 @@ Template.inviteUsers.events({
users,
}, function(err) {
if (err) {
- return toastr.error(err);
+ return handleError(err);
}
toastr.success(t('Users_added'));
instance.selectedUsers.set([]);
diff --git a/app/ui-flextab/client/tabs/userInfo.html b/app/ui-flextab/client/tabs/userInfo.html
index 69e6128cf61..69c85ecd407 100644
--- a/app/ui-flextab/client/tabs/userInfo.html
+++ b/app/ui-flextab/client/tabs/userInfo.html
@@ -29,7 +29,7 @@
{{> avatar username=username}}
- {{name}}
+ {{name}}
{{#if username}}@{{username}}
{{/if}}
{{# userPresence uid=uid}}
diff --git a/app/ui-login/client/reset-password/resetPassword.html b/app/ui-login/client/reset-password/resetPassword.html
index b1d6c4f3d8f..549d1fafb75 100644
--- a/app/ui-login/client/reset-password/resetPassword.html
+++ b/app/ui-login/client/reset-password/resetPassword.html
@@ -23,7 +23,7 @@
- {{_ "Reset"}}
+ {{_ "Reset"}}
diff --git a/app/ui-login/client/reset-password/resetPassword.js b/app/ui-login/client/reset-password/resetPassword.js
index e22c9b10c84..e5eaff9559f 100644
--- a/app/ui-login/client/reset-password/resetPassword.js
+++ b/app/ui-login/client/reset-password/resetPassword.js
@@ -59,6 +59,8 @@ async function setUserPassword(password) {
requirePasswordChange: false,
},
});
+ toastr.remove();
+ toastr.success(t('Password_changed_successfully'));
} catch (e) {
console.error(e);
toastr.error(t('Error'));
diff --git a/app/ui-master/.eslintrc b/app/ui-master/.eslintrc
deleted file mode 100644
index 275f73194ad..00000000000
--- a/app/ui-master/.eslintrc
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "globals": {
- "DynamicCss": false
- }
-}
\ No newline at end of file
diff --git a/app/ui-master/client/main.html b/app/ui-master/client/main.html
index fa1554c3c4b..22cfc0b1d33 100644
--- a/app/ui-master/client/main.html
+++ b/app/ui-master/client/main.html
@@ -1,4 +1,4 @@
-
+
@@ -45,7 +45,6 @@
{{> status}}
- {{> audioNotification }}
{{/if}}
{{/if}}
{{/unless}}
diff --git a/app/ui-master/client/main.js b/app/ui-master/client/main.js
index 0a7e0753ecc..e5580d61ea2 100644
--- a/app/ui-master/client/main.js
+++ b/app/ui-master/client/main.js
@@ -30,8 +30,6 @@ function customScriptsOnLogout() {
}
}
-settings.collection.find({ _id: /theme-color-rc/i }, { fields: { value: 1 } }).observe({ changed: () => { DynamicCss.run(true, settings); } });
-
callbacks.add('afterLogoutCleanUp', () => customScriptsOnLogout(), callbacks.priority.LOW, 'custom-script-on-logout');
Template.body.onRendered(function() {
diff --git a/app/ui-master/server/inject.js b/app/ui-master/server/inject.js
index 62b08f40584..5f0ac8fcaed 100644
--- a/app/ui-master/server/inject.js
+++ b/app/ui-master/server/inject.js
@@ -35,7 +35,6 @@ Meteor.startup(() => {
});
injectIntoHead('noreferrer', ' ');
- injectIntoHead('dynamic', ``);
if (process.env.DISABLE_ANIMATION || process.env.TEST_MODE === 'true') {
injectIntoHead('disable-animation', `
diff --git a/app/ui-message/client/blocks/MessageBlock.js b/app/ui-message/client/blocks/MessageBlock.js
index 3bd9a8739ff..2794b265583 100644
--- a/app/ui-message/client/blocks/MessageBlock.js
+++ b/app/ui-message/client/blocks/MessageBlock.js
@@ -2,6 +2,7 @@ import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/
import { UiKitMessage, UiKitComponent, kitContext, messageParser } from '@rocket.chat/fuselage-ui-kit';
import React, { useRef, useEffect } from 'react';
+import RawText from '../../../../client/components/basic/RawText';
import { renderMessageBody } from '../../../ui-utils/client';
import * as ActionManager from '../ActionManager';
@@ -11,7 +12,7 @@ messageParser.text = ({ text, type } = {}) => {
return text;
}
- return ;
+ return {renderMessageBody({ msg: text })} ;
};
export function MessageBlock({ mid: _mid, rid, blocks, appId }) {
diff --git a/app/ui-message/client/blocks/ModalBlock.js b/app/ui-message/client/blocks/ModalBlock.js
index cec19b7c74d..891c1b15c75 100644
--- a/app/ui-message/client/blocks/ModalBlock.js
+++ b/app/ui-message/client/blocks/ModalBlock.js
@@ -5,6 +5,7 @@ import { kitContext, UiKitComponent, UiKitModal, modalParser } from '@rocket.cha
import { uiKitText } from '@rocket.chat/ui-kit';
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
+import RawText from '../../../../client/components/basic/RawText';
import { renderMessageBody } from '../../../ui-utils/client';
import { getURL } from '../../../utils/lib/getURL';
import * as ActionManager from '../ActionManager';
@@ -15,7 +16,7 @@ modalParser.text = ({ text, type } = {}) => {
return text;
}
- return ;
+ return {renderMessageBody({ msg: text })} ;
};
const textParser = uiKitText({
diff --git a/app/ui-message/client/blocks/index.js b/app/ui-message/client/blocks/index.js
index 5e332705c1d..d081ff82163 100644
--- a/app/ui-message/client/blocks/index.js
+++ b/app/ui-message/client/blocks/index.js
@@ -1,4 +1,10 @@
+import { HTML } from 'meteor/htmljs';
+
import { createTemplateForComponent } from '../../../../client/reactAdapters';
-createTemplateForComponent('ModalBlock', () => import('./ModalBlock'));
+createTemplateForComponent('ModalBlock', () => import('./ModalBlock'), {
+ // eslint-disable-next-line new-cap
+ renderContainerView: () => HTML.DIV({ class: 'rc-multiselect', style: 'display: flex; width:100%;' }),
+});
+
createTemplateForComponent('Blocks', () => import('./MessageBlock'));
diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html
index 84b56e5dbe7..5269bcc5711 100644
--- a/app/ui-message/client/message.html
+++ b/app/ui-message/client/message.html
@@ -33,6 +33,9 @@
{{getName}}{{#if showUsername}} @{{msg.u.username}} {{/if}}
+ {{#if getStatus}}
+ 💬
+ {{/if}}
{{#each role in roleTags}}
{{role.description}}
@@ -57,6 +60,9 @@
{{/if}}
+ {{#if showStar}}
+
+ {{/if}}
{{#if msg.alert}}
{{/if}}
@@ -155,7 +161,7 @@
{{#each actionLink in actionLinks}}
-
+
{{#if actionLink.icon}}
{{/if}}
@@ -170,11 +176,9 @@
{{/unless}}
{{#if broadcast}}
- {{#with msg.u}}
-
- {{> icon icon="reply"}} {{_'Reply'}}
-
- {{/with}}
+
+ {{> icon icon="reply"}} {{_'Reply'}}
+
{{/if}}
{{#unless hideReactions}}
diff --git a/app/ui-message/client/message.js b/app/ui-message/client/message.js
index ed093e5db9c..a237f26b2ba 100644
--- a/app/ui-message/client/message.js
+++ b/app/ui-message/client/message.js
@@ -3,6 +3,7 @@ import s from 'underscore.string';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Template } from 'meteor/templating';
+import { Session } from 'meteor/session';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { timeAgo, formatDateAndTime } from '../../lib/client/lib/formatDate';
@@ -123,6 +124,10 @@ Template.message.helpers({
return msg.avatar.replace(/^@/, '');
}
},
+ getStatus() {
+ const { msg } = this;
+ return Session.get(`user_${ msg.u.username }_status_text`);
+ },
getName() {
const { msg, settings } = this;
if (msg.alias) {
@@ -377,6 +382,10 @@ Template.message.helpers({
const { msg: { threadMsg } } = this;
return threadMsg;
},
+ showStar() {
+ const { msg } = this;
+ return msg.starred && !(msg.actionContext === 'starred' || this.context === 'starred');
+ },
});
diff --git a/app/ui-message/client/messageBox/messageBoxAutogrow.js b/app/ui-message/client/messageBox/messageBoxAutogrow.js
index 06348dc729b..0e042d00d8a 100644
--- a/app/ui-message/client/messageBox/messageBoxAutogrow.js
+++ b/app/ui-message/client/messageBox/messageBoxAutogrow.js
@@ -1,8 +1,8 @@
import _ from 'underscore';
-const replaceWhitespaces = (whitespaces: string) => `${ ' '.repeat(whitespaces.length - 1) } `;
+const replaceWhitespaces = (whitespaces) => `${ ' '.repeat(whitespaces.length - 1) } `;
-export const setupAutogrow = (textarea: HTMLTextAreaElement, shadow: HTMLDivElement, callback: () => void) => {
+export const setupAutogrow = (textarea, shadow, callback) => {
const width = textarea.clientWidth;
const height = textarea.clientHeight;
const { font, lineHeight, maxHeight: maxHeightPx } = window.getComputedStyle(textarea);
@@ -34,9 +34,9 @@ export const setupAutogrow = (textarea: HTMLTextAreaElement, shadow: HTMLDivElem
return true;
}
- const shadowText = text.replace(//g, '>')
- .replace(/&/g, '&')
.replace(/\n$/, ' ')
.replace(/\n/g, ' ')
.replace(/ {2,}/g, replaceWhitespaces);
diff --git a/app/ui-message/client/popup/messagePopup.js b/app/ui-message/client/popup/messagePopup.js
index 7ac6a7d785f..f948dd6d4ff 100644
--- a/app/ui-message/client/popup/messagePopup.js
+++ b/app/ui-message/client/popup/messagePopup.js
@@ -288,12 +288,16 @@ Template.messagePopup.events({
const template = Template.instance();
template.clickingItem = true;
},
- 'mouseup .popup-item, touchend .popup-item'() {
+ 'mouseup .popup-item, touchend .popup-item'(e) {
+ e.stopPropagation();
const template = Template.instance();
+ const wasMenuIconClicked = e.target.classList.contains('sidebar-item__menu-icon');
template.clickingItem = false;
- template.value.set(this._id);
- template.enterValue();
- template.open.set(false);
+ if (!wasMenuIconClicked) {
+ template.value.set(this._id);
+ template.enterValue();
+ template.open.set(false);
+ }
},
});
diff --git a/app/ui-sidenav/client/SortList.js b/app/ui-sidenav/client/SortList.js
index b0add853e97..71ca52af71e 100644
--- a/app/ui-sidenav/client/SortList.js
+++ b/app/ui-sidenav/client/SortList.js
@@ -16,7 +16,7 @@ function SortListItem({ text, icon, input }) {
- {text}
+ {text}
@@ -54,7 +54,7 @@ function SortModeList() {
return <>
- {t('Sort_By')}
+ {t('Sort_By')}
@@ -84,7 +84,7 @@ function ViewModeList() {
return <>
- {t('View_mode')}
+ {t('View_mode')}
@@ -117,7 +117,7 @@ function GroupingList() {
const t = useTranslation();
return <>
- {t('Grouping')}
+ {t('Grouping')}
diff --git a/app/ui-sidenav/client/index.js b/app/ui-sidenav/client/index.js
index ce46c1f15fa..800f22494f6 100644
--- a/app/ui-sidenav/client/index.js
+++ b/app/ui-sidenav/client/index.js
@@ -4,7 +4,6 @@ import './sidebarItem.html';
import './sideNav.html';
import './toolbar.html';
import './roomList.html';
-import './userStatus.html';
import './chatRoomItem';
import { toolbarSearch } from './sidebarHeader';
import './sidebarItem';
diff --git a/app/ui-sidenav/client/sideNav.html b/app/ui-sidenav/client/sideNav.html
index 8eed7aa179d..f84739b86ba 100644
--- a/app/ui-sidenav/client/sideNav.html
+++ b/app/ui-sidenav/client/sideNav.html
@@ -2,16 +2,20 @@