diff --git a/app/accounts/index.js b/app/accounts/index.js deleted file mode 100644 index ca39cd0df4b1..000000000000 --- a/app/accounts/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './server/index'; diff --git a/app/action-links/client/index.js b/app/action-links/client/index.js index 34c929c096f9..c09886562a2d 100644 --- a/app/action-links/client/index.js +++ b/app/action-links/client/index.js @@ -1,5 +1,4 @@ -import { actionLinks } from '../both/lib/actionLinks'; -import './lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; import './init'; import './stylesheets/actionLinks.css'; diff --git a/app/action-links/client/init.js b/app/action-links/client/init.js index 2865be4279c9..b5f218b1ca18 100644 --- a/app/action-links/client/init.js +++ b/app/action-links/client/init.js @@ -1,14 +1,14 @@ import { Blaze } from 'meteor/blaze'; import { Template } from 'meteor/templating'; -import { handleError } from '../../utils'; -import { fireGlobalEvent, Layout } from '../../ui-utils'; +import { handleError } from '../../utils/client'; +import { fireGlobalEvent, Layout } from '../../ui-utils/client'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; -import { actionLinks } from '../both/lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; Template.room.events({ - 'click .action-link'(event, instance) { + 'click [data-actionlink]'(event, instance) { event.preventDefault(); event.stopPropagation(); diff --git a/app/action-links/client/lib/actionLinks.js b/app/action-links/client/lib/actionLinks.js index 4391eda94afb..b3d911398dad 100644 --- a/app/action-links/client/lib/actionLinks.js +++ b/app/action-links/client/lib/actionLinks.js @@ -1,27 +1,58 @@ import { Meteor } from 'meteor/meteor'; -import { handleError } from '../../../utils'; -import { actionLinks } from '../../both/lib/actionLinks'; -// Action Links Handler. This method will be called off the client. +import { handleError } from '../../../utils/client'; +import { Messages, Subscriptions } from '../../../models/client'; -actionLinks.run = (name, messageId, instance) => { - const message = actionLinks.getMessage(name, messageId); +// Action Links namespace creation. +export const actionLinks = { + actions: {}, + register(name, funct) { + actionLinks.actions[name] = funct; + }, + getMessage(name, messageId) { + const userId = Meteor.userId(); + if (!userId) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: 'actionLinks.getMessage' }); + } + + const message = Messages.findOne({ _id: messageId }); + if (!message) { + throw new Meteor.Error('error-invalid-message', 'Invalid message', { function: 'actionLinks.getMessage' }); + } + + const subscription = Subscriptions.findOne({ + rid: message.rid, + 'u._id': userId, + }); + if (!subscription) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { function: 'actionLinks.getMessage' }); + } + + if (!message.actionLinks || !message.actionLinks[name]) { + throw new Meteor.Error('error-invalid-actionlink', 'Invalid action link', { function: 'actionLinks.getMessage' }); + } - const actionLink = message.actionLinks[name]; + return message; + }, + run(name, messageId, instance) { + const message = actionLinks.getMessage(name, messageId); - let ranClient = false; + const actionLink = message.actionLinks[name]; - if (actionLinks && actionLinks.actions && actionLinks.actions[actionLink.method_id]) { - // run just on client side - actionLinks.actions[actionLink.method_id](message, actionLink.params, instance); + let ranClient = false; - ranClient = true; - } + if (actionLinks && actionLinks.actions && actionLinks.actions[actionLink.method_id]) { + // run just on client side + actionLinks.actions[actionLink.method_id](message, actionLink.params, instance); - // and run on server side - Meteor.call('actionLinkHandler', name, messageId, (err) => { - if (err && !ranClient) { - handleError(err); + ranClient = true; } - }); + + // and run on server side + Meteor.call('actionLinkHandler', name, messageId, (err) => { + if (err && !ranClient) { + handleError(err); + } + }); + }, }; diff --git a/app/action-links/index.js b/app/action-links/index.js deleted file mode 100644 index a67eca871efb..000000000000 --- a/app/action-links/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -if (Meteor.isClient) { - module.exports = require('./client/index.js'); -} -if (Meteor.isServer) { - module.exports = require('./server/index.js'); -} diff --git a/app/action-links/server/actionLinkHandler.js b/app/action-links/server/actionLinkHandler.js index 067f727e3dda..7ccd1b05775b 100644 --- a/app/action-links/server/actionLinkHandler.js +++ b/app/action-links/server/actionLinkHandler.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { actionLinks } from '../both/lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; // Action Links Handler. This method will be called off the client. Meteor.methods({ diff --git a/app/action-links/server/index.js b/app/action-links/server/index.js index b1c484f79888..a6fb9f92b743 100644 --- a/app/action-links/server/index.js +++ b/app/action-links/server/index.js @@ -1,4 +1,4 @@ -import { actionLinks } from '../both/lib/actionLinks'; +import { actionLinks } from './lib/actionLinks'; import './actionLinkHandler'; export { diff --git a/app/action-links/both/lib/actionLinks.js b/app/action-links/server/lib/actionLinks.js similarity index 93% rename from app/action-links/both/lib/actionLinks.js rename to app/action-links/server/lib/actionLinks.js index c87c712e079b..3f7b2f2e5775 100644 --- a/app/action-links/both/lib/actionLinks.js +++ b/app/action-links/server/lib/actionLinks.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { Messages, Subscriptions } from '../../../models'; +import { Messages, Subscriptions } from '../../../models/server'; // Action Links namespace creation. export const actionLinks = { diff --git a/app/api/index.js b/app/api/index.js deleted file mode 100644 index ca39cd0df4b1..000000000000 --- a/app/api/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './server/index'; diff --git a/app/api/server/v1/assets.js b/app/api/server/v1/assets.js index eacf92ae31cd..108f9649ffe6 100644 --- a/app/api/server/v1/assets.js +++ b/app/api/server/v1/assets.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import Busboy from 'busboy'; -import { RocketChatAssets } from '../../../assets'; +import { RocketChatAssets } from '../../../assets/server'; import { API } from '../api'; API.v1.addRoute('assets.setAsset', { authRequired: true }, { diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 5a8be4493a00..65aee384de9d 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -539,7 +539,6 @@ API.v1.addRoute('users.setPreferences', { authRequired: true }, { mobileNotifications: Match.Maybe(String), enableAutoAway: Match.Maybe(Boolean), highlights: Match.Maybe(Array), - desktopNotificationDuration: Match.Maybe(Number), desktopNotificationRequireInteraction: Match.Maybe(Boolean), messageViewMode: Match.Maybe(Number), hideUsernames: Match.Maybe(Boolean), diff --git a/app/apps/client/admin/appManage.html b/app/apps/client/admin/appManage.html index 8b6c0371ed26..dbd37a41ee41 100644 --- a/app/apps/client/admin/appManage.html +++ b/app/apps/client/admin/appManage.html @@ -104,6 +104,22 @@

{{name}}

{{/each}} + {{#if essentials}} +
+ {{_ "Apps_Essential_Alert"}} + +
+

{{_ "Apps_Essential_Disclaimer"}}

+
+ {{/if}} + {{#if categories}}

{{_ "Categories"}}

diff --git a/app/apps/client/admin/appManage.js b/app/apps/client/admin/appManage.js index fb3a2c520074..daf10f0dd176 100644 --- a/app/apps/client/admin/appManage.js +++ b/app/apps/client/admin/appManage.js @@ -317,6 +317,12 @@ Template.appManage.helpers({ bundleAppNames(apps) { return apps.map((app) => app.latest.name).join(', '); }, + essentials() { + return Template.instance()._app.get('essentials')?.map((interfaceName) => ({ + interfaceName, + i18nKey: `Apps_Interface_${ interfaceName }`, + })); + }, }); Template.appManage.events({ diff --git a/app/apps/server/converters/rooms.js b/app/apps/server/converters/rooms.js index 001ca0de6cbb..1fac2d4d6b60 100644 --- a/app/apps/server/converters/rooms.js +++ b/app/apps/server/converters/rooms.js @@ -69,7 +69,7 @@ export class AppRoomsConverter { } const newRoom = { - _id: room.id, + ...room.id && { _id: room.id }, fname: room.displayName, name: room.slugifiedName, t: room.type, diff --git a/app/assets/index.js b/app/assets/index.js deleted file mode 100644 index ca39cd0df4b1..000000000000 --- a/app/assets/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './server/index'; diff --git a/app/bigbluebutton/index.js b/app/bigbluebutton/index.js deleted file mode 100644 index ba58589ba3d7..000000000000 --- a/app/bigbluebutton/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './server/bigbluebutton-api'; diff --git a/app/bigbluebutton/server/index.js b/app/bigbluebutton/server/index.js new file mode 100644 index 000000000000..b6be696a20bd --- /dev/null +++ b/app/bigbluebutton/server/index.js @@ -0,0 +1 @@ +export { default } from './bigbluebutton-api'; diff --git a/app/blockstack/server/routes.js b/app/blockstack/server/routes.js index ee4c7cc087df..81902030e426 100644 --- a/app/blockstack/server/routes.js +++ b/app/blockstack/server/routes.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { WebApp } from 'meteor/webapp'; import { settings } from '../../settings'; -import { RocketChatAssets } from '../../assets'; +import { RocketChatAssets } from '../../assets/server'; WebApp.connectHandlers.use('/_blockstack/manifest', Meteor.bindEnvironment(function(req, res) { const name = settings.get('Site_Name'); diff --git a/app/bot-helpers/index.js b/app/bot-helpers/index.js deleted file mode 100644 index f5778a23b606..000000000000 --- a/app/bot-helpers/index.js +++ /dev/null @@ -1 +0,0 @@ -import './server/index'; diff --git a/app/crowd/server/crowd.js b/app/crowd/server/crowd.js index c91d2de399e4..6bbf95614358 100644 --- a/app/crowd/server/crowd.js +++ b/app/crowd/server/crowd.js @@ -9,6 +9,7 @@ import { _setRealName } from '../../lib'; import { Users } from '../../models'; import { settings } from '../../settings'; import { hasRole } from '../../authorization'; +import { deleteUser } from '../../lib/server/functions'; const logger = new Logger('CROWD', {}); @@ -203,6 +204,13 @@ export class CROWD { const response = self.crowdClient.searchSync('user', `email=" ${ email } "`); if (!response || response.users.length === 0) { logger.warn('Could not find user in CROWD with username or email:', crowd_username, email); + if (settings.get('CROWD_Remove_Orphaned_Users') === true) { + logger.info('Removing user:', crowd_username); + Meteor.defer(function() { + deleteUser(user._id); + logger.info('User removed:', crowd_username); + }); + } return; } crowd_username = response.users[0].name; diff --git a/app/crowd/server/settings.js b/app/crowd/server/settings.js index 29307d957a78..b58362967294 100644 --- a/app/crowd/server/settings.js +++ b/app/crowd/server/settings.js @@ -14,6 +14,7 @@ Meteor.startup(function() { this.add('CROWD_APP_PASSWORD', '', { type: 'password', enableQuery, i18nLabel: 'Password', secret: true }); this.add('CROWD_Sync_User_Data', false, { type: 'boolean', enableQuery, i18nLabel: 'Sync_Users' }); this.add('CROWD_Sync_Interval', 'Every 60 mins', { type: 'string', enableQuery: enableSyncQuery, i18nLabel: 'Sync_Interval', i18nDescription: 'Crowd_sync_interval_Description' }); + this.add('CROWD_Remove_Orphaned_Users', false, { type: 'boolean', public: true, i18nLabel: 'Crowd_Remove_Orphaned_Users' }); this.add('CROWD_Clean_Usernames', true, { type: 'boolean', enableQuery, i18nLabel: 'Clean_Usernames', i18nDescription: 'Crowd_clean_usernames_Description' }); this.add('CROWD_Allow_Custom_Username', true, { type: 'boolean', i18nLabel: 'CROWD_Allow_Custom_Username' }); this.add('CROWD_Test_Connection', 'crowd_test_connection', { type: 'action', actionText: 'Test_Connection', i18nLabel: 'Test_Connection' }); diff --git a/app/integrations/server/api/api.js b/app/integrations/server/api/api.js index db79dc0943bd..aa55b3bd1fad 100644 --- a/app/integrations/server/api/api.js +++ b/app/integrations/server/api/api.js @@ -12,7 +12,7 @@ import moment from 'moment'; import { logger } from '../logger'; import { processWebhookMessage } from '../../../lib'; -import { API, APIClass, defaultRateLimiterOptions } from '../../../api'; +import { API, APIClass, defaultRateLimiterOptions } from '../../../api/server'; import * as Models from '../../../models'; import { settings } from '../../../settings/server'; diff --git a/app/lib/server/functions/addUserToRoom.js b/app/lib/server/functions/addUserToRoom.js index 77627bba2585..2d97b88184db 100644 --- a/app/lib/server/functions/addUserToRoom.js +++ b/app/lib/server/functions/addUserToRoom.js @@ -21,6 +21,16 @@ export const addUserToRoom = function(rid, user, inviter, silenced) { return; } + try { + Promise.await(Apps.triggerEvent(AppEvents.IPreRoomUserJoined, room, user, inviter)); + } catch (error) { + if (error instanceof AppsEngineException) { + throw new Meteor.Error('error-app-prevented', error.message); + } + + throw error; + } + if (room.t === 'c' || room.t === 'p') { // Add a new event, with an optional inviter callbacks.run('beforeAddedToRoom', { user, inviter }, room); diff --git a/app/lib/server/functions/createDirectRoom.js b/app/lib/server/functions/createDirectRoom.js index 31702b2e96ba..bdf87b460642 100644 --- a/app/lib/server/functions/createDirectRoom.js +++ b/app/lib/server/functions/createDirectRoom.js @@ -7,7 +7,6 @@ import { Rooms, Subscriptions } from '../../../models/server'; import { settings } from '../../../settings/server'; import { getDefaultSubscriptionPref } from '../../../utils/server'; - const generateSubscription = (fname, name, user, extra) => ({ alert: false, unread: 0, diff --git a/app/lib/server/lib/sendNotificationsOnMessage.js b/app/lib/server/lib/sendNotificationsOnMessage.js index 834eac239b0e..0ebf3c2501cb 100644 --- a/app/lib/server/lib/sendNotificationsOnMessage.js +++ b/app/lib/server/lib/sendNotificationsOnMessage.js @@ -112,7 +112,6 @@ export const sendNotification = async ({ user: sender, message, room, - duration: subscription.desktopNotificationDuration, }); } @@ -176,7 +175,6 @@ export const sendNotification = async ({ const project = { $project: { audioNotifications: 1, - desktopNotificationDuration: 1, desktopNotifications: 1, emailNotifications: 1, mobilePushNotifications: 1, @@ -314,7 +312,7 @@ export async function sendMessageNotifications(message, room, usersInThread = [] export async function sendAllNotifications(message, room) { if (TroubleshootDisableNotifications === true) { - return; + return message; } // threads diff --git a/app/lib/server/startup/settings.js b/app/lib/server/startup/settings.js index 63241e0b0bed..281206ec273c 100644 --- a/app/lib/server/startup/settings.js +++ b/app/lib/server/startup/settings.js @@ -236,11 +236,6 @@ settings.addGroup('Accounts', function() { public: true, i18nLabel: 'Idle_Time_Limit', }); - this.add('Accounts_Default_User_Preferences_desktopNotificationDuration', 0, { - type: 'int', - public: true, - i18nLabel: 'Notification_Duration', - }); this.add('Accounts_Default_User_Preferences_desktopNotificationRequireInteraction', false, { type: 'boolean', public: true, diff --git a/app/livechat/client/index.js b/app/livechat/client/index.js index 26013edf70a5..0174884f532f 100644 --- a/app/livechat/client/index.js +++ b/app/livechat/client/index.js @@ -9,3 +9,4 @@ import './stylesheets/livechat.css'; import './views/sideNav/livechat'; import './views/sideNav/livechatFlex'; import './externalFrame'; +import './lib/messageTypes'; diff --git a/app/livechat/client/lib/messageTypes.js b/app/livechat/client/lib/messageTypes.js new file mode 100644 index 000000000000..0e3353f31366 --- /dev/null +++ b/app/livechat/client/lib/messageTypes.js @@ -0,0 +1,5 @@ +import { actionLinks } from '../../../action-links/client'; + +actionLinks.register('createLivechatCall', function(message, params, instance) { + instance.tabBar.open('video'); +}); diff --git a/app/livechat/client/views/app/livechatAgents.html b/app/livechat/client/views/app/livechatAgents.html index bb57a86497e9..a32d04763b89 100644 --- a/app/livechat/client/views/app/livechatAgents.html +++ b/app/livechat/client/views/app/livechatAgents.html @@ -51,9 +51,11 @@ {{#table fixed='true' onScroll=onTableScroll}} -
{{_ "Name"}}
-
{{_ "Username"}}
-
{{_ "Email"}}
+
{{_ "Name"}}
+
{{_ "Username"}}
+
{{_ "Email"}}
+
{{_ "Status"}}
+
{{_ "Service"}}
 
@@ -79,6 +81,8 @@ {{username}} {{emailAddress}} + {{status}} + {{statusService}} diff --git a/app/livechat/client/views/app/livechatAgents.js b/app/livechat/client/views/app/livechatAgents.js index 56c45da892ae..af3b68b88854 100644 --- a/app/livechat/client/views/app/livechatAgents.js +++ b/app/livechat/client/views/app/livechatAgents.js @@ -90,6 +90,10 @@ Template.livechatAgents.helpers({ data: Template.instance().tabBarData.get(), }; }, + statusService() { + const { status, statusLivechat } = this; + return statusLivechat === 'available' && status !== 'offline' ? t('Available') : t('Unavailable'); + }, }); const DEBOUNCE_TIME_FOR_SEARCH_AGENTS_IN_MS = 300; diff --git a/app/livechat/client/views/app/livechatCustomFieldForm.html b/app/livechat/client/views/app/livechatCustomFieldForm.html index d879ce74218c..0dcf4e1ae792 100644 --- a/app/livechat/client/views/app/livechatCustomFieldForm.html +++ b/app/livechat/client/views/app/livechatCustomFieldForm.html @@ -7,19 +7,19 @@
- +
- +
- @@ -28,18 +28,21 @@
-
- +
- +
+ {{#if customFieldsTemplate}} + {{> Template.dynamic template=customFieldsTemplate data=dataContext }} + {{/if}}
diff --git a/app/livechat/client/views/app/livechatCustomFieldForm.js b/app/livechat/client/views/app/livechatCustomFieldForm.js index b768a5babeae..9f8944dfdd7a 100644 --- a/app/livechat/client/views/app/livechatCustomFieldForm.js +++ b/app/livechat/client/views/app/livechatCustomFieldForm.js @@ -5,6 +5,7 @@ import { Template } from 'meteor/templating'; import toastr from 'toastr'; import { t, handleError } from '../../../../utils'; +import { getCustomFormTemplate } from './customTemplates/register'; import './livechatCustomFieldForm.html'; import { APIClient } from '../../../../utils/client'; @@ -12,6 +13,16 @@ Template.livechatCustomFieldForm.helpers({ customField() { return Template.instance().customField.get(); }, + + customFieldsTemplate() { + return getCustomFormTemplate('livechatCustomFieldsAdditionalForm'); + }, + + dataContext() { + // To make the dynamic template reactive we need to pass a ReactiveVar through the data property + // because only the dynamic template data will be reloaded + return Template.instance().localFields; + }, }); Template.livechatCustomFieldForm.events({ @@ -45,6 +56,16 @@ Template.livechatCustomFieldForm.events({ regexp: regexp.trim(), }; + instance.$('.additional-field').each((i, el) => { + const elField = instance.$(el); + const name = elField.attr('name'); + let value = elField.val(); + if (['true', 'false'].includes(value) && el.tagName === 'SELECT') { + value = value === 'true'; + } + customFieldData[name] = value; + }); + Meteor.call('livechat:saveCustomField', _id, customFieldData, function(error) { $btn.html(oldBtnValue); if (error) { @@ -60,12 +81,20 @@ Template.livechatCustomFieldForm.events({ e.preventDefault(); FlowRouter.go('livechat-customfields'); }, + + 'change .custom-field-input'(e, instance) { + const { target: { name, value } } = e; + instance.localFields.set({ ...instance.localFields.get(), [name]: value }); + }, }); Template.livechatCustomFieldForm.onCreated(async function() { this.customField = new ReactiveVar({}); + this.localFields = new ReactiveVar({}); + const { customField } = await APIClient.v1.get(`livechat/custom-fields/${ FlowRouter.getParam('_id') }`); if (customField) { this.customField.set(customField); + this.localFields.set({ ...customField }); } }); diff --git a/app/livechat/client/views/app/tabbar/visitorEdit.html b/app/livechat/client/views/app/tabbar/visitorEdit.html index a83fcfd5e766..778b28a7e1f5 100644 --- a/app/livechat/client/views/app/tabbar/visitorEdit.html +++ b/app/livechat/client/views/app/tabbar/visitorEdit.html @@ -30,17 +30,9 @@

{{username}}

- {{#if canViewCustomFields }} - {{#each visitorCustomFields}} -
- -
+ {{#each field in visitorCustomFields}} + {{> visitorEditCustomField field }} {{/each}} {{/if}} {{/with}} @@ -94,15 +86,8 @@

{{_ "Conversation" }}

{{#if canViewCustomFields }} - {{#each roomCustomFields}} -
- -
+ {{#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 3f9cf9b3e768..536d0b77e275 100644 --- a/app/livechat/client/views/app/tabbar/visitorEdit.js +++ b/app/livechat/client/views/app/tabbar/visitorEdit.js @@ -11,6 +11,16 @@ import { getCustomFormTemplate } from '../customTemplates/register'; const CUSTOM_FIELDS_COUNT = 100; +const getCustomFieldsByScope = (customFields = [], data = {}, filter, disabled) => + customFields + .filter(({ visibility, scope }) => visibility !== 'hidden' && scope === filter) + .map(({ _id: name, scope, label, ...extraData }) => { + const value = data[name] ? data[name] : ''; + return { name, label, scope, value, disabled, ...extraData }; + }); + +const isCustomFieldDisabled = () => !hasPermission('edit-livechat-room-customfields'); + Template.visitorEdit.helpers({ visitor() { return Template.instance().visitor.get(); @@ -20,28 +30,16 @@ Template.visitorEdit.helpers({ return hasAtLeastOnePermission(['view-livechat-room-customfields', 'edit-livechat-room-customfields']); }, - canOnlyViewCustomFields() { - return hasPermission('view-livechat-room-customfields') && !hasPermission('edit-livechat-room-customfields'); - }, - visitorCustomFields() { const customFields = Template.instance().customFields.get(); if (!customFields || customFields.length === 0) { return []; } - const fields = []; const visitor = Template.instance().visitor.get(); const { livechatData = {} } = visitor || {}; - customFields.forEach((field) => { - if (field.visibility !== 'hidden' && field.scope === 'visitor') { - const value = livechatData[field._id] ? livechatData[field._id] : ''; - fields.push({ name: field._id, label: field.label, value }); - } - }); - - return fields; + return getCustomFieldsByScope(customFields, livechatData, 'visitor', isCustomFieldDisabled()); }, room() { @@ -54,18 +52,10 @@ Template.visitorEdit.helpers({ return []; } - const fields = []; const room = Template.instance().room.get(); const { livechatData = {} } = room || {}; - customFields.forEach((field) => { - if (field.visibility !== 'hidden' && field.scope === 'room') { - const value = livechatData[field._id] ? livechatData[field._id] : ''; - fields.push({ name: field._id, label: field.label, value }); - } - }); - - return fields; + return getCustomFieldsByScope(customFields, livechatData, 'room', isCustomFieldDisabled()); }, email() { 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 000000000000..bf85704e4b26 --- /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 000000000000..f0024c0725dd --- /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/regular.js b/app/livechat/client/views/regular.js index 30f1a72c3e14..e2a3f3b8b6fd 100644 --- a/app/livechat/client/views/regular.js +++ b/app/livechat/client/views/regular.js @@ -6,6 +6,7 @@ import './app/livechatRoomTagSelector'; import './app/tabbar/agentEdit'; import './app/tabbar/agentInfo'; import './app/tabbar/visitorEdit'; +import './app/tabbar/visitorEditCustomField'; import './app/tabbar/visitorForward'; import './app/tabbar/visitorHistory'; import './app/tabbar/visitorInfo'; diff --git a/app/livechat/imports/server/rest/agent.js b/app/livechat/imports/server/rest/agent.js index b928501dc2c1..170af7636ff2 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 f8345f7d31dc..c7fe16243a78 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 2537942e74dc..af8972296b20 100644 --- a/app/livechat/imports/server/rest/dashboards.js +++ b/app/livechat/imports/server/rest/dashboards.js @@ -1,6 +1,6 @@ import { Match, check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { hasPermission } from '../../../../authorization/server'; import { findAllChatsStatus, diff --git a/app/livechat/imports/server/rest/departments.js b/app/livechat/imports/server/rest/departments.js index 8a255185f86f..9ec4113ba66a 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 cce8c53a7165..b4b8efa55034 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 a553c875fe84..3abfc5eee735 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 08d9d064892a..6b7aed33d89f 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 a40557231b53..f5ad166c8c70 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 f321a31ea5a3..7b2cd02497ee 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 d5f319f2e3c0..43b586431ea2 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 d7556006597a..052da5db3963 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 edebecb583a7..f813e7def804 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 ca6ebe7a12a0..de3d0b57f27b 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 3d28f420402e..4c27811749a6 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 04e1815b4f07..f0c88aa25c59 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 42b5a20b25d7..cc38c3bc4008 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 c68e120f0542..8f870923bd28 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/livechat.js b/app/livechat/server/api/lib/livechat.js index b1893f9a0f84..4626ce819b35 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 000000000000..60070dfc2645 --- /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 ad7cb3da93e5..d1c7477fdd56 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 3731e72f6b63..a63794bf1db0 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 68ceef87da14..7e2329058542 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 a1f4bab03405..85ef7a10a865 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 f64266d3be6c..3b19e832bc65 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 e2362baf6d32..0811bc8324fb 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 6788c30e3d86..8264228c97e7 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', { diff --git a/app/livechat/server/api/v1/pageVisited.js b/app/livechat/server/api/v1/pageVisited.js index e5ef7c42ba64..4f8c638e6146 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 83af5ffe1b61..5ec9ef1cf06e 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 02c0d9d27561..f8f3c923d25e 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 000000000000..aa3fb7facd0e --- /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 0aaa231da654..56159d8c349c 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 f34930a1b9c3..98007540876c 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/index.js b/app/livechat/server/index.js index 72632b50670e..527bc59802d6 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -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/Livechat.js b/app/livechat/server/lib/Livechat.js index b57571205fa4..ae5004a84500 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -117,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) { @@ -127,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; } diff --git a/app/livechat/server/lib/RoutingManager.js b/app/livechat/server/lib/RoutingManager.js index f654415af4a8..5558d1be5d0e 100644 --- a/app/livechat/server/lib/RoutingManager.js +++ b/app/livechat/server/lib/RoutingManager.js @@ -145,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/messageTypes.js b/app/livechat/server/lib/messageTypes.js new file mode 100644 index 000000000000..3d32da6f401f --- /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/methods/saveCustomField.js b/app/livechat/server/methods/saveCustomField.js index 4630b967cd26..772ca03cfae5 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/livestream/server/routes.js b/app/livestream/server/routes.js index 8668217d19a5..3a52aec6031c 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/meteor-accounts-saml/server/saml_utils.js b/app/meteor-accounts-saml/server/saml_utils.js index fa6a9c65c89c..80cadf2d3779 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/LivechatInquiry.js b/app/models/server/models/LivechatInquiry.js index 954c7083ab6a..9dc6251a17cb 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 25e2e51c6be2..6f9e1957c491 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', diff --git a/app/models/server/models/LivechatVisitors.js b/app/models/server/models/LivechatVisitors.js index 803ecf2dc56f..571d950b0fee 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/Rooms.js b/app/models/server/models/Rooms.js index 0f038458b700..6e5007cabf90 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/Subscriptions.js b/app/models/server/models/Subscriptions.js index 3442cfcd5a46..e991c1d925a1 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 c9515a3b03e7..5f9374fac4a6 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) { diff --git a/app/models/server/raw/LivechatVisitors.js b/app/models/server/raw/LivechatVisitors.js index 7ee3f02f2cca..8be6eccd5d49 100644 --- a/app/models/server/raw/LivechatVisitors.js +++ b/app/models/server/raw/LivechatVisitors.js @@ -1,3 +1,5 @@ +import s from 'underscore.string'; + import { BaseRaw } from './BaseRaw'; export class LivechatVisitorsRaw extends BaseRaw { @@ -12,4 +14,43 @@ export class LivechatVisitorsRaw extends BaseRaw { 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/oauth2-server-config/server/oauth/oauth2-server.js b/app/oauth2-server-config/server/oauth/oauth2-server.js index ff813497bc5a..f1c51982c760 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/push-notifications/client/views/pushNotificationsFlexTab.html b/app/push-notifications/client/views/pushNotificationsFlexTab.html index a32f1b6e9713..f1e3a28162ad 100644 --- a/app/push-notifications/client/views/pushNotificationsFlexTab.html +++ b/app/push-notifications/client/views/pushNotificationsFlexTab.html @@ -101,20 +101,6 @@ {{/with}}
-
- - {{# with "desktopNotificationDuration"}} - - {{/with}} -
diff --git a/app/push-notifications/client/views/pushNotificationsFlexTab.js b/app/push-notifications/client/views/pushNotificationsFlexTab.js index a71609be4145..43f026f2aaa9 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 3ddd80299e60..faf879b7ed94 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/search/server/events/events.js b/app/search/server/events/events.js index 79e3c8aa8142..7f0f5032a1d7 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/theme/client/imports/components/header.css b/app/theme/client/imports/components/header.css index d5b0a05cf493..3dfa4f441492 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 bda5cf955e32..04f7cb05d970 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 6d78136b841f..0da2ebec46e8 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 551c02e9dd40..807bdd7c88d2 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 b6625f0abe94..ca912108bad1 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 de54cba02ae4..fc963c5eda02 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 125ed7283f45..17771d9a3081 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 033835d8f094..d7896386bce0 100644 --- a/app/theme/client/imports/components/sidebar/sidebar.css +++ b/app/theme/client/imports/components/sidebar/sidebar.css @@ -63,10 +63,52 @@ } } - & .unread-rooms { - padding: calc(var(--sidebar-small-default-padding) - 8px); + & .wrapper-unread { + position: relative; + z-index: 2; - text-align: center; + & .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_old.css b/app/theme/client/imports/general/base_old.css index d2b2ace62a9b..c5be5ae73bde 100644 --- a/app/theme/client/imports/general/base_old.css +++ b/app/theme/client/imports/general/base_old.css @@ -1314,6 +1314,15 @@ border-bottom: none; } + & .add-token { + display: flex; + + & .rc-select { + width: 40%; + margin: 0 0 0 10px; + } + } + &:first-child { padding-top: 0; } @@ -1958,7 +1967,6 @@ width: 10px; height: 10px; - border-width: 1px; border-radius: 10px; } @@ -3302,9 +3310,15 @@ } .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; @@ -3317,10 +3331,17 @@ font-weight: 300; } + button { + margin: 0 5px; + + flex-grow: 1; + } + & .button { margin-bottom: 4px; font-size: 18px; + line-height: 22px; flex-grow: 1; @@ -4147,6 +4168,15 @@ display: none; } } + + .add-token { + display: block !important; + + & .rc-select { + width: auto !important; + margin: 10px 0 !important; + } + } } @media (width <= 500px) { diff --git a/app/theme/client/imports/general/theme_old.css b/app/theme/client/imports/general/theme_old.css index 657ebb4f3c41..268ea9cdc630 100644 --- a/app/theme/client/imports/general/theme_old.css +++ b/app/theme/client/imports/general/theme_old.css @@ -411,19 +411,18 @@ textarea { } i.status-online { - color: var(--status-online); + color: var(--rc-status-online); } .status-bg-online { - background-color: var(--status-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 { - border-color: var(--status-online-darken-10); - background-color: var(--status-online); + background-color: var(--rc-status-online); } .account-box .status-offline .thumb::after, @@ -432,11 +431,11 @@ i.status-online { } i.status-away { - color: var(--status-away); + color: var(--rc-status-away); } .status-bg-away { - background-color: var(--status-away); + background-color: var(--rc-status-away); } .account-box .status-away .thumb::after, @@ -444,38 +443,35 @@ i.status-away { .popup-user-status-away, .status-away::after, .status-pending::after { - border-color: var(--status-away-darken-10); - background-color: var(--status-away); + background-color: var(--rc-status-away); } i.status-busy { - color: var(--status-busy); + color: var(--rc-status-busy); } .status-bg-busy { - background-color: var(--status-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 { - border-color: var(--status-busy-darken-10); - background-color: var(--status-busy); + background-color: var(--rc-status-busy); } i.status-offline { - color: var(--status-offline); + color: var(--rc-status-offline); } .status-bg-offline { - background-color: var(--status-offline); + background-color: var(--rc-status-offline); } .popup-user-status-offline, .status-offline::after { - border-color: var(--status-offline-darken-10); - background-color: var(--status-offline); + background-color: var(--rc-status-offline); } .alert-warning { diff --git a/app/theme/client/imports/general/variables.css b/app/theme/client/imports/general/variables.css index d44f21266256..fb317250592e 100644 --- a/app/theme/client/imports/general/variables.css +++ b/app/theme/client/imports/general/variables.css @@ -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; @@ -40,7 +40,7 @@ /* #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; @@ -75,6 +75,7 @@ --component-color: #f2f3f5; --pending-color: #fcb316; --error-color: #bc2031; + --success-color: #2de0a5; --selection-color: #02acec; --attention-color: #9c27b0; @@ -86,7 +87,6 @@ --link-font-color: var(--primary-action-color); --info-font-color: var(--secondary-font-color); --custom-scrollbar-color: var(--transparent-darker); - --status-offline: var(--transparent-darker); /* #endregion */ @@ -108,11 +108,12 @@ --flex-tab-webrtc-2-width: 850px; --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/threads/client/flextab/threads.js b/app/threads/client/flextab/threads.js index a4fa8d9faee1..032dde9b302a 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 5d7bf76c4c8c..fe8ec38b40c6 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 a9348619caeb..8de27cafba64 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/ui-account/client/accountPreferences.html b/app/ui-account/client/accountPreferences.html index ade63fece090..0da55e54fdf9 100644 --- a/app/ui-account/client/accountPreferences.html +++ b/app/ui-account/client/accountPreferences.html @@ -92,16 +92,6 @@

{{_ "Notifications"}}

{{/if}}
-
- -
- {{#if desktopNotificationDuration}} - - {{else}} - - {{/if}} -
-
diff --git a/app/ui-account/client/accountPreferences.js b/app/ui-account/client/accountPreferences.js index 099fab32f97a..2d7080102c3f 100644 --- a/app/ui-account/client/accountPreferences.js +++ b/app/ui-account/client/accountPreferences.js @@ -83,13 +83,6 @@ Template.accountPreferences.helpers({ desktopNotificationDisabled() { return KonchatNotification.notificationStatus.get() === 'denied' || (window.Notification && Notification.permission === 'denied'); }, - desktopNotificationDuration() { - const userPref = getUserPreference(Meteor.userId(), 'desktopNotificationDuration', 'undefined'); - return userPref !== 'undefined' ? userPref : undefined; - }, - defaultDesktopNotificationDuration() { - return settings.get('Accounts_Default_User_Preferences_desktopNotificationDuration'); - }, desktopNotificationRequireInteraction() { const userPref = getUserPreference(Meteor.userId(), 'desktopNotificationRequireInteraction', 'undefined'); return userPref !== 'undefined' ? userPref : undefined; @@ -178,7 +171,6 @@ Template.accountPreferences.onCreated(function() { data.sendOnEnter = $('#sendOnEnter').find('select').val(); data.autoImageLoad = JSON.parse($('input[name=autoImageLoad]:checked').val()); data.emailNotificationMode = $('select[name=emailNotificationMode]').val(); - data.desktopNotificationDuration = $('input[name=desktopNotificationDuration]').val() === '' ? settings.get('Accounts_Default_User_Preferences_desktopNotificationDuration') : parseInt($('input[name=desktopNotificationDuration]').val()); data.desktopNotifications = $('#desktopNotifications').find('select').val(); data.mobileNotifications = $('#mobileNotifications').find('select').val(); data.unreadAlert = JSON.parse($('#unreadAlert').find('input:checked').val()); @@ -330,7 +322,6 @@ Template.accountPreferences.events({ 'click .js-test-notifications'(e) { e.preventDefault(); KonchatNotification.notify({ - duration: $('input[name=desktopNotificationDuration]').val(), payload: { sender: { username: 'rocket.cat' }, }, title: TAPi18n.__('Desktop_Notification_Test'), diff --git a/app/ui-account/client/accountProfile.html b/app/ui-account/client/accountProfile.html index edb1ce6f6a8c..9da2906056ff 100644 --- a/app/ui-account/client/accountProfile.html +++ b/app/ui-account/client/accountProfile.html @@ -115,8 +115,9 @@