From e4adb9b312f04dda0dea6faf8fb1854263258aed Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 28 Apr 2020 18:52:08 -0300 Subject: [PATCH 001/121] Bump version to 3.3.0-develop --- .docker/Dockerfile.rhel | 2 +- app/utils/rocketchat.info | 2 +- package-lock.json | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index dc9695a75fb8..8deff6b93c60 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/rhscl/nodejs-8-rhel7 -ENV RC_VERSION 3.2.0 +ENV RC_VERSION 3.3.0-develop MAINTAINER buildmaster@rocket.chat diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 7ccbef98e389..98a1785da5a7 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "3.2.0" + "version": "3.3.0-develop" } diff --git a/package-lock.json b/package-lock.json index b7cbefa15a7d..1afa69f2f29b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "3.2.0-develop", + "version": "3.3.0-develop", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d5eca0bf5930..29f7fd599db5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "3.2.0", + "version": "3.3.0-develop", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" From 60e1452436aa61b21271ddd341ed5ed0e72715cf Mon Sep 17 00:00:00 2001 From: Marcos Spessatto Defendi Date: Wed, 29 Apr 2020 17:07:19 -0300 Subject: [PATCH 002/121] Priority system messages were always created (#17479) --- app/livechat/server/methods/saveInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/livechat/server/methods/saveInfo.js b/app/livechat/server/methods/saveInfo.js index a5f2eef00632..7d2fe0534f63 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' }); } From 5b60fda3d866d52f9a6624bccd68b6a794fe838e Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 30 Apr 2020 09:59:56 -0300 Subject: [PATCH 003/121] [FIX] Change email verification label (#17450) Co-authored-by: Guilherme Gazzo --- packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 24b939428ce0..d660e05c87c7 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -69,7 +69,7 @@ "Accounts_Email_Deactivated": "[name]

Your account was deactivated.

", "Accounts_Enrollment_Email_Default": "

Welcome to [Site_Name]

Go to [Site_URL] and try the best open source chat solution available today!

", "Accounts_Email_Deactivated_Subject": "Account deactivated", - "Accounts_EmailVerification": "Email Verification", + "Accounts_EmailVerification": "Only allow verified users to login", "Accounts_EmailVerification_Description": "Make sure you have correct SMTP settings to use this feature", "Accounts_Enrollment_Email_Subject_Default": "Welcome to [Site_Name]", "Accounts_Enrollment_Email": "Enrollment Email", From 2e2e1860062c9af523a0c101ef52b1437e412887 Mon Sep 17 00:00:00 2001 From: Shiqi Mei Date: Fri, 1 May 2020 01:03:30 +0800 Subject: [PATCH 004/121] [NEW] [Apps-Engine] New Livechat event handlers (#17033) * Add livechat missing event handlers * Rely on removeAgentFromSubscription Co-authored-by: Renato Becker Co-authored-by: Douglas Gubert --- app/apps/server/bridges/listeners.js | 22 ++++++++++++++++------ app/livechat/server/lib/Helper.js | 9 +++++++++ app/livechat/server/lib/Livechat.js | 5 +++++ app/livechat/server/lib/RoutingManager.js | 7 +++++++ package-lock.json | 10 +++++----- package.json | 2 +- 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/apps/server/bridges/listeners.js b/app/apps/server/bridges/listeners.js index 693c55271c31..259b937b1686 100644 --- a/app/apps/server/bridges/listeners.js +++ b/app/apps/server/bridges/listeners.js @@ -1,3 +1,5 @@ +import { AppInterface } from '@rocket.chat/apps-engine/server/compiler'; + export class AppListenerBridge { constructor(orch) { this.orch = orch; @@ -54,13 +56,21 @@ export class AppListenerBridge { // } } - async livechatEvent(inte, room) { - const rm = this.orch.getConverters().get('rooms').convertRoom(room); - const result = await this.orch.getManager().getListenerManager().executeListener(inte, rm); + async livechatEvent(inte, data) { + switch (inte) { + case AppInterface.IPostLivechatRoomStarted: + case AppInterface.IPostLivechatRoomClosed: + const room = this.orch.getConverters().get('rooms').convertRoom(data); - if (typeof result === 'boolean') { - return result; + return this.orch.getManager().getListenerManager().executeListener(inte, room); + case AppInterface.IPostLivechatAgentAssigned: + case AppInterface.IPostLivechatAgentUnassigned: + return this.orch.getManager().getListenerManager().executeListener(inte, { + room: this.orch.getConverters().get('rooms').convertRoom(data.room), + agent: this.orch.getConverters().get('users').convertToApp(data.user), + }); + default: + break; } - return this.orch.getConverters().get('rooms').convertAppRoom(result); } } diff --git a/app/livechat/server/lib/Helper.js b/app/livechat/server/lib/Helper.js index bfa83dc0d742..5a9e78e63464 100644 --- a/app/livechat/server/lib/Helper.js +++ b/app/livechat/server/lib/Helper.js @@ -1,3 +1,4 @@ +import { AppInterface } from '@rocket.chat/apps-engine/server/compiler'; import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { MongoInternals } from 'meteor/mongo'; @@ -7,6 +8,7 @@ import { Livechat } from './Livechat'; import { RoutingManager } from './RoutingManager'; import { callbacks } from '../../../callbacks/server'; import { settings } from '../../../settings'; +import { Apps } from '../../../apps/server'; export const createLivechatRoom = (rid, name, guest, roomInfo = {}, extraData = {}) => { check(rid, String); @@ -42,6 +44,8 @@ export const createLivechatRoom = (rid, name, guest, roomInfo = {}, extraData = }, extraRoomInfo); const roomId = Rooms.insert(room); + + Apps.getBridges().getListenerBridge().livechatEvent(AppInterface.IPostLivechatRoomStarted, room); callbacks.run('livechat.newRoom', room); return roomId; }; @@ -157,8 +161,13 @@ 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(AppInterface.IPostLivechatAgentUnassigned, { room, user }); }; export const normalizeAgent = (agentId) => { diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 501e53406591..4050d17bfeaa 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -368,7 +368,12 @@ export const Livechat = { Messages.createCommandWithRoomIdAndUser('promptTranscript', rid, closeData.closedBy); Meteor.defer(() => { + /** + * @deprecated the `AppInterface.ILivechatRoomClosedHandler` event will be removed + * in the next major version of the Apps-Engine + */ Apps.getBridges().getListenerBridge().livechatEvent(AppInterface.ILivechatRoomClosedHandler, room); + Apps.getBridges().getListenerBridge().livechatEvent(AppInterface.IPostLivechatRoomClosed, room); callbacks.run('livechat.closeRoom', room); }); diff --git a/app/livechat/server/lib/RoutingManager.js b/app/livechat/server/lib/RoutingManager.js index 3b65ba14ceaa..2f4579f29b7b 100644 --- a/app/livechat/server/lib/RoutingManager.js +++ b/app/livechat/server/lib/RoutingManager.js @@ -1,3 +1,4 @@ +import { AppInterface } from '@rocket.chat/apps-engine/server/compiler'; import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; @@ -11,6 +12,7 @@ import { createLivechatSubscription, } from './Helper'; import { callbacks } from '../../../callbacks/server'; import { LivechatRooms, Rooms, Messages, Users, LivechatInquiry } from '../../../models/server'; +import { Apps } from '../../../apps/server'; export const RoutingManager = { methodName: null, @@ -74,8 +76,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(AppInterface.IPostLivechatAgentAssigned, { room, user }); return inquiry; }, @@ -98,6 +104,7 @@ export const RoutingManager = { } const { servedBy } = room; + if (servedBy) { removeAgentFromSubscription(rid, servedBy); LivechatRooms.removeAgentByRoomId(rid); diff --git a/package-lock.json b/package-lock.json index 1afa69f2f29b..4732a19cc78b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2928,9 +2928,9 @@ } }, "@rocket.chat/apps-engine": { - "version": "1.14.0-beta.3119", - "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.14.0-beta.3119.tgz", - "integrity": "sha512-SoQicHOGkQD6wwcnMzc1qETbNoMwQ2ei5j5krzh0/dachCbHG+C1rBO8yYbK2TQwuSjxR0wLM20ZbM57iHaYKw==", + "version": "1.15.0-alpha.3202", + "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.15.0-alpha.3202.tgz", + "integrity": "sha512-cNPPjTJE6V6OHp/6vY86LFOKEaaMELWocDxncMp2JetyYinYsU8rlHu8s/KHz7Nhptka5YMkXsZ4ozseld/wuw==", "requires": { "adm-zip": "^0.4.9", "cryptiles": "^4.1.3", @@ -6895,8 +6895,8 @@ "integrity": "sha1-WYc/Nej89sc2HBAjkmHXbhU0i7I=" }, "adm-zip": { - "version": "0.4.12", - "resolved": "github:RocketChat/adm-zip#34ac787ce7f45c6cea99df049deb17d0c476a3b5" + "version": "github:RocketChat/adm-zip#34ac787ce7f45c6cea99df049deb17d0c476a3b5", + "from": "github:RocketChat/adm-zip" }, "aggregate-error": { "version": "3.0.1", diff --git a/package.json b/package.json index 29f7fd599db5..3082257f2656 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "@nivo/heatmap": "^0.61.0", "@nivo/line": "^0.61.1", "@nivo/pie": "^0.61.1", - "@rocket.chat/apps-engine": "^1.14.0-beta.3119", + "@rocket.chat/apps-engine": "^1.15.0-alpha.3202", "@rocket.chat/fuselage": "^0.7.1", "@rocket.chat/fuselage-hooks": "^0.7.1", "@rocket.chat/fuselage-polyfills": "^0.7.1", From e0b867666df14edd45def0df2311b85abbb54df7 Mon Sep 17 00:00:00 2001 From: Shiqi Mei Date: Fri, 1 May 2020 01:12:29 +0800 Subject: [PATCH 005/121] [IMPROVE] [Apps-Engine] App user as the default notifier (#17050) * App user as the default notifier * Move changes to the apps-engine side Co-authored-by: Douglas Gubert --- app/apps/server/bridges/messages.js | 15 ++++++++------- app/apps/server/converters/messages.js | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/apps/server/bridges/messages.js b/app/apps/server/bridges/messages.js index cc53aa392bd4..e3f3e722dc65 100644 --- a/app/apps/server/bridges/messages.js +++ b/app/apps/server/bridges/messages.js @@ -1,6 +1,6 @@ import { Random } from 'meteor/random'; -import { Messages, Users, Subscriptions } from '../../../models'; +import { Messages, Users, Subscriptions } from '../../../models/server'; import { Notifications } from '../../../notifications'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; @@ -48,12 +48,15 @@ export class AppMessageBridge { const msg = this.orch.getConverters().get('messages').convertAppMessage(message); - Notifications.notifyUser(user.id, 'message', Object.assign(msg, { + if (!msg) { + return; + } + + Notifications.notifyUser(user.id, 'message', { + ...msg, _id: Random.id(), ts: new Date(), - u: undefined, - editor: undefined, - })); + }); } async notifyRoom(room, message, appId) { @@ -68,8 +71,6 @@ export class AppMessageBridge { _id: Random.id(), rid: room.id, ts: new Date(), - u: undefined, - editor: undefined, }); const users = Subscriptions.findByRoomIdWhenUserIdExists(room.id, { fields: { 'u._id': 1 } }) diff --git a/app/apps/server/converters/messages.js b/app/apps/server/converters/messages.js index 0da7cbd768d2..11bf7f99eb07 100644 --- a/app/apps/server/converters/messages.js +++ b/app/apps/server/converters/messages.js @@ -78,7 +78,7 @@ export class AppMessagesConverter { } convertAppMessage(message) { - if (!message) { + if (!message || !message.room) { return undefined; } From 2808ce7e97f79aac4232ed367cbbfa5af26b679a Mon Sep 17 00:00:00 2001 From: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Thu, 30 Apr 2020 17:31:17 -0300 Subject: [PATCH 006/121] [FIX] LDAP login error on Enterprise version (#17497) --- app/ldap/server/loginHandler.js | 1 + app/ldap/server/sync.js | 2 ++ ee/app/ldap-enterprise/server/listener.js | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/ldap/server/loginHandler.js b/app/ldap/server/loginHandler.js index a0c0f893e8cf..c6025bda340c 100644 --- a/app/ldap/server/loginHandler.js +++ b/app/ldap/server/loginHandler.js @@ -126,6 +126,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) { if (settings.get('LDAP_Login_Fallback') === true && typeof loginRequest.ldapPass === 'string' && loginRequest.ldapPass.trim() !== '') { Accounts.setPassword(user._id, loginRequest.ldapPass, { logout: false }); } + logger.info('running afterLDAPLogin'); callbacks.run('afterLDAPLogin', { user, ldapUser, ldap }); return { userId: user._id, diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index dd9a359c4715..9c3249b9172c 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -210,6 +210,7 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { const syncUserRolesFieldMap = settings.get('LDAP_Sync_User_Data_GroupsMap').trim(); if (!syncUserRoles || !syncUserRolesFieldMap) { + logger.debug('not syncing user roles'); return []; } @@ -296,6 +297,7 @@ export function mapLDAPGroupsToChannels(ldap, ldapUser, user) { const userChannels = []; if (!syncUserRoles || !syncUserRolesAutoChannels || !syncUserRolesChannelFieldMap) { + logger.debug('not syncing groups to channels'); return []; } diff --git a/ee/app/ldap-enterprise/server/listener.js b/ee/app/ldap-enterprise/server/listener.js index e930e2a194d4..6ba05683504e 100644 --- a/ee/app/ldap-enterprise/server/listener.js +++ b/ee/app/ldap-enterprise/server/listener.js @@ -5,8 +5,8 @@ export const onLdapLogin = ({ user, ldapUser, ldap }) => { const validateLdapRolesForEachLogin = settings.get('LDAP_Validate_Roles_For_Each_Login'); const userExists = user._id; const userId = userExists ? user._id : user.userId; - const ldapUserRoles = getLdapRolesByUsername(ldapUser.uid, ldap); if (!userExists || validateLdapRolesForEachLogin) { + const ldapUserRoles = getLdapRolesByUsername(ldapUser.uid, ldap); const roles = getRocketChatRolesByLdapRoles(JSON.parse(settings.get('LDAP_Roles_To_Rocket_Chat_Roles')), ldapUserRoles); updateUserUsingMappedLdapRoles(userId, roles); } From ef77a2120bfc73bae0ddb6dd8419b050fbb5adc7 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Fri, 1 May 2020 13:31:08 -0300 Subject: [PATCH 007/121] [FIX] Replace obsolete X-FRAME-OPTIONS header on Livechat route (#17419) Replace deprecated X-FRAME-OPTIONS header by Content-Security-Policy. Co-authored-by: Marcos Spessatto Defendi --- app/livechat/server/livechat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/livechat/server/livechat.js b/app/livechat/server/livechat.js index a60126beda7f..7e81220805a8 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); From b1ec3dfaa425d7a8b2f0cde66293a3af7abd35c4 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 4 May 2020 09:39:34 -0300 Subject: [PATCH 008/121] RegExp improvements suggested by LGTM (#17500) --- app/autotranslate/server/autotranslate.js | 2 +- app/file-upload/server/lib/FileUpload.js | 2 +- app/file-upload/server/lib/proxy.js | 2 +- app/highlight-words/client/helper.js | 2 +- app/oembed/server/providers.js | 12 ++++++------ .../client/messageBox/messageBoxAutogrow.js | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/autotranslate/server/autotranslate.js b/app/autotranslate/server/autotranslate.js index 3215dbff3833..6019dfab37a1 100644 --- a/app/autotranslate/server/autotranslate.js +++ b/app/autotranslate/server/autotranslate.js @@ -156,7 +156,7 @@ export class AutoTranslate { message = Markdown.parseMessageNotEscaped(message); // Some parsers (e. g. Marked) wrap the complete message in a

- this is unnecessary and should be ignored with respect to translations - const regexWrappedParagraph = new RegExp('^\s*

|<\/p>\s*$', 'gm'); + const regexWrappedParagraph = new RegExp('^\\s*

|

\\s*$', 'gm'); message.msg = message.msg.replace(regexWrappedParagraph, ''); for (const tokenIndex in message.tokens) { diff --git a/app/file-upload/server/lib/FileUpload.js b/app/file-upload/server/lib/FileUpload.js index 3d006b14aa0d..e17ed2dbc939 100644 --- a/app/file-upload/server/lib/FileUpload.js +++ b/app/file-upload/server/lib/FileUpload.js @@ -318,7 +318,7 @@ export const FileUpload = { // This file type can be pretty much anything, so it's better if we don't mess with the file extension if (file.type !== 'application/octet-stream') { const ext = mime.extension(file.type); - if (ext && new RegExp(`\.${ ext }$`, 'i').test(file.name) === false) { + if (ext && new RegExp(`\\.${ ext }$`, 'i').test(file.name) === false) { file.name = `${ file.name }.${ ext }`; } } diff --git a/app/file-upload/server/lib/proxy.js b/app/file-upload/server/lib/proxy.js index 0ac8d52d97ec..8aaff7ce7088 100644 --- a/app/file-upload/server/lib/proxy.js +++ b/app/file-upload/server/lib/proxy.js @@ -30,7 +30,7 @@ WebApp.connectHandlers.stack.unshift({ const path = parsedUrl.pathname.substr(UploadFS.config.storesPath.length + 1); // Get store - const regExp = new RegExp('^\/([^\/\?]+)\/([^\/\?]+)$'); + const regExp = new RegExp('^/([^/?]+)/([^/?]+)$'); const match = regExp.exec(path); // Request is not valid diff --git a/app/highlight-words/client/helper.js b/app/highlight-words/client/helper.js index 5afd122b6f9a..654e3736fa9a 100644 --- a/app/highlight-words/client/helper.js +++ b/app/highlight-words/client/helper.js @@ -16,7 +16,7 @@ const highlightTemplate = '$1$2$3'; export const getRegexHighlight = (highlight) => new RegExp(`(^|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(${ s.escapeRegExp(highlight) })($|\\b|[\\s\\n\\r\\t.,،'\\\"\\+!?:-])(?![^<]*>|[^<>]*<\\/)`, 'gmi'); -export const getRegexHighlightUrl = (highlight) => new RegExp(`https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)(${ s.escapeRegExp(highlight) })\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)`, 'gmi'); +export const getRegexHighlightUrl = (highlight) => new RegExp(`https?:\/\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)(${ s.escapeRegExp(highlight) })\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)`, 'gmi'); export const highlightWords = (msg, highlights) => highlights.reduce((msg, { highlight, regex, urlRegex }) => { const urlMatches = checkHighlightedWordsInUrls(msg, urlRegex); diff --git a/app/oembed/server/providers.js b/app/oembed/server/providers.js index 7a25e7710323..9ae9dc24c6b5 100644 --- a/app/oembed/server/providers.js +++ b/app/oembed/server/providers.js @@ -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', }); diff --git a/app/ui-message/client/messageBox/messageBoxAutogrow.js b/app/ui-message/client/messageBox/messageBoxAutogrow.js index 06348dc729b7..b7174a5eaccf 100644 --- a/app/ui-message/client/messageBox/messageBoxAutogrow.js +++ b/app/ui-message/client/messageBox/messageBoxAutogrow.js @@ -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); From c05eac5d585b18c4de7e858e794e787eaa64c2fd Mon Sep 17 00:00:00 2001 From: Gabriel Engel Date: Mon, 4 May 2020 11:00:12 -0300 Subject: [PATCH 009/121] LingoHub based on develop (#17520) * LingoHub Update :rocket: Manual push by LingoHub User: Diego Sampaio. Project: Rocket.Chat Made with :heart: by https://lingohub.com * Apply suggestions from code review * i18n variable improvents for Serbian Co-authored-by: Diego Sampaio --- ee/i18n/da.i18n.json | 21 +- ee/i18n/ja.i18n.json | 21 +- ee/i18n/lv.i18n.json | 28 +- ee/i18n/pt-BR.i18n.json | 4 +- ee/i18n/zh-TW.i18n.json | 16 +- packages/rocketchat-i18n/i18n/af.i18n.json | 3 - packages/rocketchat-i18n/i18n/ar.i18n.json | 3 - packages/rocketchat-i18n/i18n/az.i18n.json | 3 - packages/rocketchat-i18n/i18n/be-BY.i18n.json | 3 - packages/rocketchat-i18n/i18n/bg.i18n.json | 3 - packages/rocketchat-i18n/i18n/bs.i18n.json | 3 - packages/rocketchat-i18n/i18n/ca.i18n.json | 3 - packages/rocketchat-i18n/i18n/cs.i18n.json | 5 - packages/rocketchat-i18n/i18n/cy.i18n.json | 3 - packages/rocketchat-i18n/i18n/da.i18n.json | 143 ++++-- packages/rocketchat-i18n/i18n/de-AT.i18n.json | 3 - packages/rocketchat-i18n/i18n/de.i18n.json | 5 - packages/rocketchat-i18n/i18n/el.i18n.json | 3 - packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- packages/rocketchat-i18n/i18n/eo.i18n.json | 3 - packages/rocketchat-i18n/i18n/es.i18n.json | 5 +- packages/rocketchat-i18n/i18n/fa.i18n.json | 3 - packages/rocketchat-i18n/i18n/fi.i18n.json | 3 - packages/rocketchat-i18n/i18n/fr.i18n.json | 3 - packages/rocketchat-i18n/i18n/he.i18n.json | 1 - packages/rocketchat-i18n/i18n/hr.i18n.json | 3 - packages/rocketchat-i18n/i18n/hu.i18n.json | 3 - packages/rocketchat-i18n/i18n/id.i18n.json | 3 - packages/rocketchat-i18n/i18n/it.i18n.json | 3 - packages/rocketchat-i18n/i18n/ja.i18n.json | 30 +- packages/rocketchat-i18n/i18n/km.i18n.json | 3 - packages/rocketchat-i18n/i18n/ko.i18n.json | 3 - packages/rocketchat-i18n/i18n/ku.i18n.json | 3 - packages/rocketchat-i18n/i18n/lo.i18n.json | 3 - packages/rocketchat-i18n/i18n/lt.i18n.json | 3 - packages/rocketchat-i18n/i18n/lv.i18n.json | 8 +- packages/rocketchat-i18n/i18n/mn.i18n.json | 3 - packages/rocketchat-i18n/i18n/ms-MY.i18n.json | 3 - packages/rocketchat-i18n/i18n/nl.i18n.json | 3 - packages/rocketchat-i18n/i18n/no.i18n.json | 3 - packages/rocketchat-i18n/i18n/pl.i18n.json | 3 - packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 5 +- packages/rocketchat-i18n/i18n/pt.i18n.json | 3 - packages/rocketchat-i18n/i18n/ro.i18n.json | 3 - packages/rocketchat-i18n/i18n/ru.i18n.json | 5 - packages/rocketchat-i18n/i18n/sk-SK.i18n.json | 3 - packages/rocketchat-i18n/i18n/sl-SI.i18n.json | 3 - packages/rocketchat-i18n/i18n/sq.i18n.json | 3 - packages/rocketchat-i18n/i18n/sr.i18n.json | 457 +++++++----------- packages/rocketchat-i18n/i18n/sv.i18n.json | 3 - packages/rocketchat-i18n/i18n/ta-IN.i18n.json | 3 - packages/rocketchat-i18n/i18n/th-TH.i18n.json | 3 - packages/rocketchat-i18n/i18n/tr.i18n.json | 3 - packages/rocketchat-i18n/i18n/ug.i18n.json | 1 - packages/rocketchat-i18n/i18n/uk.i18n.json | 3 - packages/rocketchat-i18n/i18n/vi-VN.i18n.json | 3 - packages/rocketchat-i18n/i18n/zh-HK.i18n.json | 3 - packages/rocketchat-i18n/i18n/zh-TW.i18n.json | 23 +- packages/rocketchat-i18n/i18n/zh.i18n.json | 5 - 59 files changed, 395 insertions(+), 510 deletions(-) diff --git a/ee/i18n/da.i18n.json b/ee/i18n/da.i18n.json index 6feb27469916..416276833b84 100644 --- a/ee/i18n/da.i18n.json +++ b/ee/i18n/da.i18n.json @@ -1,15 +1,24 @@ { "error-canned-response-not-found": "Der blev ikke fundet et opbevaret svar", + "error-forwarding-department-target-not-allowed": "Videresendelsen til afdelingen er ikke tilladt.", + "error-guests-cant-have-other-roles": "Gæstebrugere kan ikke have nogen anden rolle.", + "error-invalid-priority": "Ugyldig prioritet", + "error-max-guests-number-reached": "Du nåede det maksimale antal gæstebrugere der er tilladt af din licens. Kontakt sale@rocket.chat for en ny licens.", "error-max-number-simultaneous-chats-reached": "Det maksimale tilladte antal af samtidige chats pr agent er nået", "Add_monitor": "Tilføj monitor", "Available_departments": "Tilgængelige afdelinger", "Canned Responses": "Opbevarede svar", "Canned_Response_Removed": "Opbevaret svar blev fjernet", "Canned_Responses_Enable": "Aktivér opbevarede svar", + "Closed_automatically": "Blev automatisk lukket af systemet", "Edit_Tag": "Redigér mærke", "Edit_Unit": "Redigér enhed", + "Edit_Priority": "Rediger prioritet", + "Enable_omnichannel_auto_close_abandoned_rooms": "Aktivér automatisk lukning af værelser der er forladt af den besøgende", + "Enter_a_custom_message": "Indtast en brugerdefineret besked", "Enterprise_License": "Koncernlicens", "Enterprise_License_Description": "Hvis dit arbejdsområde er registreret og en licens er tildelt af Rocket.Chat Cloud behøver du ikke manuelt at opdatere licensen her", + "Estimated_due_time": "Estimeret forfaldstidspunkt (tid i minutter)", "Failed_to_add_monitor": "Fejl ved tilføjelse af monitor", "Invalid Canned Response": "Ikke gyldigt opbevaret svar", "Invalid_Department": "Ugyldig afdeling", @@ -22,6 +31,9 @@ "LDAP_Roles_To_Rocket_Chat_Roles_Description": "Rolletilknytning i objektformat hvor objektnøglen skal være LDAP-rollen og objektværdien skal være en array af RC-roller. Eksempel: { 'ldapRole': ['rcRole', 'anotherRCRole'] }", "LDAP_Validate_Roles_For_Each_Login": "Validér tilknytning for hvert login", "LDAP_Validate_Roles_For_Each_Login_Description": "Hvis validering skal ske for hvert login (Vær forsigtig med denne indstilling, fordi den vil overskrive brugerrollerne i hvert login. Ellers valideres dette kun på det tidspunkt hvor brugeren oprettes).", + "List_of_departments_for_forward": "Liste over tilladte afdelinger til videresendelse (valgfrit)", + "List_of_departments_for_forward_description": "Tillad at indstille en begrænset liste over afdelinger der kan modtage chats fra denne afdeling", + "Livechat_abandoned_rooms_closed_custom_message": "Tilpasset besked når værelset automatisk lukkes af besøgendes inaktivitet", "Livechat_Monitors": "Monitore", "Livechat_monitors": "Livechat-monitore", "Max_number_of_chats_per_agent": "Max antal samtidige chats", @@ -32,13 +44,19 @@ "Monitor_removed": "Monitor fjernet", "Monitors": "Monitore", "New_Canned_Response": "Nyt opbevaret svar", + "New_chat_priority": "Prioritet ændret: __user__ changed the priority to __priority__", "New_Tag": "Nyt mærke", "New_Unit": "Ny enhed", + "New_Priority": "Ny prioritet", "No_Canned_Responses": "Ingen opbevarede svar", + "Number_in_seconds": "Antal i sekunder", "Number_of_most_recent_chats_estimate_wait_time": "Antal seneste chats til beregning af estimeret ventetid", "Number_of_most_recent_chats_estimate_wait_time_description": "Dette antal definerer antallet af sidst anvendte værelser, der vil blive brugt til at beregne kø-ventetider.", "Others": "Andre", "Please_select_visibility": "Vælg en synlighed", + "Priorities": "Prioriteter", + "Priority": "Prioritet", + "Priority_removed": "Prioritet fjernet", "Role_Mapping": "Rolletilknytning", "Selected_departments": "Valgte afdelinger", "Selected_monitors": "Valgte monitore", @@ -58,5 +76,6 @@ "view-livechat-unit": "Se livechat-enheder", "Waiting_queue": "Ventende kø", "Waiting_queue_message": "Meddelelse for ventende kø", - "Waiting_queue_message_description": "Meddelelse der vises for de besøgende, når de kommer i kø" + "Waiting_queue_message_description": "Meddelelse der vises for de besøgende, når de kommer i kø", + "Without_priority": "Uden prioritet" } \ No newline at end of file diff --git a/ee/i18n/ja.i18n.json b/ee/i18n/ja.i18n.json index b1a53a11f45c..fcab17e60cd5 100644 --- a/ee/i18n/ja.i18n.json +++ b/ee/i18n/ja.i18n.json @@ -1,15 +1,24 @@ { "error-canned-response-not-found": "返信定型文が見つかりません", + "error-forwarding-department-target-not-allowed": "ターゲット部門への転送は許可されていません。", + "error-guests-cant-have-other-roles": "ゲストユーザーは他の役割を持つことはできません。", + "error-invalid-priority": "無効な優先度", + "error-max-guests-number-reached": "ライセンスで許可されているゲストユーザーの最大数に達しました。新しいライセンスについては、 sale@rocket.chat にお問い合わせください。", "error-max-number-simultaneous-chats-reached": "エージェントごとの同時チャットの最大数に達しました。", "Add_monitor": "モニターを追加", "Available_departments": "利用可能な部門", "Canned Responses": "返信定型文", "Canned_Response_Removed": "削除された定型文", "Canned_Responses_Enable": "返信定型文を有効にする", + "Closed_automatically": "システムにより自動的に閉鎖", "Edit_Tag": "タグを編集", "Edit_Unit": "ユニットを編集", + "Edit_Priority": "優先度を編集", + "Enable_omnichannel_auto_close_abandoned_rooms": "訪問者が放棄した部屋の自動閉鎖を有効にする", + "Enter_a_custom_message": "カスタムメッセージを入力してください", "Enterprise_License": "エンタープライズライセンス", "Enterprise_License_Description": "ワークスペースが登録されており、ライセンスがRocket.Chat Cloudによって提供されている場合は、ここでライセンスを手動で更新する必要はありません。", + "Estimated_due_time": "見積もられた納期(分単位の時間)", "Failed_to_add_monitor": "モニターを追加できませんでした", "Invalid Canned Response": "無効な返信定型文", "Invalid_Department": "部門が無効です", @@ -22,6 +31,9 @@ "LDAP_Roles_To_Rocket_Chat_Roles_Description": "オブジェクトキーがLDAPロールであり、オブジェクト値がRCロールの配列である必要があるオブジェクト形式のロールマッピング。例:{ 'ldapRole': ['rcRole', 'anotherRCRole'] }", "LDAP_Validate_Roles_For_Each_Login": "各ログインのマッピングを検証する", "LDAP_Validate_Roles_For_Each_Login_Description": "各ログインで検証を行う必要がある場合(この設定は各ログインでユーザーロールを上書きするため、注意してください。そうでない場合、ユーザーの作成時にのみ検証されます)。", + "List_of_departments_for_forward": "転送が許可されている部門のリスト(オプション)", + "List_of_departments_for_forward_description": "この部門からチャットを受信できる部門の制限リストを設定することを許可します", + "Livechat_abandoned_rooms_closed_custom_message": "訪問者の非アクティブによって部屋が自動的に閉じられたときのカスタムメッセージ", "Livechat_Monitors": "モニター", "Livechat_monitors": "Livechatモニター", "Max_number_of_chats_per_agent": "最大同時チャットの数", @@ -32,13 +44,19 @@ "Monitor_removed": "モニターを取り外しました", "Monitors": "モニター", "New_Canned_Response": "新しい返信定型文", + "New_chat_priority": "優先度の変更:__user__が優先度を__priority__に変更しました", "New_Tag": "新しいタグ", "New_Unit": "新しいユニット", + "New_Priority": "新しい優先度", "No_Canned_Responses": "返信定型なし", + "Number_in_seconds": "秒数", "Number_of_most_recent_chats_estimate_wait_time": "推定待ち時間を計算するための最近のチャットの数", "Number_of_most_recent_chats_estimate_wait_time_description": "この数は、キュー待機時間の計算に使用される最後に提供された部屋の数を定義します。", "Others": "その他", "Please_select_visibility": "可視性を選択してください", + "Priorities": "優先度", + "Priority": "優先度", + "Priority_removed": "優先度を削除しました", "Role_Mapping": "ロールマッピング", "Selected_departments": "選択された部門", "Selected_monitors": "選択されたモニター", @@ -58,5 +76,6 @@ "view-livechat-unit": "Livechatユニットを表示", "Waiting_queue": "待ち行列", "Waiting_queue_message": "待機キューメッセージ", - "Waiting_queue_message_description": "訪問者がキューに入ったときに表示されるメッセージ" + "Waiting_queue_message_description": "訪問者がキューに入ったときに表示されるメッセージ", + "Without_priority": "優先権なし" } \ No newline at end of file diff --git a/ee/i18n/lv.i18n.json b/ee/i18n/lv.i18n.json index 6f31cf5a2e62..2a50b65db1dc 100644 --- a/ee/i18n/lv.i18n.json +++ b/ee/i18n/lv.i18n.json @@ -1 +1,27 @@ -{ } \ No newline at end of file +{ + "error-canned-response-not-found": "Konteinerota atbilde nav atrasta", + "error-forwarding-department-target-not-allowed": "Pārsūtīšana mērķa (saņēmēja) departamentam nav atļauta.", + "error-guests-cant-have-other-roles": "Vieslietotājiem nevar būt citas lomas.", + "error-invalid-priority": "Nederīga prioritāte", + "error-max-guests-number-reached": "Jūs esat sasniedzis maksimālo vieslietotāju skaitu, ko atļauj jūsu licence. Lai iegūtu jaunu licenci, sazinieties ar sale@rocket.chat.", + "error-max-number-simultaneous-chats-reached": "Ir sasniegts maksimālais vienlaicīgo tērziņu skaits vienam aģentam.", + "Add_monitor": "Pievienot monitoru/pieskatītāju", + "Available_departments": "Pieejamie departamenti", + "Canned Responses": "Konteinerotās atbildes", + "Canned_Response_Removed": "Konteinerotās atbildes noņemtas", + "Canned_Responses_Enable": "Iespējot konteinerotās atbildes", + "Closed_automatically": "Sistēmas automātiski slēgts", + "Edit_Tag": "Rediģēt birku", + "Edit_Unit": "Rediģēt vienumu", + "Edit_Priority": "Rediģēt prioritāti", + "Enable_omnichannel_auto_close_abandoned_rooms": "Iespējot apmeklētāju pamestu istabu automātisku aizvēršanu", + "Enter_a_custom_message": "Ievadīt pielāgotu ziņojumu", + "Enterprise_License": "Uzņēmuma licence", + "Enterprise_License_Description": "Ja jūsu darbvieta ir reģistrēta un licenci nodrošina Rocket.Chat Mākulis, jums šeit licence nav manuāli jāaktualalizē.", + "Estimated_due_time": "Paredzamais izpildes laiks (laiks minūtēs)", + "Failed_to_add_monitor": "Neizdevās pievienot monitoru/pieskatītāju", + "Invalid Canned Response": "Nederīga konteinerotā atbilde", + "Invalid_Department": "Nederīgs departaments", + "LDAP_Default_Role_To_User": "Lietotāja loma pēc noklusējuma", + "LDAP_Default_Role_To_User_Description": "Noklusētā RC loma, kas tiks piešķirta lietotājam, ja lietotājam ir kāda LDAP loma, kas nav kartēta." +} \ No newline at end of file diff --git a/ee/i18n/pt-BR.i18n.json b/ee/i18n/pt-BR.i18n.json index 74b62af4ea61..892daf29eed0 100644 --- a/ee/i18n/pt-BR.i18n.json +++ b/ee/i18n/pt-BR.i18n.json @@ -1,4 +1,6 @@ { + "error-forwarding-department-target-not-allowed": "O encaminhamento para o departamento selecionado não é permitido.", + "error-invalid-priority": "Prioridade inválida", "error-max-number-simultaneous-chats-reached": "O número máximo de bate-papos simultâneos por agente foi atingido.", "Add_monitor": "Adicionar Monitor", "Available_departments": "Departamentos disponíveis", @@ -10,8 +12,6 @@ "Enter_a_custom_message": "Digite uma mensagem customizada", "Enterprise_License": "Licença Enterprise", "Enterprise_License_Description": "Se você registrou seu workspace e a licença foi fornecida pelo Rocket.Chat Cloud você não precisa atualizar a licença manualmente aqui.", - "error-forwarding-department-target-not-allowed": "O encaminhamento para o departamento selecionado não é permitido.", - "error-invalid-priority": "Prioridade inválida", "Estimated_due_time": "Tempo estimado(tempo em minutos)", "Invalid_Department": "Departamento inválido", "LDAP_Default_Role_To_User": "Papel padrão para o usuário", diff --git a/ee/i18n/zh-TW.i18n.json b/ee/i18n/zh-TW.i18n.json index 735bb240b6d5..ac5a203fba9d 100644 --- a/ee/i18n/zh-TW.i18n.json +++ b/ee/i18n/zh-TW.i18n.json @@ -1,5 +1,9 @@ { "error-canned-response-not-found": "找不到罐頭訊息", + "error-forwarding-department-target-not-allowed": "不允許轉送到目標部門。", + "error-guests-cant-have-other-roles": "訪客使用者不具有任何其他角色。", + "error-invalid-priority": "無效的優先權", + "error-max-guests-number-reached": "您已達到授權允許的最大訪客使用者數量。請與 sale@rocket.chat 聯絡以取得新的授權。", "error-max-number-simultaneous-chats-reached": "已達到每個代理的最大同時聊天數。", "Add_monitor": "新增監控", "Available_departments": "可用的部門", @@ -9,10 +13,12 @@ "Closed_automatically": "系統自動關閉", "Edit_Tag": "編輯標籤", "Edit_Unit": "編輯單位", + "Edit_Priority": "編輯優先權", "Enable_omnichannel_auto_close_abandoned_rooms": "啟用自動關閉來處理訪客遺棄的房間", "Enter_a_custom_message": "輸入自訂訊息", "Enterprise_License": "企業授權", "Enterprise_License_Description": "如果您的工作區已註冊,並且授權是由Rocket.Chat Cloud提供的,則無需在此處手動更新授權。", + "Estimated_due_time": "預計到期時間(以分鐘為單位)", "Failed_to_add_monitor": "新增監控失敗", "Invalid Canned Response": "無效的罐頭訊息", "Invalid_Department": "無效的部門", @@ -25,6 +31,8 @@ "LDAP_Roles_To_Rocket_Chat_Roles_Description": "對象格式的對應身份,其中對象鍵必須是 LDAP 身份,並且對象值必須是 RC 身份群組。例如:{ 'ldapRole': ['rcRole', 'anotherRCRole'] }", "LDAP_Validate_Roles_For_Each_Login": "驗證每個登入名的對應", "LDAP_Validate_Roles_For_Each_Login_Description": "如果每次登入都要進行驗證(請謹慎使用此設定,因為它將覆寫每次登入中的使用者身份,否則僅在建立使用者時進行驗證)。", + "List_of_departments_for_forward": "允許轉送的部門列表(可選)", + "List_of_departments_for_forward_description": "允許設定可以接收從此部門聊天記錄部門的受限列表", "Livechat_abandoned_rooms_closed_custom_message": "當訪客不動作時自動關閉房間的自訂訊息", "Livechat_Monitors": "監控", "Livechat_monitors": "即時聊天監控", @@ -36,14 +44,19 @@ "Monitor_removed": "監控已移除", "Monitors": "監控", "New_Canned_Response": "新的罐頭訊息", + "New_chat_priority": "優先權已更改:__user__將優先權更改為__priority__", "New_Tag": "新的標籤", "New_Unit": "新的單位", + "New_Priority": "新優先權", "No_Canned_Responses": "沒有罐頭訊息", "Number_in_seconds": "秒數", "Number_of_most_recent_chats_estimate_wait_time": "最近聊天的次數以計算預估的等待時間", "Number_of_most_recent_chats_estimate_wait_time_description": "此數字定義將用於計算隊列等待時間的最後服務房間數。", "Others": "其他", "Please_select_visibility": "請選擇可視度", + "Priorities": "優先", + "Priority": "優先權", + "Priority_removed": "優先權已刪除", "Role_Mapping": "對應身份", "Selected_departments": "已選擇的部門", "Selected_monitors": "已選擇的監控", @@ -63,5 +76,6 @@ "view-livechat-unit": "檢視即時聊天單位", "Waiting_queue": "等待佇列", "Waiting_queue_message": "等待佇列訊息", - "Waiting_queue_message_description": "訪客進入佇列時將顯示給他們的訊息" + "Waiting_queue_message_description": "訪客進入佇列時將顯示給他們的訊息", + "Without_priority": "沒有優先權" } \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/af.i18n.json b/packages/rocketchat-i18n/i18n/af.i18n.json index c1898107a0ea..854bd29d86d8 100644 --- a/packages/rocketchat-i18n/i18n/af.i18n.json +++ b/packages/rocketchat-i18n/i18n/af.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Kennisgewing Tydsduur", "Notification_Mobile_Default_For": "Druk mobiele kennisgewings vir", "Notifications": "Kennisgewings", - "Notifications_Always_Notify_Mobile": "Meld altyd selfoon aan", - "Notifications_Always_Notify_Mobile_Description": "Kies om altyd die mobiele toestel in kennis te stel, ongeag die status van die teenwoordigheid.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Max Kamer Lede voordat alle boodskap kennisgewings gedeaktiveer word", "Notifications_Max_Room_Members_Description": "Maksimum aantal lede in die kamer wanneer kennisgewings vir alle boodskappe afgeskakel word. Gebruikers kan steeds per kamerinstelling verander om alle kennisgewings op individuele basis te ontvang. (0 om uit te skakel)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Dev wagwoordfrase", "Push_apn_key": "APN sleutel", "Push_apn_passphrase": "APN wagwoordfrase", - "Push_debug": "debug", "Push_enable": "in staat te stel", "Push_enable_gateway": "Aktiveer Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/ar.i18n.json b/packages/rocketchat-i18n/i18n/ar.i18n.json index 7eb8a15f4700..0dffa637a952 100644 --- a/packages/rocketchat-i18n/i18n/ar.i18n.json +++ b/packages/rocketchat-i18n/i18n/ar.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "مدة التنبيهات", "Notification_Mobile_Default_For": "دفع الإخطارات موبايل ل", "Notifications": "الإشعارات", - "Notifications_Always_Notify_Mobile": "إعلام الجوال دائما", - "Notifications_Always_Notify_Mobile_Description": "اختر أن تخطر دائما الجهاز المحمول بغض النظر عن حالة التواجد.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "أقصى أعضاء الغرفة قبل تعطيل جميع الإخطارات رسالة", "Notifications_Max_Room_Members_Description": "الحد الأقصى لعدد الأعضاء في الغرفة عند تعطيل الإشعارات لجميع الرسائل. يمكن للمستخدمين لا يزال تغيير في إعداد غرفة لتلقي جميع الإخطارات على أساس فردي. (0 لتعطيل)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "عبارة مرور المطور APN", "Push_apn_key": "مفتاح APN", "Push_apn_passphrase": "عبارة المرور APN", - "Push_debug": "التصحيح", "Push_enable": "تمكين", "Push_enable_gateway": "تمكين بوابة", "Push_gateway": "بوابة", diff --git a/packages/rocketchat-i18n/i18n/az.i18n.json b/packages/rocketchat-i18n/i18n/az.i18n.json index dc3e71a6462c..3787cfa58b0a 100644 --- a/packages/rocketchat-i18n/i18n/az.i18n.json +++ b/packages/rocketchat-i18n/i18n/az.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Bildiriş müddəti", "Notification_Mobile_Default_For": "Üçün Mobil Uyarıları Push", "Notifications": "Bildirişlər", - "Notifications_Always_Notify_Mobile": "Həmişə mobil xəbərdarlıq edin", - "Notifications_Always_Notify_Mobile_Description": "Mövcud statusdan asılı olmayaraq həmişə mobil cihazlara xəbərdarlıq etmək üçün seçin.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Bütün mesaj bildirişlərini aradan qaldırmadan əvvəl Max Otaq üzvləri", "Notifications_Max_Room_Members_Description": "Bütün mesajlar üçün bildirişlər çıxdıqda otaqda maksimum üzv sayı. İstifadəçilər fərdi şəkildə bütün bildirişləri qəbul etmək üçün hələ otaq qəbulu üçün dəyişə bilərlər. (Aradan qaldırılması üçün 0)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Açarı", "Push_apn_passphrase": "APN Parolası", - "Push_debug": "Debug", "Push_enable": "Enable", "Push_enable_gateway": "Gateway'i aktiv edin", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/be-BY.i18n.json b/packages/rocketchat-i18n/i18n/be-BY.i18n.json index 5f5ba55007fc..2cd769bc6292 100644 --- a/packages/rocketchat-i18n/i18n/be-BY.i18n.json +++ b/packages/rocketchat-i18n/i18n/be-BY.i18n.json @@ -1900,8 +1900,6 @@ "Notification_Duration": "апавяшчэнне Працягласць", "Notification_Mobile_Default_For": "Націсніце апавяшчэння для мабільных", "Notifications": "апавяшчэння", - "Notifications_Always_Notify_Mobile": "Заўсёды апавяшчаць мабільны", - "Notifications_Always_Notify_Mobile_Description": "Выберыце заўсёды апавяшчаць мабільнае прылада, незалежна ад статусу прысутнасці.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Кол-у нумароў карыстальнікаў Перад Адключэнне ўсіх паведамленняў апавяшчэння", "Notifications_Max_Room_Members_Description": "Максімальную колькасць удзельнікаў у пакоі, калі паведамленьняў ва ўсіх паведамленняў атрымлівае інвалід. Карыстальнікі па-ранейшаму могуць змяняцца ў залежнасці ад налады нумары, каб атрымаць усе апавяшчэння на індывідуальнай аснове. (0 для адключэння)", @@ -2070,7 +2068,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "адладжваць", "Push_enable": "Ўключыць", "Push_enable_gateway": "ўключыць шлюз", "Push_gateway": "шлюз", diff --git a/packages/rocketchat-i18n/i18n/bg.i18n.json b/packages/rocketchat-i18n/i18n/bg.i18n.json index 77643dbd88a6..d155af3974dc 100644 --- a/packages/rocketchat-i18n/i18n/bg.i18n.json +++ b/packages/rocketchat-i18n/i18n/bg.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Продължителност на известяването", "Notification_Mobile_Default_For": "Натиснете за мобилни съобщения за", "Notifications": "Известия", - "Notifications_Always_Notify_Mobile": "Винаги съобщавайте за мобилни устройства", - "Notifications_Always_Notify_Mobile_Description": "Изберете винаги да уведомявате мобилното устройство, независимо от състоянието му.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Членовете на Max Room преди да деактивират всички съобщения за известия", "Notifications_Max_Room_Members_Description": "Максимален брой членове в стаята, когато известията за всички съобщения са деактивирани. Потребителите все още могат да променят настройките за стая, за да получават всички известия на индивидуална основа. (0 за деактивиране)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN ключ", "Push_apn_passphrase": "Парола за APN", - "Push_debug": "Debug", "Push_enable": "Активиране", "Push_enable_gateway": "Активиране на портала", "Push_gateway": "врата", diff --git a/packages/rocketchat-i18n/i18n/bs.i18n.json b/packages/rocketchat-i18n/i18n/bs.i18n.json index 7daa731ed5fa..c5dbb1d3b252 100644 --- a/packages/rocketchat-i18n/i18n/bs.i18n.json +++ b/packages/rocketchat-i18n/i18n/bs.i18n.json @@ -1885,8 +1885,6 @@ "Notification_Duration": "Trajanje obavijesti", "Notification_Mobile_Default_For": "Push mobilnih obavijesti za", "Notifications": "Obavijesti", - "Notifications_Always_Notify_Mobile": "Uvijek obavijesti mobitel", - "Notifications_Always_Notify_Mobile_Description": "Odaberite uvijek obavijestite mobilni uređaj bez obzira na status prisutnosti.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Članovi Max soba prije onemogućavanja svih obavijesti o poruci", "Notifications_Max_Room_Members_Description": "Maksimalni broj članova u sobi kada se obavijesti o svim porukama onemogućuju. Korisnici se i dalje mogu mijenjati po postavkama sobe kako bi primile sve obavijesti na pojedinačnoj osnovi. (0 za onemogućavanje)", @@ -2054,7 +2052,6 @@ "Push_apn_dev_passphrase": "APN Dev zaporka", "Push_apn_key": "APN Ključ", "Push_apn_passphrase": "APN zaporka", - "Push_debug": "Debu", "Push_enable": "Omogući", "Push_enable_gateway": "Omogući Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/ca.i18n.json b/packages/rocketchat-i18n/i18n/ca.i18n.json index e754098f4026..5f5528d5c9bd 100644 --- a/packages/rocketchat-i18n/i18n/ca.i18n.json +++ b/packages/rocketchat-i18n/i18n/ca.i18n.json @@ -2037,8 +2037,6 @@ "Notification_Duration": "Duració de la notificació", "Notification_Mobile_Default_For": "Notificacions push mòbil per", "Notifications": "Notificacions", - "Notifications_Always_Notify_Mobile": "Notifiqueu sempre a mòbils", - "Notifications_Always_Notify_Mobile_Description": "Tria sempre notificar el dispositiu mòbil independentment de l'estat de la presència.", "Notifications_Duration": "Notificacions_duració", "Notifications_Max_Room_Members": "Membres de la sala Max Abans de desactivar totes les notificacions de missatges", "Notifications_Max_Room_Members_Description": "Nombre màxim de membres a l'habitació quan es desactiven les notificacions de tots els missatges. Els usuaris encara poden canviar la configuració de l'habitació per rebre totes les notificacions de manera individual. (0 per deshabilitar)", @@ -2206,7 +2204,6 @@ "Push_apn_dev_passphrase": "Contrasenya APN Dev (Passphrase)", "Push_apn_key": "Clau APN (Key)", "Push_apn_passphrase": "Contrasenya APN (Passphrase)", - "Push_debug": "Depura (Debug)", "Push_enable": "Activa", "Push_enable_gateway": "Activa porta d'enllaç", "Push_gateway": "Porta d'enllaç", diff --git a/packages/rocketchat-i18n/i18n/cs.i18n.json b/packages/rocketchat-i18n/i18n/cs.i18n.json index 1f6153096e61..6d4c48da78ff 100644 --- a/packages/rocketchat-i18n/i18n/cs.i18n.json +++ b/packages/rocketchat-i18n/i18n/cs.i18n.json @@ -2490,8 +2490,6 @@ "Notification_RequireInteraction_Description": "Funguje pouze v prohlížeči Chrome verze >50. Využívá parametru requireInteraction, aby se oznámení na ploše zobrazovalo nepřetržitě dokud jej uživatel neoznačí jako přečtené.", "Notification_Mobile_Default_For": "Zasílat mobilní notifikace", "Notifications": "Oznámení", - "Notifications_Always_Notify_Mobile": "Vždy notifikovat na mobil", - "Notifications_Always_Notify_Mobile_Description": "Pokud tuto volbu povolíte, budete vždy notifikování na mobilu nehledě na aktivitu jinde.", "Notifications_Duration": "Délka upozornění", "Notifications_Max_Room_Members": "Maximální počet uživatelů v místnosti pro zakázání zmínky všech", "Notifications_Max_Room_Members_Description": "Pokud počet uživatelů v místnosti překročí zadané číslo, notifikace všech (@all) nebude povolena. Uživatelé si toto nastavení mohou individuálně změnit. (0 možnost zakáže)", @@ -2700,9 +2698,6 @@ "Push_apn_dev_passphrase": "APN DEV heslo", "Push_apn_key": "APN Klíč", "Push_apn_passphrase": "APN heslo", - "Push_debug": "Test", - "Push_send_interval": "Interval pro kontrolu fronty nových push notifikací", - "Push_send_batch_size": "Velikost dávky, která se má zpracovat při každém běhu", "Push_enable": "Povolit", "Push_enable_gateway": "Brána povolena", "Push_gateway": "Brána", diff --git a/packages/rocketchat-i18n/i18n/cy.i18n.json b/packages/rocketchat-i18n/i18n/cy.i18n.json index e94beee1aaf7..e303828f9fe6 100644 --- a/packages/rocketchat-i18n/i18n/cy.i18n.json +++ b/packages/rocketchat-i18n/i18n/cy.i18n.json @@ -1884,8 +1884,6 @@ "Notification_Duration": "Hyd Hysbysiad", "Notification_Mobile_Default_For": "Gwthio Hysbysiadau Symudol I", "Notifications": "Hysbysiadau", - "Notifications_Always_Notify_Mobile": "Rhowch wybod i symudol bob tro", - "Notifications_Always_Notify_Mobile_Description": "Dewiswch bob amser hysbysu dyfais symudol waeth beth fo statws presenoldeb.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Aelodau'r Ystafell Fawr Cyn Analluogi Pob Hysbysiad Neges", "Notifications_Max_Room_Members_Description": "Nifer o aelodau yn yr ystafell pan fydd hysbysiadau ar gyfer pob neges yn cael eu hanabledd. Gall defnyddwyr barhau i newid fesul ystafell i dderbyn pob hysbysiad yn unigol. (0 i analluogi)", @@ -2053,7 +2051,6 @@ "Push_apn_dev_passphrase": "Cyfrinair Dyfais APN", "Push_apn_key": "Allwedd APN", "Push_apn_passphrase": "Cyfrinair APN", - "Push_debug": "Diddymu", "Push_enable": "Galluogi", "Push_enable_gateway": "Galluogi Porth", "Push_gateway": "Porth", diff --git a/packages/rocketchat-i18n/i18n/da.i18n.json b/packages/rocketchat-i18n/i18n/da.i18n.json index 04e7de148363..8e2181aa273e 100644 --- a/packages/rocketchat-i18n/i18n/da.i18n.json +++ b/packages/rocketchat-i18n/i18n/da.i18n.json @@ -99,6 +99,7 @@ "Accounts_OAuth_Custom_Token_Path": "Tokensti", "Accounts_OAuth_Custom_Token_Sent_Via": "Token sendt via", "Accounts_OAuth_Custom_Username_Field": "Brugernavnefelt", + "Accounts_OAuth_Custom_Email_Field": "Felt til e-mail", "Accounts_OAuth_Custom_Name_Field": "Navnefelt", "Accounts_OAuth_Custom_Roles_Claim": "Feltnavn for Roller/Grupper", "Accounts_OAuth_Custom_Merge_Roles": "Flet roller fra SSO", @@ -195,12 +196,12 @@ "Accounts_Registration_InviteUrlType": "Typen af invitations-URL", "Accounts_Registration_InviteUrlType_Direct": "Direkte", "Accounts_Registration_InviteUrlType_Proxy": "Proxy", - "Accounts_RequireNameForSignUp": "Navn er obligatorisk for tilmelding", + "Accounts_RequireNameForSignUp": "Navn er påkrævet for tilmelding", "Accounts_RequirePasswordConfirmation": "Kræv kodeordsbekræftelse", - "Accounts_SearchFields": "Felter, der skal medtages i søgning", + "Accounts_SearchFields": "Felter der skal medtages i søgning", "Accounts_Send_Email_When_Activating": "Send en e-mail til brugeren, når brugeren er aktiveret", "Accounts_Send_Email_When_Deactivating": "Send en e-mail til brugeren, når brugeren er deaktiveret", - "Accounts_Directory_DefaultView": "Standardvisning for vejviseren", + "Accounts_Directory_DefaultView": "Standardvisning for visning af folder", "Accounts_SetDefaultAvatar": "Vælg standardavatar", "Accounts_SetDefaultAvatar_Description": "Forsøger at finde en standardavatar baseret på OAuth-konto eller Gravatar", "Accounts_Set_Email_Of_External_Accounts_as_Verified": "Sæt e-mail for ekstern konto som verificeret", @@ -211,18 +212,18 @@ "Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In": "Tilmeld nye brugere automatisk til to-faktor via e-mail", "Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In_Description": "Nye brugere har som standard to-faktor-godkendelse via e-mail aktiveret. De vil være i stand til at deaktivere det på deres profilside.", "Accounts_TwoFactorAuthentication_By_Email_Code_Expiration": "Udløbstid i sekunder for koden sendt via e-mail", - "Accounts_TwoFactorAuthentication_Enabled": "Aktivér tofaktorgodkendelse", + "Accounts_TwoFactorAuthentication_Enabled": "Aktivér tofaktorgodkendelse vha. TOTP", "Accounts_TwoFactorAuthentication_Enabled_Description": "Brugere kan konfigurere deres to-faktor-godkendelse ved hjælp af en hvilken som helst TOTP-app, f.eks. Google Authenticator eller Authy", "Accounts_TwoFactorAuthentication_MaxDelta": "Maksimalt delta", - "Accounts_UserAddedEmail_Default": "

Velkommen til [Site_Name]

Gå til [Site_URL] for at prøve den bedste open source-chatløsning, du kan få!

Du kan logge ind med din e-mail: [email] og kodeordet: [password]. Du skal måske ændre det efter dit første login.", - "Accounts_TwoFactorAuthentication_MaxDelta_Description": "Det maksimale delta afgør, hvor mange tokens der er gyldige på et givent tidspunkt. Tokens genereres hvert 30. sekund og er gyldige i (30 * maksimalt delta) sekunder.
Eksempel: Med et maksimalt delta på 10 kan hvert tegn bruges op til 300 sekunder før eller efter sit tidsstempel. Dette er nyttigt, når klientens ur ikke er korrekt synkroniseret med serveren.", + "Accounts_UserAddedEmail_Default": "

Velkommen til [Site_Name]

Gå til [Site_URL] for at prøve den bedste open source-chatløsning man kan få!

Du kan logge ind med din e-mail: [email] og kodeordet: [password]. Du skal måske ændre det efter dit første login.", + "Accounts_TwoFactorAuthentication_MaxDelta_Description": "Det maksimale delta afgør, hvor mange tokens der er gyldige på et givent tidspunkt. Tokens genereres hvert 30. sekund og er gyldige i (30 * maksimalt delta) sekunder.
Eksempel: Med et maksimalt delta på 10 kan hvert tegn bruges op til 300 sekunder før eller efter sit tidsstempel. Dette er nyttigt når klientens tid ikke er korrekt synkroniseret med serveren.", "Accounts_TwoFactorAuthentication_RememberFor": "Husk to-faktor i (sekunder)", "Accounts_TwoFactorAuthentication_RememberFor_Description": "Anmod ikke om to-faktor-godkendelses-kode, hvis den allerede blev leveret før det givne tidspunkt.", "Accounts_UseDefaultBlockedDomainsList": "Brug en standardliste over blokerede domæner", "Accounts_UseDNSDomainCheck": "Brug DNS-domænetjek", "Accounts_UserAddedEmailSubject_Default": "Du er blevet føjet til [Site_Name]", - "Accounts_UserAddedEmail_Description": "Du kan bruge følgende pladsholdere:

  • [navn], [fname] eller [lname] for brugerens fulde navn, fornavn eller efternavn.
  • [email] for brugerens e-mail.
  • [password] for brugerens adgangskode.
  • [Site_Name] og [Site_URL] for henholdsvis applikationsnavn og url.
", - "Activate": "Aktiver", + "Accounts_UserAddedEmail_Description": "Du kan bruge følgende felter:
  • [navn], [fname] eller [lname] for brugerens fulde navn, fornavn eller efternavn.
  • [email] for brugerens e-mail.
  • [password] for brugerens adgangskode.
  • [Site_Name] og [Site_URL] for henholdsvis applikationsnavn og URL.
", + "Activate": "Aktivér", "Active_users": "Aktive brugere", "Daily_Active_Users": "Aktive brugere dagligt", "Weekly_Active_Users": "Aktive brugere ugentligt", @@ -236,12 +237,12 @@ "add-oauth-service_description": "Tilladelse til at tilføje nye OAuth-tjenester", "add-user": "Tilføj bruger", "add-user-to-any-c-room": "Tilføj bruger til enhver offentlig kanal", - "add-user-to-any-c-room_description": "Tilladelse til at føje brugere til offentlige kanaler", + "add-user-to-any-c-room_description": "Tilladelse til at tilføje brugere til offentlige kanaler", "add-user-to-any-p-room": "Tilføj bruger til enhver privat kanal", - "add-user-to-any-p-room_description": "Tilladelse til at føje brugere til private kanaler", - "add-user-to-joined-room": "Tilføj bruger til enhver kanal, man er med i", - "add-user-to-joined-room_description": "Tilladelse til at føje brugere til kanaler, man er med i", - "add-user_description": "Tilladelse til at føje nye brugere til serveren via brugere-menuen", + "add-user-to-any-p-room_description": "Tilladelse til at tilføje brugere til private kanaler", + "add-user-to-joined-room": "Tilføj bruger til enhver kanal man er med i", + "add-user-to-joined-room_description": "Tilladelse til at tilføje brugere til kanaler man er med i", + "add-user_description": "Tilladelse til at tilføje nye brugere til serveren via brugere-menuen", "Add_agent": "Tilføj agent", "Add_custom_oauth": "Tilføj brugerdefineret OAuth", "Add_Domain": "Tilføj domæne", @@ -252,6 +253,7 @@ "Add_user": "Tilføj bruger", "Add_User": "Tilføj bruger", "Add_users": "Tilføj brugere", + "Add_Reaction": "Tilføj reaktion", "Adding_OAuth_Services": "Tilføjelse af OAuth-tjenester", "Adding_permission": "Tilføjelse af tilladelse", "Adding_user": "Tilføjelse af bruger", @@ -264,8 +266,10 @@ "Administration": "Administration", "Adult_images_are_not_allowed": "Voksenbilleder er ikke tilladt", "Advocacy": "Støtte", - "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Efter OAuth2-godkendelse bliver brugerne sendt til denne url", + "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Efter OAuth2-godkendelse bliver brugerne sendt til en URL på denne liste. Du kan tilføje en URL pr. linje.", "Agent": "Agent", + "Agent_Info": "Agent-info", + "Agents": "Agenter", "Agent_added": "Agent tilføjet", "Agent_removed": "Agent fjernet", "Alerts": "Advarsler", @@ -326,6 +330,13 @@ "API_Enable_CORS": "Aktivér CORS", "API_Enable_Direct_Message_History_EndPoint": "Aktivér slutpunkt for direkte beskedhistorik", "API_Enable_Direct_Message_History_EndPoint_Description": "Dette aktiverer `/api/v1/im.history.others`, som gør det muligt at se direkte beskeder i samtaler, man ikke har været del af.", + "API_Enable_Rate_Limiter": "Aktivér rate-begrænser", + "API_Enable_Rate_Limiter_Dev": "Aktivér rate-begrænser under udvikling", + "API_Enable_Rate_Limiter_Dev_Description": "Bør der begrænses antallet af opkald til slutpunkterne i udviklingsmiljøet?", + "API_Enable_Rate_Limiter_Limit_Calls_Default": "Standard antal kald til rate-begrænseren", + "API_Enable_Rate_Limiter_Limit_Calls_Default_Description": "Antal standardopkald for hvert slutpunkt i REST API der er tilladt inden for det tidsinterval, der er defineret nedenfor", + "API_Enable_Rate_Limiter_Limit_Time_Default": "Standard tidsgrænse for rate-begrænseren (i ms)", + "API_Enable_Rate_Limiter_Limit_Time_Default_Description": "Standard timeout for at begrænse antallet af opkald på hvert slutpunkt i REST API (i ms)", "API_Enable_Personal_Access_Tokens": "Aktivér personlige adgangs-tokens for REST API", "API_Enable_Personal_Access_Tokens_Description": "Aktivér brugen af personlige adgangs-tokens i REST API'en", "API_Enable_Shields": "Aktivér skjold", @@ -342,11 +353,14 @@ "API_Personal_Access_Tokens_Regenerate_It": "Regenerér token", "API_Shield_Types": "Skjoldtyper", "API_Shield_Types_Description": "Kommasepareret liste med de typer skjold, der skal aktiveres. Vælg enten `online`,`kanal` eller `*` for at aktivere alle", + "API_Shield_user_require_auth": "Kræv godkendelse for brugerbeskyttelsen", "API_Token": "API-token", "API_Tokenpass_URL": "Tokenpass-serverens url", "API_Tokenpass_URL_Description": "Eksempel: https://domain.com (ingen efterfølgende skråstreg)", "API_Upper_Count_Limit": "Maks. antal resultater", "API_Upper_Count_Limit_Description": "Hvad er det maksimale antal poster, som REST API skal returnere (når det ikke er ubegrænset)?", + "API_Use_REST_For_DDP_Calls": "Brug REST i stedet for websocket til Meteor-kald", + "API_Use_REST_For_DDP_Calls_Alert": "Dette er en eksperimentel og midlertidig funktion. Det tvinger webklient og mobilapp til at bruge REST-anmodninger i stedet for at bruge websockets til Meteor-metode-kald.", "API_User_Limit": "Brugergrænse for at føje alle brugere til en kanal", "API_Wordpress_URL": "WordPress-url", "Apiai_Key": "Api.ai-nøgle", @@ -361,11 +375,15 @@ "App_status_disabled": "Deaktiveret", "App_status_error_disabled": "Deaktiveret: Uncaught Error", "App_status_initialized": "initialiseret", + "App_status_invalid_license_disabled": "Deaktiveret: Ugyldig licens", "App_status_invalid_settings_disabled": "Deaktiveret: Konfiguration påkrævet", "App_status_manually_disabled": "Deaktiveret: Manuelt", "App_status_manually_enabled": "Aktiveret", "App_status_unknown": "Ukendt", "App_support_url": "support-url", + "App_Url_to_Install_From": "Installér fra URL", + "App_Url_to_Install_From_File": "Installér fra fil", + "App_user_not_allowed_to_login": "App-brugere må ikke logge direkte på.", "Appearance": "Udseende", "Application_added": "Applikation tilføjet", "Application_Name": "Applikationens navn", @@ -373,12 +391,32 @@ "Apply": "Anvend", "Apply_and_refresh_all_clients": "Anvend og opdatér alle klienter", "Apps": "Apps", + "Apps_Engine_Version": "Apps Engine Version", + "Apps_Framework_Development_Mode": "Aktivér udviklingstilstand", + "Apps_Framework_Development_Mode_Description": "Udviklingstilstand tillader installation af apps der ikke er fra Rocket.Chat's markedsplads.", "Apps_Framework_enabled": "Aktivér app-framework", + "Apps_Game_Center": "Spilcenter", + "Apps_Game_Center_Back": "Tilbage til spilcenteret", + "Apps_Game_Center_enabled": "Aktivér spilcenter", + "Apps_Game_Center_Play_Game_Together": "@here Let's play __name__ together!", + "Apps_Game_Center_Invite_Friends": "Invitér dine venner til at deltage", + "Apps_Marketplace_Deactivate_App_Prompt": "Vil du virkelig deaktivere denne app?", + "Apps_Marketplace_Modify_App_Subscription": "Redigér abonnement", + "Apps_Marketplace_Uninstall_App_Prompt": "Vil du virkelig afinstallere denne app?", + "Apps_Marketplace_Uninstall_Subscribed_App_Anyway": "Afinstaller det alligevel", + "Apps_Marketplace_Uninstall_Subscribed_App_Prompt": "Denne app har et aktivt abonnement og afinstallation annullerer den ikke. Hvis du gerne vil gøre det, skal du ændre dit abonnement, før du afinstallerer.", + "Apps_Marketplace_Login_Required_Title": "Markedsplads-login påkræves", + "Apps_Marketplace_Login_Required_Description": "For at købe apps fra Rocket.Chat markedsplads, skal du registrere dit site og logge ind.", + "Apps_Marketplace_pricingPlan_monthly": "__price__ / måned", + "Apps_Marketplace_pricingPlan_monthly_perUser": "__price__ / måned pr. bruger", + "Apps_Marketplace_pricingPlan_yearly": "__price__ / år", + "Apps_Marketplace_pricingPlan_yearly_perUser": "__price__ / år pr. bruger", "Apps_Settings": "Appens indstillinger", + "Apps_User_Already_Exists": "Brugernavnet \"__username__\" bruges allerede. Omdøb eller fjern brugeren, der bruges til at installere denne app", "Apps_WhatIsIt": "Apps: Hvad er det?", "Apps_WhatIsIt_paragraph1": "Et nyt ikon i administrationsområdet! Hvad betyder det, og hvad er apps?", "Apps_WhatIsIt_paragraph2": "Først og fremmest refererer apps i denne sammenhæng ikke til mobilapplikationer. Det er faktisk bedst at tænke på dem som plugins eller avancerede integrationer.", - "Apps_WhatIsIt_paragraph3": "De er dynamiske scripts eller pakker, som lader dig tilpasse din Rocket.Chat-instans uden at forke kodebasen. Men husk, at det er et nyt funktionssæt, og derfor er det måske ikke 100% stabilt. Vi arbejder også stadig på funktionssættet, så det er ikke alt, der kan tilpasses i skrivende stund. Du kan få mere at vide om, hvordan du kommer i gang med at udvikle en app, her:", + "Apps_WhatIsIt_paragraph3": "For det andet er de dynamiske scripts eller pakker, som giver dig mulighed for at tilpasse din Rocket.Chat-instans uden at opdele kodebasen. Men husk at dette er et nyt funktionssæt og det kan derfor ikke være 100% stabilt. Desuden udvikler vi stadig funktionssættet, så ikke alt kan tilpasses på dette tidspunkt. For mere information om at komme i gang med at udvikle en app, skal du læse her:", "Apps_WhatIsIt_paragraph4": "Når det så er sagt, skal du være velkommen til at prøve systemet ved at trykke på denne knap og aktivere apps-systemet.", "Archive": "Arkiv", "archive-room": "Arkivrum", @@ -388,16 +426,21 @@ "Are_you_sure": "Er du sikker?", "Are_you_sure_you_want_to_delete_your_account": "Er du sikker på, at du vil slette din konto?", "Are_you_sure_you_want_to_disable_Facebook_integration": "Er du sikker på, at du vil deaktivere Facebook-integrationen?", + "Are_you_sure_you_want_to_delete_this_record": "Er du sikker på, at du vil slette denne post?", "Assets": "Aktiver", "assign-admin-role": "Tildel administratorrolle", "assign-admin-role_description": "Tilladelse til at tildele administratorrollen til andre brugere", "Assign_admin": "Tildeling af administrator", + "Assign_new_conversations_to_bot_agent": "Tildel nye samtaler til bot-agent", + "Assign_new_conversations_to_bot_agent_description": "Routingssystemet vil forsøge at finde en bot-agen, før den tildeler nye samtaler til en menneskelig agent.", + "assign-roles": "Tildel roller", "at": "på", "At_least_one_added_token_is_required_by_the_user": "Brugeren skal have tilføjet mindst en token", "AtlassianCrowd": "Atlassian Crowd", "Attachment_File_Uploaded": "Fil uploadet", "Attribute_handling": "Egenskabshåndtering", "Audio": "Lyd", + "Audios": "Lyde", "Audio_message": "Lydbesked", "Audio_Notification_Value_Description": "Kan være enhver brugerdefineret lyd eller en af standardlydene: beep, chelle, ding, droplet, highbell, seasons", "Audio_Notifications_Default_Alert": "Standardlyd for lydnotifikationer", @@ -412,33 +455,44 @@ "auto-translate_description": "Tilladelse til at bruge det automatiske oversættelsesværktøj", "Auto_Load_Images": "Indlæs billeder automatisk", "AutoLinker": "AutoLinker", - "AutoLinker_Email": "AutoLinker Email", + "AutoLinker_Email": "AutoLinker e-mail", "AutoLinker_Phone": "AutoLinker-telefon", - "AutoLinker_Phone_Description": "Automatisk koblet til telefonnumre. f.eks. `(123) 456-7890`", + "AutoLinker_Phone_Description": "Automatisk koblet til telefonnumre. f.eks. '0045-12345678'", "AutoLinker_StripPrefix": "AutoLinker Strip Prefix", - "AutoLinker_StripPrefix_Description": "Kort visning. f.eks. https://rocket.chat => rocket.chat", - "AutoLinker_Urls_Scheme": "AutoLinker Scheme: // URLs", + "AutoLinker_StripPrefix_Description": "Kort visning. F.eks. https://rocket.chat => rocket.chat", + "AutoLinker_Urls_Scheme": "AutoLinker skema: // URLs", "AutoLinker_Urls_TLD": "AutoLinker TLD URLs", "AutoLinker_Urls_www": "AutoLinker 'www' webadresser", "AutoLinker_UrlsRegExp": "AutoLinker URL Regular Expression", "Automatic_Translation": "Automatisk oversættelse", "Author_Site": "Forfatterens side", - "AutoTranslate": "Auto-Oversæt", + "AutoTranslate": "Auto-oversæt", + "Auto_Selection": "Auto-valg", "Auto_Translate": "Auto-oversæt", - "AutoTranslate_APIKey": "API nøgle", - "AutoTranslate_Change_Language_Description": "Ændring af automatisk oversættelsessprog oversætter ikke tidligere meddelelser.", - "AutoTranslate_Enabled": "Aktivér Auto-Translate", - "AutoTranslate_Enabled_Description": "Aktivering af automatisk oversættelse gør det muligt for folk med autooversættelse rettigheder til at få alle meddelelser automatisk oversat til deres valgte sprog. Gebyrer kan blive påført.", - "Available": "tilgængelig", - "Available_agents": "Tilgængelige agenter", + "AutoTranslate_APIKey": "API-nøgle", + "AutoTranslate_Change_Language_Description": "Ændring af det automatisk oversættelsessprog oversætter ikke tidligere meddelelser.", + "AutoTranslate_DeepL": "DeepL", + "AutoTranslate_Enabled": "Aktivér auto-oversættelse", + "AutoTranslate_Enabled_Description": "Aktivering af automatisk oversættelse gør det muligt for personer med auto-oversættelse rettigheder at få alle meddelelser automatisk oversat til deres valgte sprog. Gebyrer kan blive påført.", + "AutoTranslate_Google": "Google", + "AutoTranslate_Microsoft": "Microsoft", + "AutoTranslate_Microsoft_API_Key": "OCP-APIM-abonnement-Key", + "AutoTranslate_ServiceProvider": "Serviceudbyder", + "Available": "Ledig", + "Available_agents": "Ledige agenter", "Avatar": "Avatar", - "Avatar_changed_successfully": "Avatar blev ændret med succes", - "Avatar_URL": "Avatar URL", - "Avatar_url_invalid_or_error": "Den angivne url er ugyldig eller ikke tilgængelig. Prøv igen, men med en anden url.", - "Avg_chat_duration": "Gns. chat-varighed", - "Avg_first_response_time": "Gns. første svartid", - "Avg_response_time": "Gns. svartid", - "Avg_reaction_time": "Gns. reaktionstid", + "Avatar_changed_successfully": "Avatar blev ændret", + "Avatar_URL": "Avatar-URL", + "Avatar_url_invalid_or_error": "Den angivne URL er ugyldig eller ikke tilgængelig. Prøv igen, men med en anden URL.", + "Avg_chat_duration": "Gennemsnit af chat-varighed", + "Avg_first_response_time": "Gennemsnit af første responstid", + "Avg_of_abandoned_chats": "Gennemsnit af forladte chats", + "Avg_of_available_service_time": "Gennemsnitlig tilgængelighed servicen", + "Avg_of_chat_duration_time": "Gennemsnit af chatvarighedstid", + "Avg_of_service_time": "Gennemsnit af servicetid", + "Avg_of_waiting_time": "Gennemsnit af ventetid", + "Avg_response_time": "Gennemsnitlig reaktionstid", + "Avg_reaction_time": "Gennemsnitlig reaktionstid", "away": "væk", "Away": "Væk", "away_female": "væk", @@ -448,20 +502,23 @@ "Back": "Tilbage", "Back_to_applications": "Tilbage til applikationer", "Back_to_chat": "Tilbage til chat", - "Back_to_integration_detail": "Tilbage til integration detaljer", + "Back_to_imports": "Tilbage til import", + "Back_to_integration_detail": "Tilbage til integrations-detaljer", "Back_to_integrations": "Tilbage til integrationer", "Back_to_login": "Tilbage til login", - "Back_to_Manage_Apps": "Tilbage til Administrer apps", + "Back_to_Manage_Apps": "Tilbage til Administration af Apps", "Back_to_permissions": "Tilbage til tilladelser", + "Back_to_room": "Tilbage til rum", "Backup_codes": "Backup koder", - "ban-user": "Forbud bruger", - "ban-user_description": "Tilladelse til at forbyde en bruger fra en kanal", - "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta funktion. Afhænger af videokonference for at være aktiveret.", + "ban-user": "Ban bruger", + "ban-user_description": "Tilladelse til at banne en bruger fra en kanal", + "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta-funktion. Afhænger af videokonference for at blive aktiveret.", + "Better": "Bedre", "Best_first_response_time": "Bedste første svartid", "Block_User": "Bloker bruger", "Blockchain": "Blockchain", - "Blockstack_Auth_Description": "Godkendelsesbeskrivelse", - "Blockstack_ButtonLabelText": "Knap-tekst", + "Blockstack_Auth_Description": "Auth-beskrivelse", + "Blockstack_ButtonLabelText": "Tekst på knap", "Blockstack_Generate_Username": "Generér brugernavn", "Body": "Legeme", "bold": "fed", @@ -1732,6 +1789,7 @@ "List_of_Direct_Messages": "Liste over direkte meddelelser", "Livechat": "Livechat", "Livechat_agents": "Livechat agenter", + "Livechat_Agents": "Agenter", "Livechat_AllowedDomainsList": "Livechat Tilladte Domæner", "Livechat_Dashboard": "Livechat Dashboard", "Livechat_enabled": "Livechat aktiveret", @@ -2029,8 +2087,6 @@ "Notification_Duration": "Notifikationsvarighed", "Notification_Mobile_Default_For": "Skub mobile meddelelser til", "Notifications": "Meddelelser", - "Notifications_Always_Notify_Mobile": "Altid underrette mobil", - "Notifications_Always_Notify_Mobile_Description": "Vælg at altid underrette mobilenheden uanset tilstedeværelsesstatus.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Medlemmer af Max Room før deaktivere alle meddelelsesmeddelelser", "Notifications_Max_Room_Members_Description": "Maks antal medlemmer i rummet, når meddelelser for alle meddelelser bliver deaktiveret. Brugere kan stadig ændre pr. Værelseindstilling for at modtage alle meddelelser individuelt. (0 for at deaktivere)", @@ -2212,7 +2268,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN-nøgle", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Debug", "Push_enable": "Muliggøre", "Push_enable_gateway": "Aktivér Gateway", "Push_gateway": "Gateway", @@ -3012,4 +3067,4 @@ "Your_push_was_sent_to_s_devices": "Dit skub blev sendt til%s-enheder", "Your_server_link": "Din server link", "Your_workspace_is_ready": "Dit arbejdsområde er klar til brug 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json index 523abbd1db5a..7720cf06ae26 100644 --- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -1891,8 +1891,6 @@ "Notification_Duration": "Dauer der Benachrichtigung", "Notification_Mobile_Default_For": "Push Mobile Benachrichtigungen für", "Notifications": "Benachrichtigungen", - "Notifications_Always_Notify_Mobile": "Mobil immer benachrichtigen", - "Notifications_Always_Notify_Mobile_Description": "Immer mobil benachrichtigen, unabhängig vom Verfügbarkeitsstatus.", "Notifications_Duration": "Benachrichtigungen_Dauer", "Notifications_Max_Room_Members": "Max Room-Mitglieder vor dem Deaktivieren aller Nachrichtenbenachrichtigungen", "Notifications_Max_Room_Members_Description": "Maximale Anzahl von Mitgliedern im Raum, wenn Benachrichtigungen für alle Nachrichten deaktiviert werden. Benutzer können die Einstellung pro Zimmer ändern, um alle Benachrichtigungen auf individueller Basis zu erhalten. (0 zum Deaktivieren)", @@ -2060,7 +2058,6 @@ "Push_apn_dev_passphrase": "APN-Dev-Passphrase", "Push_apn_key": "APN-Key", "Push_apn_passphrase": "APN-Passphrase", - "Push_debug": "Debuggen", "Push_enable": "Aktivieren", "Push_enable_gateway": "Gateway aktivieren", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 8c6eee319cbb..196da04d0b28 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -2391,8 +2391,6 @@ "Notification_RequireInteraction_Description": "Verwendung nur mit Chrome Browser Version > 50 möglich. Verwendet den Parameter requireInteraction um die Anzeigedauer der Desktop-Benachrichtigung bis zur Benutzerinteraktion zu verlängern.", "Notification_Mobile_Default_For": "Mobile Benachrichtigungen anzeigen für", "Notifications": "Benachrichtigungen", - "Notifications_Always_Notify_Mobile": "Immer das Mobiltelefon benachrichtigen", - "Notifications_Always_Notify_Mobile_Description": "Immer mobil benachrichtigen, unabhängig vom Verfügbarkeitsstatus.", "Notifications_Duration": "Benachrichtigungen_Dauer", "Notifications_Max_Room_Members": "Maximale Anzahl der Raummitglieder, ab der alle Nachrichten-Benachrichtigungen deaktiviert werden", "Notifications_Max_Room_Members_Description": "Maximale Anzahl der Raummitglieder, ab der alle Benachrichtigungen deaktiviert werden. Benutzer können weiterhin die Einstellung für den Raum ändern, um auf individueller Basis alle Nachrichten zu erhalten (0 deaktiviert die Einstellung)", @@ -2592,9 +2590,6 @@ "Push_apn_dev_passphrase": "APN-Dev-Passphrase", "Push_apn_key": "APN-Key", "Push_apn_passphrase": "APN-Passphrase", - "Push_debug": "Debuggen", - "Push_send_interval": "Intervall zum Überprüfen der Warteschlange auf neue Push-Benachrichtigungen", - "Push_send_batch_size": "Chargengröße, die bei jedem Tick zu verarbeiten ist", "Push_enable": "Aktivieren", "Push_enable_gateway": "Gateway aktivieren", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/el.i18n.json b/packages/rocketchat-i18n/i18n/el.i18n.json index 05c4e635cfe1..9ccc3f4be419 100644 --- a/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/packages/rocketchat-i18n/i18n/el.i18n.json @@ -1893,8 +1893,6 @@ "Notification_Duration": "Διάρκεια ειδοποίησης", "Notification_Mobile_Default_For": "Push Κινητά ειδοποιήσεις για", "Notifications": "Ειδοποιήσεις", - "Notifications_Always_Notify_Mobile": "Να ειδοποιείτε πάντα για κινητά", - "Notifications_Always_Notify_Mobile_Description": "Επιλέξτε να ενημερώνετε πάντα την κινητή συσκευή ανεξάρτητα από την κατάσταση παρουσίας.", "Notifications_Duration": "Ειδοποιήσεις_Αυτή", "Notifications_Max_Room_Members": "Μέλη Max Room πριν απενεργοποιήσετε όλες τις ειδοποιήσεις μηνυμάτων", "Notifications_Max_Room_Members_Description": "Μέγιστος αριθμός μελών στο δωμάτιο, όταν οι ειδοποιήσεις για όλα τα μηνύματα αποκλείονται. Οι χρήστες μπορούν ακόμα να αλλάξουν ανά ρύθμιση δωματίου για να λάβουν όλες τις ειδοποιήσεις σε ατομική βάση. (0 για απενεργοποίηση)", @@ -2062,7 +2060,6 @@ "Push_apn_dev_passphrase": "APN Dev Συνθηματικό", "Push_apn_key": "APN Κλειδί", "Push_apn_passphrase": "APN Συνθηματικό", - "Push_debug": "Αποσφαλμάτωση", "Push_enable": "Ενεργοποίηση", "Push_enable_gateway": "Ενεργοποίηση Πύλη", "Push_gateway": "πύλη", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index d660e05c87c7..6eb99794b603 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -693,7 +693,7 @@ "close-others-livechat-room": "Close other Omnichannel Room", "Cloud_workspace_connected_without_account": "Your workspace is now connected to the Rocket.Chat Cloud. If you would like, you can login to the Rocket.Chat Cloud and associate your workspace with your Cloud account.", "close-others-livechat-room_description": "Permission to close other Omnichannel rooms", - "Close_room_description" : "You are about to close this chat. Are you sure you want to continue?", + "Close_room_description": "You are about to close this chat. Are you sure you want to continue?", "Closed": "Closed", "Closed_At": "Closed at", "Closed_by_visitor": "Closed by visitor", diff --git a/packages/rocketchat-i18n/i18n/eo.i18n.json b/packages/rocketchat-i18n/i18n/eo.i18n.json index 47fb5b40a3e5..9d8b8f11d7a5 100644 --- a/packages/rocketchat-i18n/i18n/eo.i18n.json +++ b/packages/rocketchat-i18n/i18n/eo.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Sciigo Daŭro", "Notification_Mobile_Default_For": "Push Poŝtelefonaj Sciigoj Por", "Notifications": "Sciigoj", - "Notifications_Always_Notify_Mobile": "Ĉiam sciigu telefonon", - "Notifications_Always_Notify_Mobile_Description": "Elektu ĉiam sciigi moveblan aparaton sendepende de ĉeesto.", "Notifications_Duration": "Sciigoj_Durado", "Notifications_Max_Room_Members": "Max Room-Membroj Antaŭ Malŝalti Ĉiujn Mesaĝajn Sciigojn", "Notifications_Max_Room_Members_Description": "Max nombro da membroj en ĉambro kiam sciigoj por ĉiuj mesaĝoj malŝaltas. Uzantoj ankoraŭ povas ŝanĝi laŭ ĉambro por ricevi ĉiujn sciigojn individue. (0 malŝalti)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN-Dev-frazŝlosilo", "Push_apn_key": "APN-ŝlosilo", "Push_apn_passphrase": "APN-pasvorto", - "Push_debug": "Depuŝo", "Push_enable": "Ebligu", "Push_enable_gateway": "Ebligu Gateway", "Push_gateway": "Pordejo", diff --git a/packages/rocketchat-i18n/i18n/es.i18n.json b/packages/rocketchat-i18n/i18n/es.i18n.json index ffc7136190ed..397df04e8260 100644 --- a/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/packages/rocketchat-i18n/i18n/es.i18n.json @@ -2383,8 +2383,6 @@ "Notification_Duration": "Duración de la notificación", "Notification_Mobile_Default_For": "Push Mobile Notifications para", "Notifications": "Notificaciones", - "Notifications_Always_Notify_Mobile": "Siempre notifica al móvil", - "Notifications_Always_Notify_Mobile_Description": "Elija notificar siempre al dispositivo móvil independientemente del estado de presencia.", "Notifications_Duration": "Duración de las notificaciones", "Notifications_Max_Room_Members": "Máximo de miembros de sala antes de deshabilitar todas las notificaciones de mensajes", "Notifications_Max_Room_Members_Description": "Número máximo de miembros en la sala cuando las notificaciones para todos los mensajes se desactivan. Los usuarios aún pueden cambiar por configuración de sala para recibir todas las notificaciones de forma individual. (0 para deshabilitar)", @@ -2553,7 +2551,6 @@ "Push_apn_dev_passphrase": "Frase de contraseña APN de desarrollador", "Push_apn_key": "Llave de APN", "Push_apn_passphrase": "Frase de contraseña APN", - "Push_debug": "Depurar", "Push_enable": "Habilitar", "Push_enable_gateway": "Habilitar Puerta de Enlace", "Push_gateway": "Puerta de Enlace", @@ -3364,4 +3361,4 @@ "Your_push_was_sent_to_s_devices": "Su push fue enviado a los dispositivos %s", "Your_server_link": "Su enlace de servidor", "Your_workspace_is_ready": "Su espacio de trabajo está listo para usar 🎉" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/fa.i18n.json b/packages/rocketchat-i18n/i18n/fa.i18n.json index 6d1e6031c375..a1db2d28b55e 100644 --- a/packages/rocketchat-i18n/i18n/fa.i18n.json +++ b/packages/rocketchat-i18n/i18n/fa.i18n.json @@ -2098,8 +2098,6 @@ "Notification_Duration": "مدت زمان اعلان", "Notification_Mobile_Default_For": "push notification های تلفن همراه برای", "Notifications": "اعلانات", - "Notifications_Always_Notify_Mobile": "همیشه به موبایل اطلاع دهید", - "Notifications_Always_Notify_Mobile_Description": "انتخاب کنید که هر زمان که دستگاه همراه را بدون در نظر گرفتن وضعیت حضور، اطلاع دهید.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "کاربران حداکثر اتاق قبل از غیر فعال کردن همه پیام های پیام", "Notifications_Max_Room_Members_Description": "حداکثر تعداد اعضا در اتاق زمانی که اعلان برای همه پیامها غیرفعال می شود. کاربران هنوز هم می توانند در هر اتاق تنظیمات را تغییر دهند تا همه اعلان ها را به صورت فردی دریافت کنند. (0 برای غیر فعال کردن)", @@ -2267,7 +2265,6 @@ "Push_apn_dev_passphrase": "APN نویس کلمه عبور", "Push_apn_key": "APN کلیدی", "Push_apn_passphrase": "APN کلمه عبور", - "Push_debug": "اشکال زدایی", "Push_enable": "قادر ساختن", "Push_enable_gateway": "فعال کردن دروازه", "Push_gateway": "دروازه", diff --git a/packages/rocketchat-i18n/i18n/fi.i18n.json b/packages/rocketchat-i18n/i18n/fi.i18n.json index 9965bc54021c..05522909e0ea 100644 --- a/packages/rocketchat-i18n/i18n/fi.i18n.json +++ b/packages/rocketchat-i18n/i18n/fi.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Ilmoituksen kesto", "Notification_Mobile_Default_For": "Push mobiiliviestit", "Notifications": "Ilmoitukset", - "Notifications_Always_Notify_Mobile": "Ilmoita aina matkapuhelimelle", - "Notifications_Always_Notify_Mobile_Description": "Valitse aina ilmoitus mobiililaitteesta läsnäolotilasta riippumatta.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Max Room Members Ennen kaikkien viestiviestien käytöstäpoistoa", "Notifications_Max_Room_Members_Description": "Maksimi jäsenmäärä huoneessa, kun kaikkien viestien ilmoitukset poistetaan käytöstä. Käyttäjät voivat silti muuttaa huoneen asetusta kohti vastaanottaa kaikki ilmoitukset yksilöllisesti. (0 poista käytöstä)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Passphrase (kehitys)", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Debug", "Push_enable": "Kytke päälle", "Push_enable_gateway": "Ota yhdyskäytävä käyttöön", "Push_gateway": "Yhdyskäytävä", diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index 630fe4da3571..bd657ac1282a 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -2058,8 +2058,6 @@ "Notification_Duration": "Durée de la notification", "Notification_Mobile_Default_For": "Pousser les notifications mobiles pour ", "Notifications": "Notifications", - "Notifications_Always_Notify_Mobile": "Toujours informer le mobile", - "Notifications_Always_Notify_Mobile_Description": "Choisissez de toujours informer l'appareil mobile, quel que soit l'état de présence.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Max Room Membres avant de désactiver toutes les notifications de message", "Notifications_Max_Room_Members_Description": "Nombre maximal de membres dans le salon lorsque les notifications pour tous les messages sont désactivées. Les utilisateurs peuvent toujours modifier les paramètres du salon pour recevoir toutes les notifications individuellement. (0 pour désactiver)", @@ -2250,7 +2248,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "Clef APN", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Débogage", "Push_enable": "Activer", "Push_enable_gateway": "Activer la passerelle", "Push_gateway": "Passerelle", diff --git a/packages/rocketchat-i18n/i18n/he.i18n.json b/packages/rocketchat-i18n/i18n/he.i18n.json index a04bf3ef779e..0cc1bba30c0b 100644 --- a/packages/rocketchat-i18n/i18n/he.i18n.json +++ b/packages/rocketchat-i18n/i18n/he.i18n.json @@ -1039,7 +1039,6 @@ "Push_apn_dev_passphrase": "סיסמת סביבת פיתוח של APN", "Push_apn_key": "מפתח ל-APN", "Push_apn_passphrase": "סיסמה ל-APN", - "Push_debug": "ניפוי שגיאות", "Push_enable": "אפשר", "Push_enable_gateway": "הפעלת שער גישה", "Push_gateway": "שער גישה", diff --git a/packages/rocketchat-i18n/i18n/hr.i18n.json b/packages/rocketchat-i18n/i18n/hr.i18n.json index bdc1b25f95f2..24b414b06202 100644 --- a/packages/rocketchat-i18n/i18n/hr.i18n.json +++ b/packages/rocketchat-i18n/i18n/hr.i18n.json @@ -2020,8 +2020,6 @@ "Notification_Duration": "Trajanje obavijesti", "Notification_Mobile_Default_For": "Push mobilnih obavijesti za", "Notifications": "Obavijesti", - "Notifications_Always_Notify_Mobile": "Uvijek obavijesti mobitel", - "Notifications_Always_Notify_Mobile_Description": "Odaberite uvijek obavijestite mobilni uređaj bez obzira na status prisutnosti.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Članovi Max soba prije onemogućavanja svih obavijesti o poruci", "Notifications_Max_Room_Members_Description": "Maksimalni broj članova u sobi kada se obavijesti o svim porukama onemogućuju. Korisnici se i dalje mogu mijenjati po postavkama sobe kako bi primile sve obavijesti na pojedinačnoj osnovi. (0 za onemogućavanje)", @@ -2189,7 +2187,6 @@ "Push_apn_dev_passphrase": "APN Dev zaporka", "Push_apn_key": "APN Ključ", "Push_apn_passphrase": "APN zaporka", - "Push_debug": "Debu", "Push_enable": "Omogući", "Push_enable_gateway": "Omogući Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/hu.i18n.json b/packages/rocketchat-i18n/i18n/hu.i18n.json index 9e878e1958d2..7d5173dd6067 100644 --- a/packages/rocketchat-i18n/i18n/hu.i18n.json +++ b/packages/rocketchat-i18n/i18n/hu.i18n.json @@ -2227,8 +2227,6 @@ "Notification_Duration": "Értesítés időtartama", "Notification_Mobile_Default_For": "Értesítések továbbítása mobilra", "Notifications": "Értesítések", - "Notifications_Always_Notify_Mobile": "Mindig értesítse a mobiltelefont", - "Notifications_Always_Notify_Mobile_Description": "Válassza ki, hogy mindig értesítse a mobilkészüléket a jelenlét állapotától függetlenül.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "A Max Room tagjai minden üzenet értesítés letiltása előtt", "Notifications_Max_Room_Members_Description": "A tagok maximális száma a szobában, amikor az összes üzenet értesítései le vannak tiltva. A felhasználók szobaházonként bármikor módosíthatják az összes értesítést. (0 letiltani)", @@ -2420,7 +2418,6 @@ "Push_apn_dev_passphrase": "APN Dev Jelszó", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Jelszó", - "Push_debug": "Debug", "Push_enable": "Engedélyezze", "Push_enable_gateway": "engedélyezze Gateway", "Push_gateway": "Átjáró", diff --git a/packages/rocketchat-i18n/i18n/id.i18n.json b/packages/rocketchat-i18n/i18n/id.i18n.json index 17a7980c3516..78318a129fe1 100644 --- a/packages/rocketchat-i18n/i18n/id.i18n.json +++ b/packages/rocketchat-i18n/i18n/id.i18n.json @@ -1896,8 +1896,6 @@ "Notification_Duration": "Durasi Pemberitahuan", "Notification_Mobile_Default_For": "Pemberitahuan Push Mobile Untuk", "Notifications": "pemberitahuan", - "Notifications_Always_Notify_Mobile": "Selalu beritahu ponsel", - "Notifications_Always_Notify_Mobile_Description": "Pilih untuk selalu memberi tahu perangkat seluler terlepas dari status keberadaannya.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Anggota Ruang Maksimal Sebelum Menonaktifkan Semua Pemberitahuan Pesan", "Notifications_Max_Room_Members_Description": "Jumlah anggota maksimal di ruangan saat pemberitahuan untuk semua pesan dinonaktifkan. Pengguna tetap dapat mengubah setelan per kamar untuk menerima semua notifikasi secara individual. (0 untuk menonaktifkan)", @@ -2065,7 +2063,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase ", - "Push_debug": "Debug", "Push_enable": "Hidupkan", "Push_enable_gateway": "aktifkan Gateway", "Push_gateway": "pintu gerbang", diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index 4582d7591d77..cf93796c15a2 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -1918,8 +1918,6 @@ "Notification_Duration": "Durata notifica", "Notification_Mobile_Default_For": "Spingere le notifiche mobili per", "Notifications": "Notifiche", - "Notifications_Always_Notify_Mobile": "Notifica sempre mobile", - "Notifications_Always_Notify_Mobile_Description": "Scegliere di notificare sempre il dispositivo mobile indipendentemente dallo stato di presenza.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Membri della stanza massima prima di disabilitare tutte le notifiche dei messaggi", "Notifications_Max_Room_Members_Description": "Numero massimo di membri nella stanza quando le notifiche per tutti i messaggi vengono disabilitate. Gli utenti possono comunque modificare l'impostazione di ogni stanza per ricevere tutte le notifiche su base individuale. (0 per disabilitare)", @@ -2087,7 +2085,6 @@ "Push_apn_dev_passphrase": "Password di sviluppo APN", "Push_apn_key": "Chiave APN", "Push_apn_passphrase": "Password APN", - "Push_debug": "Debug", "Push_enable": "Attivo", "Push_enable_gateway": "Abilita Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json index f6c02c9e114b..1073647cd010 100644 --- a/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -99,6 +99,7 @@ "Accounts_OAuth_Custom_Token_Path": "トークンパス", "Accounts_OAuth_Custom_Token_Sent_Via": "トークン送信元", "Accounts_OAuth_Custom_Username_Field": "ユーザー名フィールド", + "Accounts_OAuth_Custom_Email_Field": "メールフィールド", "Accounts_OAuth_Custom_Name_Field": "名前フィールド", "Accounts_OAuth_Custom_Roles_Claim": "ロール/グループのフィールド名", "Accounts_OAuth_Custom_Merge_Roles": "SSOからロールのマージ", @@ -658,6 +659,7 @@ "Chatpal_Welcome": "あなたの検索をお楽しみください!", "Chatpal_Window_Size": "インデックスウィンドウサイズ", "Chatpal_Window_Size_Description": "インデックスウィンドウのサイズ (ブートストラップ時)", + "Check_All": "すべて調べる", "Check_Progress": "進捗を確認", "Choose_a_room": "ルームを選択してください", "Choose_messages": "メッセージを選択", @@ -689,6 +691,7 @@ "close-others-livechat-room": "ライブチャットルームを閉じる", "Cloud_workspace_connected_without_account": "あなたのワークスペースは Rocket.Chat クラウドに接続されました。Rocket.Chat クラウドにログインし、ワークスペースをクラウドアカウントに関連付けることができます。", "close-others-livechat-room_description": "他のライブチャットチャンネルを閉じる権限", + "Close_room_description": "このチャットを閉じようとしています。続行してもよろしいですか?", "Closed": "閉じた状態", "Closed_At": "閉じた日時", "Closed_by_visitor": "訪問者によって閉鎖される", @@ -1151,6 +1154,7 @@ "Direct_message_creation_description": "複数のユーザーとのチャットを作成しようとしています。ダイレクトメッセージを使用して、話したい人たちを同じ場所に全員追加します。", "Direct_message_you_have_joined": "新しいダイレクトメッセージに参加しました", "Direct_message_someone": "ユーザーへダイレクトメッセージする", + "Direct_Message": "ダイレクトメッセージ", "Direct_Messages": "ダイレクトメッセージ", "Direct_Reply": "直接返信", "Direct_Reply_Advice": "このメールに直接返信することができます。スレッド内の以前の電子メールは変更しないでください。", @@ -1406,6 +1410,8 @@ "error-password-policy-not-met-oneSpecial": "パスワードが少なくとも1つの特殊文字のサーバーのポリシーを満たしていない", "error-password-policy-not-met-oneUppercase": "パスワードが少なくとも1つの大文字のサーバーのポリシーを満たしていない", "error-password-policy-not-met-repeatingCharacters": "パスワードが繰り返し文字の禁止というサーバポリシーを満たしていない (同じ文字の連続が多すぎます)", + "error-pinning-message": "メッセージを固定できませんでした", + "error-unpinning-message": "メッセージの固定を解除できませんでした", "error-push-disabled": "プッシュが無効になっています", "error-remove-last-owner": "これは最後の所有者です。これを削除する前に、新しい所有者を設定してください。", "error-tags-must-be-assigned-before-closing-chat": "チャットを閉じる前にタグを割り当てる必要があります", @@ -2112,6 +2118,7 @@ "leave-c": "チャンネルを退出する", "leave-p": "プライベートグループを退出する", "Leave": "退出", + "Leave_a_comment": "コメントを残す", "Leave_Group_Warning": "本当にグループ \"%s\" から退出しますか?", "Leave_Livechat_Warning": "本当に \"%s\" とのライブチャットから退出しますか?", "Leave_Private_Warning": "本当に \"%s\" との会話から退出しますか?", @@ -2152,7 +2159,9 @@ "Livechat_title_color": "ライブチャットタイトル 背景色", "Livechat_transcript_sent": "Livechatトランスクリプトが送信されました", "Livechat_transfer_to_agent": "__from__ がチャットを __to__ に転送しました", + "Livechat_transfer_to_agent_with_a_comment": "__from__ がチャットを __to__ にコメント付きで転送しました: __comment__", "Livechat_transfer_to_department": "__from__ はチャットを __to__ 部門に転送しました", + "Livechat_transfer_to_department_with_a_comment": "__from__ がチャットを部門 __to__ にコメント付きで転送しました: __comment__", "Livechat_transfer_return_to_the_queue": "__from__ はチャットをキューに戻しました", "Livechat_Triggers": "Livechatトリガー", "Livechat_Users": "ライブチャット 担当者", @@ -2321,6 +2330,8 @@ "Message_GlobalSearch": "グローバル検索", "Message_GroupingPeriod": "グループ化する期限 (秒数)", "Message_GroupingPeriodDescription": "指定した秒数の間で、同じユーザーが投稿した以前のメッセージをグループ化します。", + "Message_has_been_pinned": "メッセージが固定されました", + "Message_has_been_unpinned": "メッセージの固定が解除されました", "Message_has_been_starred": "メッセージにスターを付けました", "Message_has_been_unstarred": "メッセージのスターを外しました", "Message_HideType_au": "「ユーザーが追加されました」メッセージを非表示にする", @@ -2503,8 +2514,6 @@ "Notification_RequireInteraction_Description": "Chromeブラウザバージョン50以上でのみ動作します。パラメーター requireInteraction を使用して、ユーザーが操作するまでデスクトップ通知を無期限に表示します。", "Notification_Mobile_Default_For": "モバイルプッシュ通知のタイミング", "Notifications": "通知", - "Notifications_Always_Notify_Mobile": "モバイルに常に通知する", - "Notifications_Always_Notify_Mobile_Description": "プレゼンス状態に関係なく常にモバイルデバイスに通知することを選択します。", "Notifications_Duration": "通知期間", "Notifications_Max_Room_Members": "すべてのメッセージ通知を無効にする前の最大ルームメンバー", "Notifications_Max_Room_Members_Description": "すべてのメッセージの通知が無効になったときのルームのメンバーの最大数。ユーザーは個々の基準ですべての通知を受け取るために、ルームごとの設定を変更することができます。 (0は無効)", @@ -2620,6 +2629,7 @@ "Pin_Message": "ピン留めする", "Pinned_a_message": "メッセージをピン留めしました :", "Pinned_Messages": "ピン留めされたメッセージ", + "pinning-not-allowed": "固定は許可されていません", "PiwikAdditionalTrackers": "追加のPiwikサイト", "PiwikAdditionalTrackers_Description": "[{\"trackerURL\": \"https://my.piwik.domain2/\", \"siteId\": 42}、別のウェブサイトに同じデータをトラッキングする場合、追加のPiwikウェブサイトURLとサイトIDを次の形式で入力します。 {\"trackerURL\": \"https://my.piwik.domain3/\", \"siteId\": 15}]", "PiwikAnalytics_cookieDomain": "すべてのサブドメイン", @@ -2722,9 +2732,6 @@ "Push_apn_dev_passphrase": "APN 開発者 パスフレーズ", "Push_apn_key": "APN キー", "Push_apn_passphrase": "APN パスフレーズ", - "Push_debug": "デバッグ", - "Push_send_interval": "新しいプッシュ通知のキューをチェックする間隔", - "Push_send_batch_size": "一回に処理されるバッチサイズ", "Push_enable": "有効にする", "Push_enable_gateway": "ゲートウェイを有効にする", "Push_gateway": "ゲートウェイ", @@ -2739,6 +2746,7 @@ "Purchase_for_price": "$%sで購入", "Query": "条件クエリー", "Query_description": "メール送信先のユーザーを決める追加条件。購読解除されたユーザーは、自動的に条件から除外されます。条件は、有効なJSON形式でなければなりません。例:  \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"", + "Query_is_not_valid_JSON": "クエリは有効なJSONではありません", "Queue": "キュー", "quote": "引用", "Quote": "引用", @@ -2917,6 +2925,7 @@ "SAML": "SAML", "SAML_Custom_Authn_Context": "カスタム認証コンテキスト", "SAML_Custom_Authn_Context_Comparison": "認証コンテキストの比較", + "SAML_Custom_Authn_Context_description": "リクエストから authn コンテキストを除外するには、これを空のままにします。", "SAML_Custom_Cert": "カスタム 証明書", "SAML_Custom_Debug": "デバッグを有効化", "SAML_Custom_Entry_point": "カスタム エントリーポイント", @@ -2931,6 +2940,12 @@ "SAML_Custom_Private_Key": "秘密鍵の内容", "SAML_Custom_Provider": "カスタム プロバイダー", "SAML_Custom_EMail_Field": "電子メールのフィールド名", + "SAML_Custom_signature_validation_all": "すべての署名を検証", + "SAML_Custom_signature_validation_assertion": "警告の署名を検証", + "SAML_Custom_signature_validation_either": "どちらかの署名を検証", + "SAML_Custom_signature_validation_response": "応答の署名を検証", + "SAML_Custom_signature_validation_type": "署名検証タイプ", + "SAML_Custom_signature_validation_type_description": "カスタム証明書が提供されていない場合、この設定は無視されます。", "SAML_Custom_user_data_fieldmap": "ユーザーデータのフィールドマップ", "SAML_Custom_user_data_fieldmap_description": "検出されたSAMLのレコードからユーザーアカウントフィールド(電子メールなど)が入力される方法を構成します。
例として、 `{\"cn\":\"name\", \"mail\":\"email\"}`は、cn属性から人間が読める名前を選択し、mail属性からそのメールアドレスを選択します。
Rocket.Chatで有効なフィールド: `name`、`email`と`username`、それ以外はすべて`customFields`として保存されます。
次のように、正規表現を使用してフィールド値を取得することもできます: `{\"NameID\": { \"field\": \"username\", \"regex\": \"(.*)@.+$\"}, \"email\": \"email\"}`", "SAML_Custom_Username_Field": "ユーザー名のフィールド名", @@ -3036,6 +3051,7 @@ "set-readonly": "ReadOnlyを設定する", "set-readonly_description": "チャンネルを読み取り専用チャンネルに設定する許可", "Set_as_leader": "リーダーとして設定する", + "Set_as_favorite": "お気に入りとして設定", "Set_as_moderator": "モデレーターに設定", "Set_as_owner": "オーナーに設定", "Settings": "設定", @@ -3151,6 +3167,7 @@ "Statistics": "統計", "Statistics_reporting": "Rocket.Chatに統計情報を送信します", "Statistics_reporting_Description": "あなたの統計情報を送信することにより、あなたは、私たちはRocket.Chatのどのように多くのインスタンスを識別するのに役立ちます展開されているだけでなく、システムがどのように動作しているかに良いので、私たちはそれを更に向上させることができます。ユーザ情報が送信されないと、我々は受信したすべての情報は機密扱いされているように、心配しないでください。", + "Stats_Active_Guests": "アクティブなゲスト", "Stats_Active_Users": "アクティブなユーザー", "Stats_App_Users": "Rocket.Chatアプリのユーザー", "Stats_Avg_Channel_Users": "チャンネルの平均ユーザー", @@ -3273,6 +3290,7 @@ "theme-color-rc-color-error-light": "エラー 明るい色", "theme-color-rc-color-link-active": "リンク有効の色", "theme-color-rc-color-primary": "基本の色", + "theme-color-rc-color-primary-background": "基本 背景", "theme-color-rc-color-primary-dark": "基本 暗い色", "theme-color-rc-color-primary-darkest": "基本 最も暗い色", "theme-color-rc-color-primary-light": "基本 明るい色", @@ -3420,6 +3438,7 @@ "unarchive-room": "ルームをアーカイブ解除", "unarchive-room_description": "チャンネルをアーカイブ解除する権限", "Unblock_User": "ユーザーをブロック解除する", + "Uncheck_All": "すべてのチェックを外す", "Undefined": "未定義", "Unfavorite": "お気に入り解除", "Unfollow_message": "メッセージのフォローを解除", @@ -3432,6 +3451,7 @@ "Unnamed": "無名", "Unpin": "ピン解除", "Unpin_Message": "ピン留めを外す", + "unpinning-not-allowed": "固定を解除することはできません", "Unread": "未読", "Unread_Count": "未読数", "Unread_Count_DM": "ダイレクトメッセージの未読数", diff --git a/packages/rocketchat-i18n/i18n/km.i18n.json b/packages/rocketchat-i18n/i18n/km.i18n.json index a24feba8c7b2..9a0586af194e 100644 --- a/packages/rocketchat-i18n/i18n/km.i18n.json +++ b/packages/rocketchat-i18n/i18n/km.i18n.json @@ -2165,8 +2165,6 @@ "Notification_Duration": "រយៈពេលជូនដំណឹង", "Notification_Mobile_Default_For": "ជំរុញការជូនដំណឹងចល័តសម្រាប់", "Notifications": "ការជូនដំណឹង", - "Notifications_Always_Notify_Mobile": "ជូនដំណឹងទូរស័ព្ទជានិច្ច", - "Notifications_Always_Notify_Mobile_Description": "ជ្រើសដើម្បីជូនដំណឹងជានិច្ចឧបករណ៍ចល័តដោយមិនគិតពីស្ថានភាពវត្តមាន។", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "សមាជិកបន្ទប់អតិបរមាមុនពេលបិទការជូនដំណឹងសារទាំងអស់", "Notifications_Max_Room_Members_Description": "ចំនួនអតិបរមានៃសមាជិកនៅក្នុងបន្ទប់នៅពេលដែលការជូនដំណឹងសម្រាប់សារទាំងអស់ត្រូវបានបិទ។ អ្នកប្រើនៅតែអាចផ្លាស់ប្តូរក្នុងការកំណត់បន្ទប់ដើម្បីទទួលបានការជូនដំណឹងទាំងអស់នៅលើមូលដ្ឋានបុគ្គល។ (0 ដើម្បីបិទ)", @@ -2342,7 +2340,6 @@ "Push_apn_dev_passphrase": "ឃ្លា​សម្ងាត់​អ្នក​អភិវឌ្ឍន៍ APN", "Push_apn_key": "សោរ APN", "Push_apn_passphrase": "ឃ្លា​សម្ងាត់ APN", - "Push_debug": "ត្រួត​ពិនិត្យ​​កំហុស", "Push_enable": "អនុញ្ញាត", "Push_enable_gateway": "អនុញ្ញាតឱ្យមានផ្លូវចេញចូល", "Push_gateway": "ផ្លូវចេញចូល", diff --git a/packages/rocketchat-i18n/i18n/ko.i18n.json b/packages/rocketchat-i18n/i18n/ko.i18n.json index 80fd7cf5e173..c7d4f94b65b5 100644 --- a/packages/rocketchat-i18n/i18n/ko.i18n.json +++ b/packages/rocketchat-i18n/i18n/ko.i18n.json @@ -2097,8 +2097,6 @@ "Notification_Duration": "알림 기간", "Notification_Mobile_Default_For": "모바일 알림", "Notifications": "알림", - "Notifications_Always_Notify_Mobile": "모바일로 항상 알리기", - "Notifications_Always_Notify_Mobile_Description": "현재 상태에 관계없이 항상 모바일로 알리도록 선택합니다.", "Notifications_Duration": "알림 기간", "Notifications_Max_Room_Members": "알림을 보낸 최대 대화방의 참여자수", "Notifications_Max_Room_Members_Description": "모든 메시지에 대한 알림이 중지된 경우 대화방의 최대 구성원 수입니다. 사용자는 개별적으로 모든 알림을 수신하도록 대화방 설정을 변경할 수 있습니다. (0은 비활성화)", @@ -2285,7 +2283,6 @@ "Push_apn_dev_passphrase": "APN 개발용 암호", "Push_apn_key": "APN 키", "Push_apn_passphrase": "APN 암호", - "Push_debug": "디버그용", "Push_enable": "사용", "Push_enable_gateway": "게이트웨이 사용", "Push_gateway": "게이트웨이", diff --git a/packages/rocketchat-i18n/i18n/ku.i18n.json b/packages/rocketchat-i18n/i18n/ku.i18n.json index 9be1c0e99b77..216854ae4e1a 100644 --- a/packages/rocketchat-i18n/i18n/ku.i18n.json +++ b/packages/rocketchat-i18n/i18n/ku.i18n.json @@ -1886,8 +1886,6 @@ "Notification_Duration": "Agahdariya Demjimêr", "Notification_Mobile_Default_For": "Ji bo Telefonên Push-Pûşper", "Notifications": "notifications", - "Notifications_Always_Notify_Mobile": "Her dem herdem telefon bikin", - "Notifications_Always_Notify_Mobile_Description": "Hilbijêre ku herdem her timê cîhaza mobîl agahdar bikin ku bêyî rewşa statuya hebe.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Berî Berî Xanûbereyên Xanûberey Niştecihan Hemû Peywendiyên Şandî", "Notifications_Max_Room_Members_Description": "Hejmareke hejmarek endamên hejmarê heger agahdariyên hemî mesajan neçalak kirin. Bikaranîna bikarhêner dikarin li ser sazûmanek odeyê biguherînin ku hemî agahdariyên li ser bingeha kesan bigirin. (0 heta qedexekirin)", @@ -2055,7 +2053,6 @@ "Push_apn_dev_passphrase": "APN Dev nasnavê", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN nasnavê", - "Push_debug": "Debug", "Push_enable": "Bikêrkirin", "Push_enable_gateway": "çalak Gateway", "Push_gateway": "Derî", diff --git a/packages/rocketchat-i18n/i18n/lo.i18n.json b/packages/rocketchat-i18n/i18n/lo.i18n.json index b39bf92f9f02..5c4dfcc4298f 100644 --- a/packages/rocketchat-i18n/i18n/lo.i18n.json +++ b/packages/rocketchat-i18n/i18n/lo.i18n.json @@ -1886,8 +1886,6 @@ "Notification_Duration": "Notification Duration", "Notification_Mobile_Default_For": "ຊຸກຍູ້ການແຈ້ງເຕືອນໂທລະສັບມືຖືສໍາລັບ", "Notifications": "ການແຈ້ງເຕືອນ", - "Notifications_Always_Notify_Mobile": "Always ແຈ້ງໂທລະສັບມືຖື", - "Notifications_Always_Notify_Mobile_Description": "ເລືອກທີ່ຈະສະແດງແຈ້ງອຸປະກອນມືຖືຕະຫຼອດເວລາໂດຍບໍ່ສົນເລື່ອງສະຖານະພາບ.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "ສະມາຊິກຫ້ອງການສູງສຸດກ່ອນທີ່ຈະປິດການແຈ້ງເຕືອນຂໍ້ຄວາມທັງຫມົດ", "Notifications_Max_Room_Members_Description": "ຈໍານວນສະມາຊິກສູງສຸດໃນຫ້ອງເມື່ອແຈ້ງເຕືອນສໍາລັບຂໍ້ຄວາມທັງຫມົດຖືກປິດປະຕິບັດ. ຜູ້ໃຊ້ຍັງສາມາດປ່ຽນແປງຕໍ່ການຕັ້ງຄ່າຫ້ອງເພື່ອຮັບການແຈ້ງເຕືອນທັງຫມົດໃນແຕ່ລະພື້ນຖານ. (0 ເພື່ອປິດການໃຊ້ງານ)", @@ -2055,7 +2053,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "debug", "Push_enable": "ເຮັດໃຫ້ສາມາດ", "Push_enable_gateway": "ເຮັດໃຫ້ທາງເລືອກໃຫມ່", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/lt.i18n.json b/packages/rocketchat-i18n/i18n/lt.i18n.json index 006155af8a1a..66e33eba7989 100644 --- a/packages/rocketchat-i18n/i18n/lt.i18n.json +++ b/packages/rocketchat-i18n/i18n/lt.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Pranešimo trukmė", "Notification_Mobile_Default_For": "Paspauskite \"Mobile\" pranešimus", "Notifications": "Pranešimai", - "Notifications_Always_Notify_Mobile": "Visada pranešti mobiliesiems", - "Notifications_Always_Notify_Mobile_Description": "Pasirinkite visada pranešti mobiliajam įrenginiui, nepriklausomai nuo buvimo būsenos.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Didžiausio kambario nariai prieš išjungiant visus pranešimus", "Notifications_Max_Room_Members_Description": "Maksimalus narių skaičius kambaryje, kai pranešimai apie visus pranešimus išjungiami. Vartotojai vis dar gali pakeisti kambario nustatymą, kad gautų visus pranešimus atskirai. (Išjungti 0)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Dev frazė", "Push_apn_key": "APN raktas", "Push_apn_passphrase": "APN slaptažodžio frazė", - "Push_debug": "Derinti", "Push_enable": "Įgalinti", "Push_enable_gateway": "Įgalinti vartai", "Push_gateway": "Vartai", diff --git a/packages/rocketchat-i18n/i18n/lv.i18n.json b/packages/rocketchat-i18n/i18n/lv.i18n.json index 997896fc93c0..21413ba14abd 100644 --- a/packages/rocketchat-i18n/i18n/lv.i18n.json +++ b/packages/rocketchat-i18n/i18n/lv.i18n.json @@ -3,19 +3,24 @@ "500": "Iekšējā servera kļūda", "#channel": "#kanāls", "0_Errors_Only": "0 - Tikai kļūdas", + "12_Hour": "12-stundu pulkstenis", "1_Errors_and_Information": "1 - Kļūdas un informācija", + "24_Hour": "24-stundu pulkstenis", "2_Erros_Information_and_Debug": "2 - Kļūdas, informācija un atkļūdošana", "@username": "@ lietotājvārds", "@username_message": "@ lietotājvārds ", "__username__is_no_longer__role__defined_by__user_by_": "lietotājvārds vairs nav loma lietotājs", "__username__was_set__role__by__user_by_": "lietotājvārds tika iestatīts loma lietotājs", + "%_of_conversations": "% no sarunām", "Accept": "Apstiprināt", "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Pieņemt ienākošos livechat pieprasījumus, pat ja tiešsaistē nav neviena aģenta", + "Accept_new_livechats_when_agent_is_idle": "Pieņemiet jaunus daudzkanāla pieprasījumus, kad aģents ir dīkstāvē", "Accept_with_no_online_agents": "Pieņemt bez tiešsaistē esošiem aģentiem", "access-mailer": "Piekļūt sūtītāja ekrānam", "access-mailer_description": "Atļauja nosūtīt masu e-pastu visiem lietotājiem.", "access-permissions": "Piekļūt Atļauju ekrānam", "access-permissions_description": "Mainīt Atļaujas dažādām lomām.", + "access-setting-permissions": "Modificēt iestatījumos balstītas atļaujas", "Access_not_authorized": "Piekļuve nav atļauta", "Access_Token_URL": "Piekļuves žetona URL", "Accessing_permissions": "Piekļūšana atļaujām", @@ -1886,8 +1891,6 @@ "Notification_Duration": "Paziņojuma ilgums", "Notification_Mobile_Default_For": "Push mobilā tālruņa paziņojumi par", "Notifications": "Paziņojumi", - "Notifications_Always_Notify_Mobile": "Vienmēr paziņot mobilajā tālrunī", - "Notifications_Always_Notify_Mobile_Description": "Izvēlieties vienmēr paziņot mobilajā ierīcē neatkarīgi no dalības statusa.", "Notifications_Duration": "Paziņojuma_ilgums", "Notifications_Max_Room_Members": "Maksimālais istabas biedru skaits pirms atspējot visus paziņojumus par ziņojumiem", "Notifications_Max_Room_Members_Description": "Maksimālais dalībnieku skaits istabā, kad paziņojumi par visiem ziņojumiem tiek atspējoti. Lietotāji joprojām var mainīt istabas iestatījumus, lai saņemtu visus paziņojumus individuāli. (0 lai atspējotu)", @@ -2055,7 +2058,6 @@ "Push_apn_dev_passphrase": "APN Dev drošības frāze", "Push_apn_key": "APN atslēga", "Push_apn_passphrase": "APN drošbas frāze", - "Push_debug": "Atkļūdošana", "Push_enable": "Iespējot", "Push_enable_gateway": "Iespējot vārteju", "Push_gateway": "Vārteja", diff --git a/packages/rocketchat-i18n/i18n/mn.i18n.json b/packages/rocketchat-i18n/i18n/mn.i18n.json index 78a3c7b9ec5d..5f17324b076c 100644 --- a/packages/rocketchat-i18n/i18n/mn.i18n.json +++ b/packages/rocketchat-i18n/i18n/mn.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Мэдэгдэл өгөх хугацаа", "Notification_Mobile_Default_For": "Дуут Мобайл мэдэгдэл", "Notifications": "Мэдэгдэл", - "Notifications_Always_Notify_Mobile": "Гар утсаараа үргэлжид мэдэгдэх хэрэгтэй", - "Notifications_Always_Notify_Mobile_Description": "Гар утасны аппликейшн зэргийг үл харгалзан үргэлжлүүлэн өгөхийг сонгоно уу.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Бүх Мессежний мэдэгдэлийг хүчингүй болгохоос өмнө Max Room гишүүд", "Notifications_Max_Room_Members_Description": "Бүх мессежийн мэдэгдлийн үед өрөөнд гишүүдийн хамгийн их тоо идэвхгүй болно. Хэрэглэгчид нь бүх мэдэгдлийг нэг нэгээр нь хүлээн авахын тулд өрөөнд тохируулан өөрчилж болно. (0-ыг идэвхгүй болгох)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN түлхүүр", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Дебаг хийх", "Push_enable": "Бататгах", "Push_enable_gateway": "Гарцыг идэвхжүүлнэ үү", "Push_gateway": "Гарц", diff --git a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json index d840ea2bad04..a94165838bb2 100644 --- a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json +++ b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json @@ -1896,8 +1896,6 @@ "Notification_Duration": "Tempoh Pemberitahuan", "Notification_Mobile_Default_For": "Tolak Pemberitahuan Bergerak Untuk", "Notifications": "Pemberitahuan", - "Notifications_Always_Notify_Mobile": "Sentiasa beritahu telefon bimbit", - "Notifications_Always_Notify_Mobile_Description": "Pilih untuk selalu memberitahu peranti mudah alih tanpa mengira status kehadiran.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Ahli Bilik Maks sebelum Melumpuhkan Semua Pemberitahuan Mesej", "Notifications_Max_Room_Members_Description": "Bilangan ahli dalam bilik apabila pemberitahuan untuk semua mesej menjadi kurang upaya. Pengguna masih boleh menukar tetapan bilik untuk menerima semua pemberitahuan secara individu. (0 untuk melumpuhkan)", @@ -2065,7 +2063,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "Frasa Laluan APN", - "Push_debug": "Debug", "Push_enable": "Aktifkan", "Push_enable_gateway": "membolehkan Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/nl.i18n.json b/packages/rocketchat-i18n/i18n/nl.i18n.json index e3830a359672..0915abcd6811 100644 --- a/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -2028,8 +2028,6 @@ "Notification_Duration": "Notification Duration", "Notification_Mobile_Default_For": "Push mobiele meldingen voor", "Notifications": "meldingen", - "Notifications_Always_Notify_Mobile": "Mobiel altijd op de hoogte stellen", - "Notifications_Always_Notify_Mobile_Description": "Kies ervoor om het mobiele apparaat altijd op de hoogte te stellen, ongeacht de aanwezigheidsstatus.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Max Room-leden voordat u alle berichtmeldingen uitschakelt", "Notifications_Max_Room_Members_Description": "Max aantal leden in de ruimte wanneer meldingen voor alle berichten worden uitgeschakeld. Gebruikers kunnen nog steeds per kamerinstelling wijzigen om alle meldingen op individuele basis te ontvangen. (0 om uit te schakelen)", @@ -2208,7 +2206,6 @@ "Push_apn_dev_passphrase": "APN Ontwikkelaars Wachtwoord", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Wachtwoord", - "Push_debug": "Debug", "Push_enable": "Enable", "Push_enable_gateway": "Enable Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/no.i18n.json b/packages/rocketchat-i18n/i18n/no.i18n.json index 47c6a3715949..c4b046d0cdc1 100644 --- a/packages/rocketchat-i18n/i18n/no.i18n.json +++ b/packages/rocketchat-i18n/i18n/no.i18n.json @@ -1963,8 +1963,6 @@ "Notification_Duration": "Varsel varighet", "Notification_Mobile_Default_For": "Push Mobile Notifications For", "Notifications": "Påminnelser", - "Notifications_Always_Notify_Mobile": "Alltid varsle mobil", - "Notifications_Always_Notify_Mobile_Description": "Velg å alltid varsle mobilenhet uansett tilstedestatus.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Maksimumsrom medlemmer før deaktivere alle meldingsvarsler", "Notifications_Max_Room_Members_Description": "Maks antall medlemmer i rommet når meldinger for alle meldinger blir deaktivert. Brukere kan fortsatt endre seg per rominnstilling for å motta alle varsler på individuell basis. (0 for å deaktivere)", @@ -2132,7 +2130,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN-nøkkel", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Debug", "Push_enable": "Aktiver", "Push_enable_gateway": "Aktiver Gateway", "Push_gateway": "Inngangsport", diff --git a/packages/rocketchat-i18n/i18n/pl.i18n.json b/packages/rocketchat-i18n/i18n/pl.i18n.json index 30ef859e76a9..7e2a9e7649cf 100644 --- a/packages/rocketchat-i18n/i18n/pl.i18n.json +++ b/packages/rocketchat-i18n/i18n/pl.i18n.json @@ -2282,8 +2282,6 @@ "Notification_Duration": "Czas trwania powiadomienia", "Notification_Mobile_Default_For": "Prześlij powiadomienia mobilne dla", "Notifications": "Powiadomienia", - "Notifications_Always_Notify_Mobile": "Zawsze powiadamiaj urządzenie mobilne", - "Notifications_Always_Notify_Mobile_Description": "Wybierz, aby zawsze powiadamiać urządzenie mobilne, niezależnie od statusu obecności.", "Notifications_Duration": "Czas trwania notyfikacji", "Notifications_Max_Room_Members": "Maksymalna liczba członków pokoju przed wyłączeniem wszystkich powiadomień o wiadomościach", "Notifications_Max_Room_Members_Description": "Maksymalna liczba członków w pokoju, gdy powiadomienia o wszystkich wiadomościach zostaną wyłączone. Użytkownicy mogą nadal zmieniać ustawienia w zależności od pokoju, aby otrzymywać wszystkie powiadomienia indywidualnie. (0, aby wyłączyć)", @@ -2478,7 +2476,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "Klucz APN", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Debugowanie", "Push_enable": "Włącz", "Push_enable_gateway": "Włącz bramę", "Push_gateway": "Brama", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 22ed0b99fea2..d93b11241dc8 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -670,7 +670,7 @@ "close-others-livechat-room": "Sala de Omnichannel fechada", "Cloud_workspace_connected_without_account": "Seu workspace está agora conectado ao Rocket.Chat Cloud. Se desejar, você pode fazer o login no Rocket.Chat Cloud e associar seu workspace à sua conta do Cloud.", "close-others-livechat-room_description": "Permissão para fechar outras salas de Omnichannel", - "Close_room_description" : "Você está prestes a fechar este bate-papo. Você tem certeza que quer continuar?", + "Close_room_description": "Você está prestes a fechar este bate-papo. Você tem certeza que quer continuar?", "Closed": "Fechado", "Closed_At": "Encerrado em", "Closed_by_visitor": "Encerrado pelo visitante", @@ -2294,8 +2294,6 @@ "Notification_Duration": "Tempo de Duração da Notificação", "Notification_Mobile_Default_For": "Ativar Notificações Push Para", "Notifications": "Notificações", - "Notifications_Always_Notify_Mobile": "Sempre notificar celular", - "Notifications_Always_Notify_Mobile_Description": "Escolha sempre notificar o dispositivo móvel independentemente do status de presença.", "Notifications_Duration": "Notificações_Duração", "Notifications_Max_Room_Members": "Máximo de membros na sala antes de desativar todas as notificações de mensagens", "Notifications_Max_Room_Members_Description": "Número máximo de membros na sala quando as notificações para todas as mensagens são desabilitadas. Os usuários ainda podem alterar a configuração por sala para receber todas as notificações de forma individual. (0 para desativar)", @@ -2494,7 +2492,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Debug", "Push_enable": "Habilitar", "Push_enable_gateway": "Ativar Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/pt.i18n.json b/packages/rocketchat-i18n/i18n/pt.i18n.json index 1dd71c48a532..715088a58199 100644 --- a/packages/rocketchat-i18n/i18n/pt.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt.i18n.json @@ -2178,8 +2178,6 @@ "Notification_Duration": "Duração da notificação", "Notification_Mobile_Default_For": "Pressione notificações móveis para", "Notifications": "Notificações", - "Notifications_Always_Notify_Mobile": "Sempre notificar no telemóvel", - "Notifications_Always_Notify_Mobile_Description": "Escolha sempre notificar o dispositivo móvel independentemente do status de presença.", "Notifications_Duration": "Notificações_Duração", "Notifications_Max_Room_Members": "Máximo de membros na sala antes de desactivar todas as notificações de mensagens", "Notifications_Max_Room_Members_Description": "Número máximo de membros na sala quando as notificações para todas as mensagens são desabilitadas. Os utilizadores ainda podem alterar a configuração por sala para receber todas as notificações de forma individual. (0 para desactivar)", @@ -2370,7 +2368,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "Chave APN ", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Depurar", "Push_enable": "Habilitar", "Push_enable_gateway": "Habilitar Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/ro.i18n.json b/packages/rocketchat-i18n/i18n/ro.i18n.json index 302daa372c8b..f022524838f6 100644 --- a/packages/rocketchat-i18n/i18n/ro.i18n.json +++ b/packages/rocketchat-i18n/i18n/ro.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Durata notificării", "Notification_Mobile_Default_For": "Împingeți notificările mobile pentru", "Notifications": "notificările", - "Notifications_Always_Notify_Mobile": "Notificați întotdeauna despre mobil", - "Notifications_Always_Notify_Mobile_Description": "Alegeți să notificați întotdeauna dispozitivul mobil indiferent de starea prezenței.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Membrii camerei maximă înainte de a dezactiva toate notificările mesajelor", "Notifications_Max_Room_Members_Description": "Numărul maxim de membri în cameră, când notificările pentru toate mesajele sunt dezactivate. Utilizatorii pot schimba în continuare setările camerei pentru a primi toate notificările în mod individual. (0 pentru a dezactiva)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Depanare", "Push_enable": "Activează", "Push_enable_gateway": "Activați Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json index d4c4d8779ef6..f6fbbc1689b2 100644 --- a/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -2377,8 +2377,6 @@ "Notification_RequireInteraction_Description": "Работает только с версиями браузера Chrome > 50. Использует параметр requireInteraction для отображения уведомления рабочего стола на неопределенное время до тех пор, пока пользователь не будет взаимодействовать с ним.", "Notification_Mobile_Default_For": "Включить push уведомления для", "Notifications": "Уведомления", - "Notifications_Always_Notify_Mobile": "Всегда отправлять уведомление на мобильное устройство", - "Notifications_Always_Notify_Mobile_Description": "Выберите, чтобы всегда уведомлять мобильное устройство независимо от состояния присутствия.", "Notifications_Duration": "Длительность показа уведомлений", "Notifications_Max_Room_Members": "Максимальное количество участников комнаты до отключения всех текстовых уведомлений", "Notifications_Max_Room_Members_Description": "Максимальное количество участников в комнате, превышение которого отключает все уведомления. Участники в индивидуальном порядке по-прежнему могут изменять настройки комнаты, чтобы получать все уведомления. 0 для отключения.", @@ -2579,9 +2577,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "Ключ APN", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Отладка", - "Push_send_interval": "Интервал для проверки очереди на новые push-уведомления", - "Push_send_batch_size": "Размер партии для обработки каждого тика", "Push_enable": "Включить", "Push_enable_gateway": "Включить шлюз", "Push_gateway": "Шлюз", diff --git a/packages/rocketchat-i18n/i18n/sk-SK.i18n.json b/packages/rocketchat-i18n/i18n/sk-SK.i18n.json index cb79f7bcb9c5..3800a3b379a5 100644 --- a/packages/rocketchat-i18n/i18n/sk-SK.i18n.json +++ b/packages/rocketchat-i18n/i18n/sk-SK.i18n.json @@ -1888,8 +1888,6 @@ "Notification_Duration": "Doba oznámenia", "Notification_Mobile_Default_For": "Push Mobile oznámenia pre", "Notifications": "oznámenia", - "Notifications_Always_Notify_Mobile": "Vždy upozorňujte na mobilné zariadenia", - "Notifications_Always_Notify_Mobile_Description": "Vyberte, či chcete vždy oznámiť mobilné zariadenie bez ohľadu na stav prítomnosti.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Max. Členovia izby pred vypnutím všetkých upozornení správ", "Notifications_Max_Room_Members_Description": "Maximálny počet členov v miestnosti, kedy sú upozornenia na všetky správy zakázané. Užívatelia sa môžu stále meniť za každé nastavenie miestnosti, aby dostali všetky upozornenia na individuálnom základe. (0 pre vypnutie)", @@ -2057,7 +2055,6 @@ "Push_apn_dev_passphrase": "Heslo APN Dev", "Push_apn_key": "Kľúč APN", "Push_apn_passphrase": "Heslo APN", - "Push_debug": "ladiť", "Push_enable": "umožniť", "Push_enable_gateway": "Povoliť bránu", "Push_gateway": "brána", diff --git a/packages/rocketchat-i18n/i18n/sl-SI.i18n.json b/packages/rocketchat-i18n/i18n/sl-SI.i18n.json index 56fae77199aa..14dad82b5e7e 100644 --- a/packages/rocketchat-i18n/i18n/sl-SI.i18n.json +++ b/packages/rocketchat-i18n/i18n/sl-SI.i18n.json @@ -1885,8 +1885,6 @@ "Notification_Duration": "Trajanje obvestil", "Notification_Mobile_Default_For": "Potisni mobilna obvestila za", "Notifications": "Obvestila", - "Notifications_Always_Notify_Mobile": "Vedno obvesti mobilni telefon", - "Notifications_Always_Notify_Mobile_Description": "Izberi stalno prejemanje mobilnih obvestil, ne glede na prisotnost.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Največje število uporabnikov v sobi preden so onemogočena vsa obvestila o sporočilih", "Notifications_Max_Room_Members_Description": "Največje število uporabnikov v sobi, ko se obvestila za vsa sporočila onemogočijo. Uporabnik lahko še vedno spremeni nastavitve posamezne sobe, da prejme vsa obvestila posamezno. (0 za onemogoči)", @@ -2054,7 +2052,6 @@ "Push_apn_dev_passphrase": "APN geslo razvijalca", "Push_apn_key": "APN ključ", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Odpravi napake", "Push_enable": "Omogoči", "Push_enable_gateway": "Omogoči prehod", "Push_gateway": "Prehod", diff --git a/packages/rocketchat-i18n/i18n/sq.i18n.json b/packages/rocketchat-i18n/i18n/sq.i18n.json index 304e8177dab8..459a9a094945 100644 --- a/packages/rocketchat-i18n/i18n/sq.i18n.json +++ b/packages/rocketchat-i18n/i18n/sq.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Kohëzgjatja e njoftimit", "Notification_Mobile_Default_For": "Push Njoftimet Mobile për", "Notifications": "Njoftime", - "Notifications_Always_Notify_Mobile": "Gjithmonë njoftoni celularin", - "Notifications_Always_Notify_Mobile_Description": "Zgjidhni të njoftoni gjithmonë pajisjen celulare pa marrë parasysh statusin e prezencës.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Anëtarët e Dhomës Maksimale para çaktivizimit të të gjitha njoftimeve të mesazhit", "Notifications_Max_Room_Members_Description": "Numri maksimal i anëtarëve në dhomë kur njoftimet për të gjitha mesazhet çaktivizohen. Përdoruesit mund të ndryshojnë ende për vendosjen e dhomës për të marrë të gjitha njoftimet në baza individuale. (0 për të çaktivizuar)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN Dev parafrazën", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN parafrazën", - "Push_debug": "rregulloj", "Push_enable": "Aktivizoj", "Push_enable_gateway": "Aktivizo Gateway", "Push_gateway": "portë", diff --git a/packages/rocketchat-i18n/i18n/sr.i18n.json b/packages/rocketchat-i18n/i18n/sr.i18n.json index 36cc8e6d6046..b08f75b6b66c 100644 --- a/packages/rocketchat-i18n/i18n/sr.i18n.json +++ b/packages/rocketchat-i18n/i18n/sr.i18n.json @@ -8,22 +8,17 @@ "@username": "@корисничко име", "@username_message": "@ корисничко име ", "Accept": "Прихвати", - "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Прихватите долазеће захтјеве ливецхат чак и ако нема онлине агената", - "Accept_with_no_online_agents": "Прихватите без онлајн агента", - "access-mailer": "Приступ Маилер екрану", - "access-mailer_description": "Дозвола за слање масовног е-поште свим корисницима.", - "access-permissions": "Екран дозвола приступа", + "access-mailer_description": "Дозвола за слање масовне е-поште свим корисницима.", + "access-permissions": "Приказ дозвола приступа", "access-permissions_description": "Измени дозволе за различите улоге.", "Access_not_authorized": "Приступ није овлашћен", "Access_Token_URL": "УРЛ адреса приступног токена", "Accessing_permissions": "Приступне дозволе", "Account_SID": "SID налога", "Accounts": "Налози", - "Accounts_Admin_Email_Approval_Needed_Default": "

Корисник [име] ([емаил]) је регистрован.

Молимо вас да проверите \"Администратор -_ЛХ_ХТМЛ_ЕНД> Корисници\" да бисте га активирали или избрисали.

", - "Accounts_Admin_Email_Approval_Needed_Subject_Default": "Нови корисник је регистрован и потребан је одобрење", - "Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "

Корисник [име] ([емаил]) је регистрован. \">

Разлог: [разлог]

Молимо вас да проверите \"Управа -_ЛХ_ХТМЛ_ЕНД> Корисници\" за активирање или брисање га.

", - "Accounts_AllowAnonymousRead": "Дозволи Анонимно читање", - "Accounts_AllowAnonymousWrite": "Дозволи Анонимни Врите", + "Accounts_Admin_Email_Approval_Needed_Subject_Default": "Нови корисник је регистрован и потребно је одобрење", + "Accounts_AllowAnonymousRead": "Дозволи анонимно читање", + "Accounts_AllowAnonymousWrite": "Дозволи анонимно писање", "Accounts_AllowDeleteOwnAccount": "Дозволи корисницима да обришу свој налог", "Accounts_AllowedDomainsList": "Листа дозвољених домена", "Accounts_AllowedDomainsList_Description": "Зарезом одвојена листа дозвољених домена", @@ -33,34 +28,28 @@ "Accounts_AllowUserAvatarChange": "Дозволите промену корисничког аватара", "Accounts_AllowUsernameChange": "Дозволи промену корисничког имена", "Accounts_AllowUserProfileChange": "Дозволи промену корисничког профила", - "Accounts_AvatarCacheTime": "Аватар цацхе тиме", - "Accounts_AvatarCacheTime_description": "Број секунди за који је протоколу за протокол речено да кешира слике слика.", "Accounts_AvatarResize": "Промени величину аватара", "Accounts_AvatarSize": "Величина аватара", "Accounts_BlockedDomainsList": "Листа блокираних домена", "Accounts_BlockedDomainsList_Description": "Зарезом одвојена листа блокираних домена", "Accounts_BlockedUsernameList": "Листа блокираних корисничких имена", "Accounts_BlockedUsernameList_Description": "Зарезом одвојена листа блокираних корисничких имена (небитна величина слова)", - "Accounts_CustomFields_Description": "Требало би бити валидан ЈСОН гдје су кључеви имена поља која садрже речник за подешавања поља. Пример:
{\n\"улога\": {\n\"тип\": \"изаберите\",\n\"дефаултВалуе\": \"студент\",\n\"опције\": [\"наставник\", \"студент\"],\n\"обавезно\": тачно,\n\"модифиРецордФиелд\": {\n\"арраи\": труе,\n\"поље\": \"улога\"\n}\n},\n\"твиттер\": {\n\"тип\": \"текст\",\n\"обавезно\": тачно,\n\"минЛенгтх\": 2,\n\"макЛенгтх\": 10\n}\n} ", "Accounts_CustomFieldsToShowInUserInfo": "Прилагођена поља која ће се приказати у корисничким информацијама", "Accounts_Default_User_Preferences": "Подразумеване корисничке поставке", - "Accounts_Default_User_Preferences_audioNotifications": "Подразумевана обавештења о аудио обавештењима", - "Accounts_Default_User_Preferences_desktopNotifications": "Подразумевана обавештења о радној површини", - "Accounts_Default_User_Preferences_mobileNotifications": "Подразумевана обавештења о мобилним нотификацијама", - "Accounts_Default_User_Preferences_not_available": "Није успело преузимати корисничке опције, јер их још увек није подесио корисник", - "Accounts_DefaultUsernamePrefixSuggestion": "Подразумевано предлошено корисничко име", + "Accounts_Default_User_Preferences_desktopNotifications": "Подразумевана обавештења на радној површини", + "Accounts_DefaultUsernamePrefixSuggestion": "Предефинисани предлог префикса корисничког имена", "Accounts_denyUnverifiedEmail": "Одбиј непотврђене адресе е-поште", - "Accounts_Email_Activated": "[име]

Ваш налог је активиран.

", - "Accounts_Email_Activated_Subject": "Рачун активиран", - "Accounts_Email_Approved": "[име]

Ваш налог је одобрен.

", - "Accounts_Email_Approved_Subject": "Рачун одобрен", - "Accounts_Email_Deactivated": "[име]

Ваш налог је деактивиран.

", + "Accounts_Email_Activated": "[name]

Ваш налог је активиран.

", + "Accounts_Email_Activated_Subject": "Налог активиран", + "Accounts_Email_Approved": "[name]

Ваш налог је одобрен.

", + "Accounts_Email_Approved_Subject": "Налог одобрен", + "Accounts_Email_Deactivated": "[name]

Ваш налог је деактивиран.

", "Accounts_Enrollment_Email_Default": "

Добродошли на[Site_Name]

Иди на [Site_URL] и испробај најбоље решење за ћаскање отвореног кода које је тренутно доступно!

", - "Accounts_Email_Deactivated_Subject": "Рачун је деактивиран", + "Accounts_Email_Deactivated_Subject": "Налог деактивиран", "Accounts_EmailVerification": "Потврда е-поште", "Accounts_EmailVerification_Description": "Потврдите да имате исправне SMTP параметре да би сте користили ову могућност", "Accounts_Enrollment_Email_Subject_Default": "Добродошли на [Site_Name]", - "Accounts_ForgetUserSessionOnWindowClose": "Заборавите корисничко сесије на прозору Затвори", + "Accounts_ForgetUserSessionOnWindowClose": "Заборави корисничку сесију по затварању прозора", "Accounts_Iframe_api_method": "Api метода", "Accounts_Iframe_api_url": "API адреса", "Accounts_iframe_enabled": "Омогућено", @@ -74,7 +63,6 @@ "Accounts_OAuth_Custom_Enable": "Омогући", "Accounts_OAuth_Custom_id": "Id", "Accounts_OAuth_Custom_Identity_Path": "Путања идентитета", - "Accounts_OAuth_Custom_Identity_Token_Sent_Via": "Идентификован жетон Послато преко", "Accounts_OAuth_Custom_Login_Style": "Стил пријаве", "Accounts_OAuth_Custom_Merge_Users": "Споји кориснике", "Accounts_OAuth_Custom_Scope": "Обим", @@ -83,33 +71,17 @@ "Accounts_OAuth_Custom_Token_Sent_Via": "Токен послат путем", "Accounts_OAuth_Custom_Username_Field": "Поље корисничког имена", "Accounts_OAuth_Custom_Roles_Claim": "Име поља улоге/групе", - "Accounts_OAuth_Drupal": "Друпал Логин Енаблед", - "Accounts_OAuth_Drupal_callback_url": "Друпал оАутх2 Редирецт УРИ", - "Accounts_OAuth_Drupal_id": "Друпал оАутх2 Клијент ИД", - "Accounts_OAuth_Drupal_secret": "Друпал оАутх2 Цлиент Сецрет", - "Accounts_OAuth_Github": "ОАутх Омогућено", - "Accounts_OAuth_GitHub_Enterprise": "ОАутх Омогућено", "Accounts_OAuth_GitHub_Enterprise_id": "Ид клијента", "Accounts_OAuth_GitHub_Enterprise_secret": "Тајна клијента", "Accounts_OAuth_Github_id": "Ид клијента", "Accounts_OAuth_Github_secret": "Тајна клијента", - "Accounts_OAuth_Gitlab": "ОАутх Омогућено", - "Accounts_OAuth_Gitlab_identity_path": "идентитет Пут", + "Accounts_OAuth_Gitlab_identity_path": "Путања до идентитета", "Accounts_OAuth_Gitlab_secret": "Тајна клијента", - "Accounts_OAuth_Nextcloud": "ОАутх Омогућено", - "Accounts_OAuth_Nextcloud_secret": "klijent Тајна", - "Accounts_OAuth_Proxy_host": "Проки Хост", - "Accounts_OAuth_Proxy_services": "Проки Сервицес", - "Accounts_OAuth_Tokenpass": "Токенпасс Логин", - "Accounts_OAuth_Tokenpass_callback_url": "Токенпасс Цаллбацк УРЛ", - "Accounts_OAuth_Tokenpass_id": "Токенпасс Ид", - "Accounts_OAuth_Tokenpass_secret": "Токенпасс Сецрет", - "Accounts_OAuth_Wordpress_authorize_path": "овластити Патх", - "Accounts_OAuth_Wordpress_identity_path": "идентитет Пут", - "Accounts_OAuth_Wordpress_identity_token_sent_via": "Идентификован жетон Послато преко", + "Accounts_OAuth_Wordpress_authorize_path": "Путања до ауторизације", + "Accounts_OAuth_Wordpress_identity_path": "Путања до идентитета", "Accounts_OAuth_Wordpress_scope": "Обим", - "Accounts_OAuth_Wordpress_server_type_custom": "Обичај", - "Accounts_OAuth_Wordpress_server_type_wordpress_com": "Вордпресс.цом", + "Accounts_OAuth_Wordpress_server_type_custom": "Прилагођено", + "Accounts_OAuth_Wordpress_server_type_wordpress_com": "Wordpress.com", "Accounts_OAuth_Wordpress_server_type_wp_oauth_server": "ВП ОАутх Сервер Плугин", "Accounts_OAuth_Wordpress_token_path": "токен Пут", "Accounts_Password_Policy_AtLeastOneLowercase": "Најмање једна мала слова", @@ -188,101 +160,79 @@ "Alerts": "Упозорења", "Alias": "Псеудоним", "Alias_Format": "Алиас Формат", - "Alias_Format_Description": "Увезите поруке из Слацк-а са алијасом; % с је замењено корисничким именом корисника. Ако је празно, неће се користити ништа.", + "Alias_Format_Description": "Увезите поруке из Слацк-а са алијасом; %s је замењено корисничким именом корисника. Ако је празно, неће се користити ништа.", "Alias_Set": "Алиас Сет", "All": "Све", - "All_added_tokens_will_be_required_by_the_user": "Корисник ће тражити све додатне токове", "All_channels": "Сви канали", "All_logs": "Сви записи", "All_messages": "Све поруке", "All_users": "Сви корисници", "All_users_in_the_channel_can_write_new_messages": "Сви корисници канала могу писати нове поруке", - "Allow_switching_departments": "Дозволите посетиоцу да пребаце одјела", - "Allow_Marketing_Emails": "Дозволи маркетинг емаилс", "Alphabetical": "Абецедно", - "Always_open_in_new_window": "Увек отворите у новом прозору", - "Analytics": "аналитика", + "Always_open_in_new_window": "Увек отвори у новом прозору", + "Analytics": "Аналитика", "Analytics_features_enabled": "Активиране могућности", - "Analytics_Google": "Гугл анализе", + "Analytics_Google": "Гугл аналитика", "Analytics_Google_id": "ИД праћења", "and": "и", "And_more": "И још __length__ ", "Animals_and_Nature": "Животиње и природа", "Announcement": "Најава", "API": "АПИ", - "API_Allow_Infinite_Count": "Дозволите да добијете све", - "API_Allow_Infinite_Count_Description": "Да ли би требало дозволити да се позивима на РЕСТ АПИ врати све у једном позиву?", "API_Analytics": "Аналитика", - "API_CORS_Origin": "ЦОРС Оригин", - "API_Default_Count": "Дефаулт Цоунт", - "API_Default_Count_Description": "Подразумевано бројање за РЕСТ АПИ резултате ако потрошач није обезбедио.", - "API_Drupal_URL": "УРЛ Друпал сервера", - "API_Drupal_URL_Description": "Пример: хттпс://домаин.цом (искључујући траилинг цртицу)", + "API_Drupal_URL_Description": "Пример: https://domain.com (искључујући крајњу косу црту)", "API_Embed": "Убаци преглед линкова", - "API_Embed_Description": "Да ли су омогућени прегледи уграђене везе или не када корисник поставља линк на веб локацију.", - "API_Embed_UserAgent": "Ставите захтев за корисника", - "API_EmbedCacheExpirationDays": "Ембед Цацхе Екпиратион Даис", + "API_Embed_Description": "Да ли су омогућени прегледи уграђене везе када корисник поставља линк на веб локацију.", "API_EmbedDisabledFor_Description": "Зарезом одвојена листа корисничких имена којима је онемогућен преглед убачених линкова.", "API_EmbedSafePorts": "Безбедни портови", "API_EmbedSafePorts_Description": "Зарезом одвојена листа портова дозвољених за преглед.", - "API_Enable_CORS": "Омогући ЦОРС", "API_Enable_Direct_Message_History_EndPoint": "Омогући крајњу тачку историје директних порука", - "API_Enable_Direct_Message_History_EndPoint_Description": "Ово омогућава `/ апи / в1 / им.хистори.отхерс` који дозвољава преглед директних порука које су послали други корисници да позивалац није дио.", "API_Enable_Shields": "Омогући штитове", - "API_Enable_Shields_Description": "Омогућити штитове доступне на `/ апи / в1 / схиелд.свг`", "API_GitHub_Enterprise_URL": "УРЛ адреса сервера", "API_GitHub_Enterprise_URL_Description": "Пример: http://domain.com (без косе црте на крају)", - "API_Shield_Types": "Врсте штита", - "API_Shield_Types_Description": "Врсте штитова који омогућавају листу одвојених зарезима, изаберите `онлине`,` канал` или `*` за све", + "API_Shield_Types": "Врсте штитова", + "API_Shield_Types_Description": "Врсте омогућених штитова у облику листе раздвојене зарезима, изаберите `online`,`channel` или `*` за све", "API_Token": "АПИ токен", - "API_Tokenpass_URL": "УРЛ сервера Токенпасс", - "API_Tokenpass_URL_Description": "Пример: хттпс://домаин.цом (искључујући траилинг цртицу)", - "API_Upper_Count_Limit": "Максимални рекордни износ", - "API_Upper_Count_Limit_Description": "Који је максималан број записа који РЕСТ АПИ треба да врати (када није неограничен)?", + "API_Tokenpass_URL_Description": "Пример: https://domain.com (искључујући крајњу косу црту)", + "API_Upper_Count_Limit": "Максимално записа", "API_User_Limit": "Ограничење корисника за додавање свих корисника у канал", - "App_author_homepage": "аутор хомепаге", + "App_author_homepage": "веб страница аутора", "App_Information": "Информације о апликацији", "App_Installation": "Инсталација апликације", "App_status_auto_enabled": "Омогућено", "App_status_compiler_error_disabled": "Онемогућено: грешка компајлера", "App_status_constructed": "Израђено", "App_status_disabled": "Онемогућено", - "App_status_error_disabled": "Дисаблед: Унцаугхт Еррор", - "App_status_initialized": "Иницијализован", + "App_status_initialized": "Иницијализовано", "App_status_invalid_settings_disabled": "Онемогућено: неопходна конфигурација", "App_status_manually_disabled": "Онемогућено: ручно", "App_status_manually_enabled": "Омогућено", "App_status_unknown": "Непознат", - "App_support_url": "подршка урл", + "App_support_url": "адреса подршке", "Appearance": "Изглед", "Application_added": "Апликација је додата", "Application_Name": "Име апликације", "Application_updated": "Аппликација ажурирана", - "Apply": "Применити", + "Apply": "Примени", "Apply_and_refresh_all_clients": "Примени и освежи све клијенте", "Apps": "Програми", - "Apps_Framework_enabled": "Омогућите Апп Фрамеворк", "Apps_Settings": "Подешавања апликације", - "Apps_WhatIsIt": "Апликације: Шта су они?", - "Apps_WhatIsIt_paragraph1": "Нова икона у области администрације! Шта ово значи и које су апликације?", - "Apps_WhatIsIt_paragraph2": "Прво, Аппс у овом контексту не односе се на мобилне апликације. У ствари, најбоље би било размишљати о њима у смислу плугина или напредних интеграција.", - "Apps_WhatIsIt_paragraph3": "Друго, то су динамичне скрипте или пакети који ће вам омогућити да прилагодите своју Роцкет.Цхат инстанцу без потребе да отворите кодни бајт. Али немојте имати на уму, ово је нова функција и због тога можда није 100% стабилна. Такође, још увијек развијамо скуп функција тако да се у овом тренутку не може све прилагодити. За више информација о почетку развоја апликације, идите овде да прочитате:", - "Apps_WhatIsIt_paragraph4": "Али, уз то, ако сте заинтересовани да омогућите ову функцију и покушате то, кликните овдје да бисте омогућили систем Аппс.", + "Apps_WhatIsIt": "Апликације: Шта су оне?", + "Apps_WhatIsIt_paragraph1": "Нова икона у области администрације! Шта ово значи и шта су апликације?", "Archive": "Архива", - "archive-room": "Архива соба", + "archive-room": "Архивирај собу", "archive-room_description": "Дозвола за архивирање канала", "are_also_typing": "такође куцају", "are_typing": "куцају", "Are_you_sure": "Да ли сте сигурни?", "Are_you_sure_you_want_to_delete_your_account": "Да ли сте сигурни да желите да избришете свој налог?", - "Are_you_sure_you_want_to_disable_Facebook_integration": "Да ли сте сигурни да желите онемогућити интеграцију Фацебоок-а?", + "Are_you_sure_you_want_to_disable_Facebook_integration": "Да ли сигурно желите да онемогућите интеграцију са Фејсбуком?", "assign-admin-role": "Додели улогу администратора", - "assign-admin-role_description": "Дозвола доделити улогу администратора другим корисницима", + "assign-admin-role_description": "Дозвола за додељивање улоге администратора другим корисницима", "Assign_admin": "Додељивање администратора", "at": "у", "At_least_one_added_token_is_required_by_the_user": "Корисник захтева бар један додатни токен", - "AtlassianCrowd": "Атлассиан Цровд", - "Attachment_File_Uploaded": "Филе Уплоадед", + "Attachment_File_Uploaded": "Датотека постављена", "Attribute_handling": "Управљање атрибутима", "Audio": "Аудио", "Audio_message": "Аудио порука", @@ -359,12 +309,9 @@ "CAS_base_url": "База базе података ССО", "CAS_base_url_Description": "Основни УРЛ вашег спољног сервиса ССО: хттпс: //ссо.екампле.ундеф/ссо/", "CAS_button_color": "Боја дугмета за пријављивање", - "CAS_button_label_color": "Пријава Буттон Тект Цолор", - "CAS_button_label_text": "Лабел Буттон Лабел", + "CAS_button_label_color": "Боја текста дугмета за пријављивање", "CAS_enabled": "Омогућено", "CAS_Login_Layout": "Распоред пријаве ЦАС-а", - "CAS_login_url": "ССО Логин УРЛ", - "CAS_login_url_Description": "УРЛ за пријаву ваше спољне услуге ССО на: хттпс: //ссо.екампле.ундеф/ссо/логин", "CAS_popup_height": "Пријава Попуп висина", "CAS_popup_width": "Пријава Попуп Ширина", "CAS_Sync_User_Data_Enabled": "Алваис Синц Усер Дата", @@ -373,57 +320,43 @@ "CAS_Sync_User_Data_FieldMap_Description": "Користите овај ЈСОН улаз да бисте креирали интерне атрибуте (кључ) из вањских атрибута (вриједности). Спољна имена атрибута затворена са '%' ће интерполирати у низовима вриједности.
Пример, `{\" емаил \":\"% емаил% \",\" наме \":\"% фирстнаме%,% ластнаме% \"}`

Мапа атрибута је увек интерполирана. У ЦАС 1.0 само је доступан атрибут `усернаме`. Доступни интерни атрибути су: корисничко име, име, емаил, собе; собе су комеморисане листе соба које се придружују креирању корисника нпр: {\"собе\": \"% тим%,% одељење%\"} ће се придружити корисницима ЦАС-а приликом креирања њиховом каналу и одељењу.", "CAS_version": "ЦАС верзија", "CAS_version_Description": "Користите само подржану верзију ЦАС коју подржава ЦАС ЦАС ССО.", - "CDN_PREFIX": "ЦДН Префикс", - "Certificates_and_Keys": "Сертификати и тастери", + "Certificates_and_Keys": "Сертификати и кључеви", "Change_Room_Type": "Промена типа собе", - "Changing_email": "Промена е-маил", + "Changing_email": "Промена е-поште", "channel": "канал", - "Channel": "канал", - "Channel_already_exist": "Канал '#%s \"већ постоји.", + "Channel": "Канал", + "Channel_already_exist": "Канал '#%s ' већ постоји.", "Channel_already_exist_static": "Канал већ постоји.", "Channel_already_Unarchived": "Channel са именом `#%s` је већ у неархивираном стању", "Channel_Archived": "Channel са именом `#%s` је успешно архивиран", "Channel_created": "Channel `#%s` је креиран.", "Channel_doesnt_exist": "Канал `#%s` не постоји.", "Channel_name": "Име канала", - "Channel_Name_Placeholder": "Молимо унесите име канала ...", - "Channel_to_listen_on": "Канал за слушање", + "Channel_Name_Placeholder": "Унеси име канала...", "Channels": "Канали", "Channels_are_where_your_team_communicate": "Канали су тамо где ваш тим комуницира", "Channels_list": "Листа јавних канала", - "Chat_Now": "Цхат Сада", - "Chatpal_AdminPage": "Цхатпал Админ Страница", - "Chatpal_All_Results": "све", + "Chat_Now": "Ћаскај сада", + "Chatpal_All_Results": "Све", "Chatpal_API_Key": "АПИ кључ", - "Chatpal_API_Key_Description": "Још немате <ЛХ_ХТМЛ_ОПЕН_стронг_ЛХ_ХТМЛ_ЕНД> кључ за АПИ <ЛХ_ХТМЛ_ЦЛОСЕ_стронг_ЛХ_ХТМЛ_ЕНД>? <ЛХ_ХТМЛ_ОПЕН_а цласс = \"цхатпал-админ-линк\" хреф = \"./ админ / цхатпал\" _ЛХ_ХТМЛ_ЕНД> Гет оне! <ЛХ_ХТМЛ_ЦЛОСЕ_а_ЛХ_ХТМЛ_ЕНД>", - "Chatpal_Backend": "Бацкенд Типе", - "Chatpal_Backend_Description": "Изаберите ако желите да користите Цхатпал као услугу или као инсталацију на лицу места", "Chatpal_Base_URL": "Основни УРЛ", - "Chatpal_Base_URL_Description": "Пронађите неки опис како покренути локалну инстанцу <ЛХ_ХТМЛ_ОПЕН_а циљ = \"_ празно\" цласс = \"цхатпал-админ-линк\" хреф = \"хттпс://гитхуб.цом/цхатпал/цхатпал-сеарцх-стандалоне\" _ЛХ_ХТМЛ_ЕНД> на гитуб <ЛХ_ХТМЛ_ЦЛОСЕ_а_ЛХ_ХТМЛ_ЕНД >. УРЛ адреса мора бити апсолутна и показивати језгру ћаскања, нпр. хттп: // лоцалхост: 8983 / солр / цхатпал.", - "Chatpal_Batch_Size": "Индек Батцх Сизе", - "Chatpal_Batch_Size_Description": "Величина серије индексних докумената (на боотстраппинг)", "Chatpal_create_key": "Креирај кључ", - "Chatpal_created_key_successfully": "АПИ-Кеи креиран успешно", "Chatpal_Default_Result_Type": "Подразумевани тип резултата", - "Chatpal_Default_Result_Type_Description": "Дефинише који тип резултата приказује резултат. Све значи да је обезбеђен преглед свих врста.", + "Chatpal_Default_Result_Type_Description": "Дефинише који тип резултата је приказан. Све значи да је обезбеђен преглед свих врста.", "Chatpal_Email_Address": "Адреса Е-поште", - "Chatpal_ERROR_Email_must_be_set": "Емаил мора бити подешен", - "Chatpal_ERROR_Email_must_be_valid": "Е-маил мора бити валидан", - "Chatpal_ERROR_TAC_must_be_checked": "Услове и услови морају бити проверени", + "Chatpal_ERROR_Email_must_be_set": "Е-пошта мора бити подешена", + "Chatpal_ERROR_Email_must_be_valid": "Е-пошта мора бити исправна", "Chatpal_ERROR_username_already_exists": "Корисничко име већ постоји", - "Chatpal_Get_more_information_about_chatpal_on_our_website": "Сазнајте више о Цхатпал-у на <ЛХ_ХТМЛ_ОПЕН_а таргет = \"_ бланк\" хреф = \"хттп://цхатпал.ио\" _ЛХ_ХТМЛ_ЕНД> хттп://цхатпал.ио <ЛХ_ХТМЛ_ЦЛОСЕ_а_ЛХ_ХТМЛ_ЕНД>!", - "Chatpal_go_to_message": "јумп", - "Chatpal_go_to_room": "јумп", - "Chatpal_go_to_user": "Пошаљите директну поруку", - "Chatpal_HTTP_Headers": "Хттп Хеадерс", - "Chatpal_HTTP_Headers_Description": "Листа ХТТП заглавља, један заглавље по линији. Формат: име: вредност", + "Chatpal_go_to_message": "Скочи", + "Chatpal_go_to_room": "Скочи", + "Chatpal_go_to_user": "Пошаљи директну поруку", "Chatpal_Main_Language": "Главни језик", "Chatpal_Main_Language_Description": "Језик који се највише користи у разговорима", "Chatpal_Messages": "Поруке", "Chatpal_Messages_Only": "Поруке", "Chatpal_More": "Више", "Chatpal_No_Results": "Нема резултата", - "Chatpal_no_search_results": "Без резултата", + "Chatpal_no_search_results": "Нема резултата", "Chatpal_one_search_result": "Пронађено 1 резултат", "Chatpal_Rooms": "Собе", "Chatpal_run_search": "Претрага", @@ -641,7 +574,7 @@ "Country_Namibia": "Намибија", "Country_Nauru": "Науру", "Country_Nepal": "Непал", - "Country_Netherlands": "Низоземска", + "Country_Netherlands": "Холандија", "Country_Netherlands_Antilles": "Холандски Антили", "Country_New_Caledonia": "Нова Каледонија", "Country_New_Zealand": "Нови Зеланд", @@ -704,7 +637,7 @@ "Country_Tanzania_United_Republic_of": "Танзанија, Уједињена Република", "Country_Thailand": "Тајланд", "Country_Timor_leste": "Тимор-лесте", - "Country_Togo": "Да иде", + "Country_Togo": "Того", "Country_Tokelau": "Токелау", "Country_Tonga": "Тонга", "Country_Trinidad_and_Tobago": "Тринидад и Тобаго", @@ -717,7 +650,7 @@ "Country_Ukraine": "Украјина", "Country_United_Arab_Emirates": "Уједињени арапски Емирати", "Country_United_Kingdom": "Велика Британија", - "Country_United_States": "Америка", + "Country_United_States": "Сједињене Америчке Државе", "Country_United_States_Minor_Outlying_Islands": "Мањих острваца Сједињених Држава", "Country_Uruguay": "Уругвај", "Country_Uzbekistan": "Узбекистан", @@ -727,7 +660,7 @@ "Country_Virgin_Islands_British": "Дјевице, Британци", "Country_Virgin_Islands_US": "Виргин Исландс, У.С.", "Country_Wallis_and_Futuna": "Валлис и Футуна", - "Country_Western_Sahara": "западна Сахара", + "Country_Western_Sahara": "Западна Сахара", "Country_Yemen": "Јемен", "Country_Zambia": "Замбија", "Country_Zimbabwe": "Зимбабве", @@ -749,38 +682,36 @@ "Created_at_s_by_s_triggered_by_s": "Креиран је на %s од стране %s покренут од стране %s", "CRM_Integration": "ЦРМ интеграција", "CROWD_Reject_Unauthorized": "Одбијте неовлашћено", - "Crowd_sync_interval_Description": "Интервал између синхронизације. Пример \"сваких 24 сата\" или \"првог дана у недељи\", више примјера у [Црон Тект Парсер] (хттп://бункат.гитхуб.ио/латер/парсерс.хтмл#тект)", + "Crowd_sync_interval_Description": "Интервал између синхронизације. Пример \"свака 24 сата\" или \"првог дана у недељи\", више примера на [Cron Text Parser](http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Тренутна ћаскања", "Current_Status": "Тренутни статус", "Custom": "Произвољно", - "Custom CSS": "цустом ЦСС", - "Custom_agent": "Царински агент", - "Custom_emoji": "Цустом Емоји", - "Custom_Emoji": "Цустом Емоји", - "Custom_Emoji_Add": "Додајте нове емоји", - "Custom_Emoji_Added_Successfully": "Прилагођени емоји су успешно додати", - "Custom_Emoji_Delete_Warning": "Брисање емоји не може бити поништено.", - "Custom_Emoji_Error_Invalid_Emoji": "Неважећи емоји", - "Custom_Emoji_Error_Name_Or_Alias_Already_In_Use": "Уобичајени емоји или један од његових алиас већ се користи.", - "Custom_Emoji_Has_Been_Deleted": "Кориснички емоји је избрисан.", - "Custom_Emoji_Info": "Цустом Емоји Инфо", - "Custom_Emoji_Updated_Successfully": "Прилагођени емоји су успешно ажурирани", + "Custom CSS": "Прилагођени CSS", + "Custom_agent": "Прилагођени агент", + "Custom_emoji": "Прилагођени емотикони", + "Custom_Emoji": "Прилагођени емотикони", + "Custom_Emoji_Add": "Додај нови емотикон", + "Custom_Emoji_Added_Successfully": "Прилагођени емотикон успешно додат", + "Custom_Emoji_Delete_Warning": "Брисање емотикона не може бити поништено.", + "Custom_Emoji_Error_Invalid_Emoji": "Неисправан емотикон", + "Custom_Emoji_Error_Name_Or_Alias_Already_In_Use": "Прилагођени емотикон или један од његових псеудонима је већ у употреби.", + "Custom_Emoji_Has_Been_Deleted": "Прилагођени емотикон је обрисан.", + "Custom_Emoji_Info": "Информације о прилагођеном емотикону", + "Custom_Emoji_Updated_Successfully": "Прилагођени емотикон је успешно ажуриран", "Custom_Fields": "Произвољна поља", - "Custom_Scripts": "Прилагођени скрипти", + "Custom_Scripts": "Прилагођене скрипте", "Custom_Script_Logged_In": "Произвољне скрипте за пријављене кориснике", "Custom_Script_Logged_Out": "Произвољне скрипте за одјављене кориснике", - "Custom_Sound_Add": "Додајте прилагођени звук", + "Custom_Sound_Add": "Додај прилагођени звук", "Custom_Sound_Delete_Warning": "Брисање звука не може бити поништено.", - "Custom_Sound_Error_Invalid_Sound": "Неважећи звук", - "Custom_Sound_Error_Name_Already_In_Use": "Име прилагођеног звука се већ користи.", - "Custom_Sound_Has_Been_Deleted": "Изабрани звук је избрисан.", - "Custom_Sound_Info": "Прилагођене информације о звуку", + "Custom_Sound_Error_Invalid_Sound": "Неисправан звук", + "Custom_Sound_Error_Name_Already_In_Use": "Име прилагођеног звука је већ у употреби.", + "Custom_Sound_Has_Been_Deleted": "Прилагођени звук је обрисан.", + "Custom_Sound_Info": "Информације о прилагођеном звуку", "Custom_Sound_Saved_Successfully": "Прилагођен звук је успешно сачуван", "Custom_Sounds": "Прилагођени звуци", "Custom_Translations": "Прилагођени преводи", - "Custom_Translations_Description": "Требало би бити валидан ЈСОН гдје су кључеви језици који садрже кључни рјечник и преводе. Пример:
{\n\"ср\": {\n\"Канали\": \"Собе\"\n},\n\"пт\": {\n\"Канали\": \"Салас\"\n}\n} ", "Customize": "Прилагоди", - "CustomSoundsFilesystem": "Цустом Соундс Филесистем", "Dashboard": "Kомандна табла", "Date": "Датум", "Date_From": "Од", @@ -789,7 +720,7 @@ "DB_Migration": "Премештање базе података", "DB_Migration_Date": "Датум премештања базе података", "Deactivate": "Деактивирај", - "Decline": "Одбити", + "Decline": "Одбиј", "Default": "Предефинисано", "Delete": "Обриши", "delete-c": "Избриши јавне канале", @@ -798,7 +729,7 @@ "delete-d_description": "Дозвола за брисање директних порука", "delete-message": "Избриши поруку", "delete-message_description": "Дозвола за брисање поруке у соби", - "delete-p": "Избришите приватне канале", + "delete-p": "Обриши приватне канале", "delete-p_description": "Дозвола за брисање приватних канала", "delete-user": "Обриши корисника", "delete-user_description": "Дозвола за брисање корисника", @@ -806,18 +737,18 @@ "Delete_my_account": "Обриши мој налог", "Delete_Room_Warning": "Брисање собе ће избрисати све поруке послате у соби. Ово се не може поништити.", "Delete_User_Warning": "Брисање корисника ће такође обрисати све поруке тог корисника. Ово се не може поништити.", - "Delete_User_Warning_Delete": "Брисање корисника ће обрисати све поруке из тог корисника као добро. Ово се не може поништити.", + "Delete_User_Warning_Delete": "Брисање корисника ће такође обрисати све поруке од тог корисника. Ово се не може поништити.", "Delete_User_Warning_Keep": "Корисник ће бити избрисан, али ће њихове поруке остати видљиве. Ово се не може поништити.", "Delete_User_Warning_Unlink": "Брисање корисника ће уклонити корисничко име из свих њихових порука. Ово се не може поништити.", "Deleted": "Обрисан!", - "Department": "Одељење", + "Department": "Сектор", "Department_removed": "Сектор уклоњен", "Departments": "Сектори", "Description": "Опис", "Desktop": "Радна површина", "Desktop_Notification_Test": "Тест обавештења на радној површини", "Desktop_Notifications": "Обавештења на радној површини", - "Desktop_Notifications_Default_Alert": "Подразумевана обавештења о радној површини", + "Desktop_Notifications_Default_Alert": "Подразумевана обавештења на радној површини", "Desktop_Notifications_Disabled": "Обавештења на радној површини су искључена. Промените подешавања вашег прегледача ако желите да укључите обавештења.", "Desktop_Notifications_Duration": "Трајање обавештења на радној површини", "Desktop_Notifications_Enabled": "Обавештења на радној површини су омогућена", @@ -825,33 +756,27 @@ "Direct_message_someone": "Пошаљи директну поруку некоме", "Direct_Messages": "Директне поруке", "Direct_Reply": "Директни одговор", - "Direct_Reply_Debug": "Дебуг директни одговор", - "Direct_Reply_Debug_Description": "[Беваре] Омогућавање режима дебуга ће приказати вашу \"чисту текстуалну лозинку\" на конзоли за администрацију.", - "Direct_Reply_Delete": "Избришите пресретнуте емаилс", - "Direct_Reply_Enable": "Омогућите директан одговор", - "Direct_Reply_Frequency": "Фреквенција провере е-поште", - "Direct_Reply_Frequency_Description": "(у минутима, дефаулт / минимум 2)", + "Direct_Reply_Delete": "Обриши е-поруке", + "Direct_Reply_Enable": "Омогући директан одговор", + "Direct_Reply_Frequency": "Учесталост провере е-поште", + "Direct_Reply_Frequency_Description": "(у минутима, предефинисано/минимално 2)", "Direct_Reply_Host": "Директни одговор домаћина", - "Direct_Reply_IgnoreTLS": "ИгнореТЛС", "Direct_Reply_Password": "Лозинка", - "Direct_Reply_Port": "Дирецт_Репли_Порт", "Direct_Reply_Protocol": "Протокол директног одговора", "Direct_Reply_Separator": "Сепаратор", - "Direct_Reply_Separator_Description": "[Алтер само ако тачно знате шта радите, погледајте документе]
Сепаратор између дела базне и ознаке е-поште", "Direct_Reply_Username": "Корисничко име", - "Direct_Reply_Username_Description": "Молимо користите апсолутну е-пошту, означавање није дозвољено, било би преписано", + "Direct_Reply_Username_Description": "Молимо користите апсолутну е-пошту, означавање није дозвољено, биће преписано", "Directory": "Именик", - "Disable_Facebook_integration": "Онемогући интеграцију Фацебоок-а", + "Disable_Facebook_integration": "Онемогући интеграцију са Фејсбуком", "Disable_Notifications": "Онемогући обавештења", - "Disable_two-factor_authentication": "Онемогућите двоструку аутентификацију", "Disabled": "Онемогућено", - "Disallow_reacting": "Дисаллов Реацтинг", - "Disallow_reacting_Description": "Забрањује реаговање", - "Display_unread_counter": "Приказати број непрочитаних порука", + "Disallow_reacting": "Онемогући реаговања", + "Disallow_reacting_Description": "Онемогући реаговања", + "Display_unread_counter": "Прикажи број непрочитаних порука", "Displays_action_text": "Приказује текст акције", "Dont_ask_me_again": "Не питај ме поново!", - "Dont_ask_me_again_list": "Не питајте ме поново", - "Do_not_display_unread_counter": "Немојте приказивати било који бројач овог канала", + "Dont_ask_me_again_list": "Не питајте ме поново листа", + "Do_not_display_unread_counter": "Не приказуј било који бројач овог канала", "Do_you_want_to_accept": "Да ли желите да прихватите?", "Do_you_want_to_change_to_s_question": "Да ли желите да промените на %s?", "Document_Domain": "Доцумент Домаин", @@ -885,7 +810,7 @@ "edit-room-retention-policy_description": "Дозвола да уредите политику задржавања собе, да бисте аутоматски избрисали поруке у њему", "Edit_Custom_Field": "Измена прилагођеног поља", "Edit_Department": "Измена сектора", - "Edit_previous_message": "`% с` - Измени претходну поруку", + "Edit_previous_message": "`%s` - Измени претходну поруку", "Edit_Trigger": "Едит Триггер", "edited": "измењено", "Editing_user": "уређивање корисника", @@ -966,7 +891,7 @@ "error-invalid-permission": "Неважећа дозвола", "error-invalid-role": "Неисправна улога", "error-invalid-room": "Неисправна соба", - "error-invalid-room-name": "% с није исправан назив соба", + "error-invalid-room-name": "__room_name__ није исправан назив соба", "error-invalid-room-type": "__type__ није исправан тип собе.", "error-invalid-settings": "Унешена су неисправна подешавања", "error-invalid-subscription": "Неисправна претплата", @@ -992,21 +917,19 @@ "error-role-in-use": "Не можете обрисати улогу јер је у употреби", "error-role-name-required": "Име улоге је неопходно", "error-room-is-not-closed": "Соба није затворена", - "error-this-is-not-a-livechat-room": "Ово није соба Ливецхат", - "error-user-has-no-roles": "Корисник нема улогу", + "error-user-has-no-roles": "Корисник нема улоге", "error-user-is-not-activated": "Корисник није активиран", "error-user-limit-exceeded": "Број корисника које покушавате позвати у #цханнел_наме превазилази лимит који је поставио администратор", "error-user-not-in-room": "Корисник није у овој соби", - "error-logged-user-not-in-room": "Нисте у соби `% с`", - "error-user-registration-disabled": "Регистрација је онемогућен", - "error-user-registration-secret": "Регистрација је дозвољено само преко Сецрет УРЛ", - "error-you-are-last-owner": "Ви сте последњи власник. Молимо поставите новог власника пре него што напусти просторију.", + "error-logged-user-not-in-room": "Нисте у соби `%s`", + "error-user-registration-disabled": "Регистрација је онемогућена", + "error-user-registration-secret": "Регистрација је дозвољена само путем тајне адресе", + "error-you-are-last-owner": "Ви сте последњи власник. Молим поставите новог власника пре напуштања собе.", "Error_404": "Грешка: 404", "Error_changing_password": "Грешка приликом промене лозинке", "Error_loading_pages": "Грешка у учитавању страница", "Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances": "Грешка: Роцкет.Цхат захтева оплог таилинг када ради у више инстанци", "Error_RocketChat_requires_oplog_tailing_when_running_in_multiple_instances_details": "Проверите да ли је ваш МонгоДБ на режиму РеплицаСет, а варијабла окружења МОНГО_ОПЛОГ_УРЛ је исправно дефинирана на серверу апликација", - "Esc_to": "Есц да", "Event_Trigger": "Евент Триггер", "Event_Trigger_Description": "Изаберите који тип догађаја ће покренути ову Оутгоинг ВебХоок интеграцију", "every_second": "Једном сваке секунде", @@ -1014,53 +937,41 @@ "every_minute": "Једном у сваком тренутку", "every_5_minutes": "Једном на 5 минута", "every_30_minutes": "Једном на сваких 30 минута", - "every_hour": "Једном на сат", + "every_hour": "Сваких сат времена", "every_six_hours": "Једном на сваких шест сати", - "every_day": "Једном сваки дан", - "Everyone_can_access_this_channel": "Свако може приступити овом каналу", + "every_day": "Једном дневно", + "Everyone_can_access_this_channel": "Свако може да приступи овом каналу", "Example_s": "Пример: %s", - "Exclude_Botnames": "Искључи Ботс", - "Exclude_Botnames_Description": "Немојте пропагирати поруке из бота чије име одговара редовном изразу изнад. Ако је остављено празно, све поруке из бота ће бити пропагиране.", - "Exclude_pinned": "Искључите закачене поруке", + "Exclude_Botnames": "Изузми ботове", + "Exclude_pinned": "Изузми закачене поруке", "except_pinned": "(осим оних које су закачене)", "Execute_Synchronization_Now": "Изврши синхронизацију одмах", - "Export_My_Data": "Извози своје податке", - "External_Queue_Service_URL": "УРЛ адреса спољне редакције", + "Export_My_Data": "Извези моје податке (JSON)", "External_Service": "Спољни сервис", - "Facebook_Page": "Фацебоок страница", + "Facebook_Page": "Фејсбук страница", "False": "Лажан", "Favorite_Rooms": "Омогући омиљене собе", - "Favorite": "Фаворит", + "Favorite": "Омиљено", "Favorites": "Омиљене", - "Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Ова функција зависи од \"Пошаљи историју навигације посетилаца као поруке\" која ће бити омогућена.", - "FEDERATION_Domain": "домен", + "FEDERATION_Domain": "Домен", "FEDERATION_Status": "Стање", "Field": "Поље", "Field_removed": "Поље уклоњено", - "Field_required": "Потребно поље", + "Field_required": "Неопходно поље", "File_exceeds_allowed_size_of_bytes": "Датотека премашује дозвољену величину од __size__.", "File_name_Placeholder": "Претрага датотека ...", - "File_removed_by_automatic_prune": "Датотека је уклоњена аутоматским пруне", + "File_removed_by_automatic_prune": "Датотека је уклоњена аутоматским чишћењем", "File_not_allowed_direct_messages": "Дељење датотека није дозвољено у директним порукама.", - "File_removed_by_prune": "Датотека је уклоњена пруне", + "File_removed_by_prune": "Датотека је уклоњена чишћењем", "File_type_is_not_accepted": "Тип датотеке није прихваћен.", "File_uploaded": "Датотека је отпремљена", "FileUpload": "Отпремање датотека", - "FileUpload_Disabled": "Преузимање датотека је онемогућено.", + "FileUpload_Disabled": "Онемогућено је постављање датотека.", "FileUpload_Enabled": "Отпремање датотека је омогућено", - "FileUpload_Error": "Грешка при уносу датотеке", - "FileUpload_Enabled_Direct": "Датотеке су омогућене у директним порукама", + "FileUpload_Error": "Грешка при постављању датотеке", + "FileUpload_Enabled_Direct": "Постављање датотека је омогућено у директним порукама", "FileUpload_File_Empty": "Датотека је празна", "FileUpload_FileSystemPath": "Системска путања", - "FileUpload_GoogleStorage_AccessId": "ИД за Гоогле складишни приступ", - "FileUpload_GoogleStorage_AccessId_Description": "Приступ приступа је углавном у формату е-поште, на пример: \"екампле-тест@екампле.иам.гсервицеаццоунт.цом\"", - "FileUpload_GoogleStorage_Bucket": "Гоогле Стораге Буцкет Наме", - "FileUpload_GoogleStorage_Bucket_Description": "Име каде на које се датотеке требају отпремити.", - "FileUpload_GoogleStorage_Proxy_Avatars": "Проки Аватарс", - "FileUpload_GoogleStorage_Proxy_Avatars_Description": "Прокси пренос датотека преноса преко вашег сервера уместо директног приступа УРЛ-у средства", - "FileUpload_GoogleStorage_Proxy_Uploads": "Проки Уплоадс", - "FileUpload_GoogleStorage_Proxy_Uploads_Description": "Проки уплоад фајлова преко вашег сервера уместо директног приступа УРЛ-у средства", - "FileUpload_GoogleStorage_Secret": "Гоогле Стораге Сецрет", "FileUpload_GoogleStorage_Secret_Description": "Пратите ова упутства и залепите резултат овде.", "FileUpload_MaxFileSize": "Максимална величина уплоад сизе (ин битес)", "FileUpload_MaxFileSizeDescription": "Подесите на -1 да уклоните ограничење величине датотеке.", @@ -1132,61 +1043,46 @@ "Global_purge_override_warning": "Успостављена је глобална политика задржавања. Ако оставите \"Оверриде глобал полици ретентион\" искључену, можете примијенити само политику која је строжија од глобалне политике.", "Global_Search": "Глобално претраживање", "Go_to_your_workspace": "Идите у свој радни простор", - "Google_Vision_usage_limit_exceeded": "Граница употребе Гоогле Висион-а је прекорачена", - "GoogleCloudStorage": "Гоогле Цлоуд Стораге", - "GoogleNaturalLanguage_ServiceAccount_Description": "ЈСОН датотеку кључа услуге налога. Више информација може се наћи [овде] (хттпс://цлоуд.гоогле.цом/натурал-лангуаге/доцс/цоммон/аутх#сет_уп_а_сервице_аццоунт)", - "GoogleVision_Block_Adult_Images": "Блокирај слике одраслих", - "GoogleVision_Block_Adult_Images_Description": "Блокирање слика одраслих неће радити када се достигне месечна граница", - "GoogleVision_Current_Month_Calls": "Тренутни месечни позиви", - "GoogleVision_Enable": "Омогући Гоогле Висион", + "GoogleVision_Block_Adult_Images": "Блокирај слике за одрасле", + "GoogleVision_Block_Adult_Images_Description": "Блокирање слика за одрасле неће радити када се достигне месечно ограничење", + "GoogleVision_Current_Month_Calls": "Позиви у тренутном месецу", "GoogleVision_Max_Monthly_Calls": "Максимални месечни позиви", "GoogleVision_Max_Monthly_Calls_Description": "Користите 0 за неограничено", - "GoogleVision_ServiceAccount": "Гоогле Висион Сервице налог", - "GoogleVision_ServiceAccount_Description": "Креирајте кључ сервера (ЈСОН формат) и налепите ЈСОН садржај овде", - "GoogleVision_Type_Document": "Детекција документа документа", + "GoogleVision_Type_Document": "Откривање текста документа", "GoogleVision_Type_Faces": "Детекција лица", - "GoogleVision_Type_Labels": "Откривање наљепница", - "GoogleVision_Type_Landmarks": "Детекција оријентација", - "GoogleVision_Type_Logos": "Логос Детецтион", - "GoogleVision_Type_Properties": "Особине (Цолор) Детекција", - "GoogleVision_Type_SafeSearch": "Детекција сигурног претраживања", - "GoogleVision_Type_Similar": "Тражи Сличне слике", + "GoogleVision_Type_Labels": "Откривање ознака", + "GoogleVision_Type_Properties": "Откривање својства (боје)", + "GoogleVision_Type_SafeSearch": "Откривање сигурног претраживања", + "GoogleVision_Type_Similar": "Тражи сличне слике", "Government": "Влада", - "Group_by_Type": "Група по типу", - "Group_favorites": "Омиљене групе", - "Group_mentions_disabled_x_members": "Група помиње \"@ алл\" и \"@ хере\" била онемогућена за собе са више од __тотал__ чланова.", - "Group_mentions_only": "Група помиње само", + "Group_by_Type": "Групиши по типу", + "Group_favorites": "Групиши омиљено", + "Group_mentions_only": "Само помињања у групи", "Hash": "Хеш", "Header": "Заглавље", - "Header_and_Footer": "Хедер и футер", - "Healthcare_and_Pharmaceutical": "Здравство / Фармацеутски", + "Header_and_Footer": "Заглавље и подножје", + "Healthcare_and_Pharmaceutical": "Здравство/фармација", "Help_Center": "Центар за помоћ", "Helpers": "Помоћници", - "Hex_Color_Preview": "Хек Цолор Превиев", "Hidden": "Сакривен", "Hide_Avatars": "Сакриј аватаре", "Hide_counter": "Сакриј бројач", - "Hide_flextab": "Сакриј десно бочно дугме са кликом", "Hide_Group_Warning": "Да ли заиста желите да сакријете групу \"%s\"?", "Hide_Livechat_Warning": "Да ли заиста желите да сакријете ћаскање са \"%s\"?", "Hide_Private_Warning": "Да ли заиста желите да сакријете расправу са \"%s\"?", "Hide_roles": "Сакриј улоге", "Hide_room": "Сакриј собу", - "Hide_Room_Warning": "Да ли сте сигурни да желите да сакријете собу \"%s\"?", + "Hide_Room_Warning": "Да ли заиста желите да сакријете собу \"%s\"?", "Hide_Unread_Room_Status": "Сакриј статус непрописане собе", - "Hide_usernames": "Хиде корисничка имена", - "Highlights": "праменови", - "Highlights_How_To": "Да будете обавештени када неко помене реч или фразу, додајте га овде. Можете одвојити речи или фразе са зарезима. Хигхлигхт Речи се не разликују.", - "Highlights_List": "Хигхлигхт речи", - "History": "Историјат", + "Hide_usernames": "Сакриј корисничка имена", + "Highlights": "Наглашавања", + "Highlights_How_To": "Да би били обавештени када неко помене реч или фразу, додајте је овде. Можеш раздвојити речи или фразе зарезом. Велика/мала слова нису битна.", + "Highlights_List": "Наглаши речи", + "History": "Историја", "Host": "Домаћин", "hours": "сати", "Hours": "Сати", - "How_friendly_was_the_chat_agent": "Како пријатељски је био агент ћаскање?", - "How_knowledgeable_was_the_chat_agent": "Како знања је агент ћаскање?", "How_long_to_wait_after_agent_goes_offline": "Како дуго чекати након што агент престане да ради", - "How_responsive_was_the_chat_agent": "Како реагују био агент ћаскање?", - "How_satisfied_were_you_with_this_chat": "Колико сте задовољни су са овом разговору?", "How_to_handle_open_sessions_when_agent_goes_offline": "Како поступати са отвореним сесијама када агент отпутује ван мреже", "Idle_Time_Limit": "Лимит Идле Тиме", "Idle_Time_Limit_Description": "Период времена док се статус не промијени. Вредност мора бити у секундама.", @@ -1194,7 +1090,7 @@ "If_this_email_is_registered": "Ако је ова е-пошта регистрована, пошаљитећемо упутства о томе како да ресетујете своју лозинку. Ако ускоро не примите е-пошту, вратите се и покушајте поново.", "If_you_are_sure_type_in_your_password": "Ако сте сигурни унесите лозинку:", "Members_List": "Списак чланова", - "If_you_are_sure_type_in_your_username": "Ако сте сигурни тип ваше корисничко име:", + "If_you_are_sure_type_in_your_username": "Ако сте сигурни унесите ваше корисничко име:", "If_you_dont_have_one_send_an_email_to_omni_rocketchat_to_get_yours": "Ако немате један, пошаљите е-пошту на [омни@роцкет.цхат] (маилто: омни@роцкет.цхат) да бисте добили своје.", "Iframe_Integration": "Интеграција Ифраме-а", "Iframe_Integration_receive_enable": "Омогући Рецеиве", @@ -1263,82 +1159,58 @@ "Integration_Outgoing_WebHook_History_Messages_Sent_From_Process_Script": "Поруке послате из корака процеса", "Integration_Outgoing_WebHook_History_Time_Ended_Or_Error": "Време је завршено или Еррор'д", "Integration_Outgoing_WebHook_History_Time_Triggered": "Покренута интеграција времена", - "Integration_Outgoing_WebHook_History_Trigger_Step": "Задњи покренути корак", - "Integration_Outgoing_WebHook_No_History": "Ова излазна мрежна интеграција тек треба да има било какву историју.", - "Integration_Retry_Count": "Покушајте поново", - "Integration_Retry_Count_Description": "Колико пута треба интегрисати интеграцију уколико позив на урл не успије?", - "Integration_Retry_Delay": "Ретри Делаи", + "Integration_Outgoing_WebHook_History_Trigger_Step": "Последњи корак окидача", + "Integration_Retry_Count": "Број поновних покушаја", + "Integration_Retry_Delay": "Одлагање поновних покушаја", "Integration_Retry_Delay_Description": "Који алгоритам кашњења треба да користи поновити? 10^к или 2^к или к*2", - "Integration_Retry_Failed_Url_Calls": "Понови неуспеле позиве Урл", - "Integration_Retry_Failed_Url_Calls_Description": "Да ли интеграција покуша у разумном временском року, ако позив на УРЛ не успе?", - "Integration_Run_When_Message_Is_Edited": "Покрените измене", + "Integration_Run_When_Message_Is_Edited": "Покрени при изменама", "Integration_Run_When_Message_Is_Edited_Description": "Да ли се интеграција покреће када се порука уређује? Постављање овог на неисправно ће довести до тога да интеграција покреће само нове поруке.", "Integration_updated": "Интеграција је ажурирана", - "Integration_Word_Trigger_Placement": "Постављање ријечи било гдје", "Integration_Word_Trigger_Placement_Description": "Да ли би се требала покренути Реч када се постави било гдје у реченици осим почетка?", "Integrations": "Интеграције", "Integrations_for_all_channels": "Ентер алл_публиц_цханнелс да слушају на свим јавним каналима, алл_привате_гроупс да слушају на свим приватним групама, и алл_дирецт_мессагес да слушате све директне поруке.", - "Integrations_Outgoing_Type_FileUploaded": "Филе Уплоадед", + "Integrations_Outgoing_Type_FileUploaded": "Датотека постављена", "Integrations_Outgoing_Type_RoomArchived": "Соба архивирана", - "Integrations_Outgoing_Type_RoomCreated": "Креирана соба (јавна и приватна)", - "Integrations_Outgoing_Type_RoomJoined": "Састанак корисника", - "Integrations_Outgoing_Type_RoomLeft": "Усер Лефт Роом", + "Integrations_Outgoing_Type_RoomCreated": "Соба креирана (јавна и приватна)", + "Integrations_Outgoing_Type_RoomJoined": "Корисник је приступио соби", + "Integrations_Outgoing_Type_RoomLeft": "Корисник је напустио собу", "Integrations_Outgoing_Type_SendMessage": "Порука послата", - "Integrations_Outgoing_Type_UserCreated": "Усер Цреатед", + "Integrations_Outgoing_Type_UserCreated": "Корисник креиран", "InternalHubot_EnableForChannels": "Омогући за јавне канале", - "InternalHubot_EnableForDirectMessages": "Омогућите директне поруке", - "InternalHubot_EnableForPrivateGroups": "Омогућите приватне канале", - "InternalHubot_PathToLoadCustomScripts": "Фолдер за учитавање скрипте", - "InternalHubot_reload": "Поново учитајте скрипте", + "InternalHubot_EnableForDirectMessages": "Омогући за директне поруке", + "InternalHubot_EnableForPrivateGroups": "Омогући за приватне канале", + "InternalHubot_PathToLoadCustomScripts": "Фасцикла за учитавање скрипти", + "InternalHubot_reload": "Поново учитај скрипте", "InternalHubot_ScriptsToLoad": "Скрипте за учитавање", "InternalHubot_Username_Description": "Ово мора бити валидно корисничко име бота регистрованог на твом серверу.", "Invalid_confirm_pass": "Потврдна лозинка се не поклапа са лозинком", "Invalid_email": "Унета је неисправна адреса е-поште", - "Invalid_username": "Уписано корисничко име је неважеће", + "Invalid_username": "Уписано корисничко име је неисправно", "Invalid_name": "Име не сме бити празно", "Invalid_notification_setting_s": "Неисправна поставка обавештења: %s", "Invalid_pass": "Лозинка не сме бити празна", - "Invalid_reason": "Разлог за придруживање не сме бити празан", + "Invalid_reason": "Разлог за приступање не може бити празно", "Invalid_room_name": "%s није исправно име канала", "Invalid_secret_URL_message": "Достављена УРЛ адреса није исправна.", "Invalid_setting_s": "Неисправна поставка: %s", - "Invalid_two_factor_code": "Неважећи код два фактора", "invisible": "невидљив(а)", "Invisible": "Невидљив(а)", - "Invitation": "Позив", + "Invitation": "Позивница", "Invitation_Email_Description": "Можете користити следеће симболе:
  • [email] за примаоца е-поште.
  • [Site_Name] и [Site_URL] за име апликације и УРЛ адресу.
", "Invitation_Subject": "Наслов позивнице", "Invitation_Subject_Default": "Позвани сте на [Site_Name]", "Invite_user_to_join_channel": "Позови једног корисника да се придружи овом каналу", - "Invite_user_to_join_channel_all_from": "Позовите све кориснике из [# канала] да бисте се придружили овом каналу", + "Invite_user_to_join_channel_all_from": "Позовите све кориснике из [#channel] да се придруже овом каналу", "Invite_user_to_join_channel_all_to": "Позовите све кориснике са овог канала да се придруже [#канал]", "Invite_Users": "Позови кориснике", - "IRC_Channel_Join": "Излаз команде ЈОИН.", - "IRC_Channel_Leave": "Излаз команде ПАРТ.", - "IRC_Channel_Users": "Излаз команде НАМЕС.", - "IRC_Channel_Users_End": "Крај излаза команде НАМЕС.", - "IRC_Description": "Интернет Релаи Цхат (ИРЦ) је текстуални скуп комуникациони алат. Корисници се придружују јединствено именованим каналима или просторијама за отворену дискусију. ИРЦ такође подржава приватне поруке између појединачних корисника и могућности дељења датотека. Овај пакет интегрише ове слојеве функционалности са Роцкет.Цхат.", - "IRC_Enabled": "Покушај интеграције ИРЦ подршке. Промена ове вредности захтева поновно покретање Роцкет.Цхат.", - "IRC_Enabled_Alert": "Подршка ИРЦ-а је посао у току. Употреба на производном систему се не препоручује у овом тренутку.", - "IRC_Federation": "ИРЦ Федератион", - "IRC_Federation_Disabled": "ИРЦ Федератион је онемогућен.", - "IRC_Hostname": "ИРЦ хост сервер за повезивање.", - "IRC_Login_Fail": "Излаз након неуспеле везе са ИРЦ сервером.", - "IRC_Login_Success": "Излаз након успјешне везе са ИРЦ сервером.", - "IRC_Message_Cache_Size": "Ограничење кеша за руковање одлазним порукама.", - "IRC_Port": "Порт за везу на ИРЦ хост серверу.", - "IRC_Private_Message": "Излаз ПРИВМСГ наредбе.", - "IRC_Quit": "Излаз након напуштања ИРЦ сесије.", "is_also_typing": "такође куца", "is_also_typing_female": "такође куца", "is_also_typing_male": "такође куца", "is_typing": "куца", "is_typing_female": "куца", "is_typing_male": "куца", - "Issue_Links": "Питања везане за праћење", - "IssueLinks_Incompatible": "Упозорење: не омогућујте ово и 'Хек Цолоур Превиев' истовремено.", "IssueLinks_LinkTemplate": "Шаблон за проблеме са везама", - "IssueLinks_LinkTemplate_Description": "Шаблон за питања везаних за проблем; % с ће бити замењен бројем броја.", + "IssueLinks_LinkTemplate_Description": "Шаблон за проблеме са везама; %s ће бити замењен бројем проблема.", "It_works": "То ради", "italics": "курзив", "Mobex_sms_gateway_from_number": "Од", @@ -1663,8 +1535,8 @@ "More_direct_messages": "Више директне поруке", "More_groups": "Више приватне групе", "More_unreads": "više унреадс", - "Move_beginning_message": "`% с` - Пређите на почетак поруке", - "Move_end_message": "`% с` - Пређите на крај поруке", + "Move_beginning_message": "`%s` - Пређите на почетак поруке", + "Move_end_message": "`%s` - Пређите на крај поруке", "Msgs": "Поруке", "multi": "више", "multi_line": "мулти лине", @@ -1690,7 +1562,7 @@ "New_Custom_Field": "Нови прилагођено поље", "New_Department": "Нови сектор", "New_integration": "Нова интеграција", - "New_line_message_compose_input": "`% с` - Нова линија у поруци садржи улаз", + "New_line_message_compose_input": "`%s` - Нова линија у поруци садржи улаз", "New_logs": "Нови записи", "New_Message_Notification": "Обавештење о новој поруци", "New_messages": "Нове поруке", @@ -1701,9 +1573,9 @@ "New_role": "Нова улога", "New_Room_Notification": "Обавештење о новој соби", "New_Trigger": "Нови Триггер", - "New_version_available_(s)": "Нова доступна верзија (% с)", + "New_version_available_(s)": "Нова доступна верзија (%s)", "New_videocall_request": "Нови позив за видео позив", - "New_visitor_navigation": "Нова навигација: __хистори__", + "New_visitor_navigation": "Нова навигација: __history__", "Newer_than": "Новије од", "Newer_than_may_not_exceed_Older_than": "\"Новије од\" можда не прелази \"Старије од\"", "No_available_agents_to_transfer": "Нема расположивих агената за пренос", @@ -1737,8 +1609,6 @@ "Notification_Duration": "Трајање обавјештења", "Notification_Mobile_Default_For": "Пусх Мобиле Нотифицатионс Фор", "Notifications": "Обавештења", - "Notifications_Always_Notify_Mobile": "Увек обавијестите мобилни телефон", - "Notifications_Always_Notify_Mobile_Description": "Изаберите да увек обавијестите мобилни уређај без обзира на статус присутности.", "Notifications_Duration": "Нотифицатионс_Дуратион", "Notifications_Max_Room_Members": "Максималне чланове собе пре него што онемогућите све обавештења о порукама", "Notifications_Max_Room_Members_Description": "Максималан број чланова у соби када се обавештења за све поруке онемогућавају. Корисници могу и даље мењати по подешавању собе како би примили сва обавјештења на индивидуалној основи. (0 за онемогућавање)", @@ -1780,7 +1650,7 @@ "Oops!": "Упс", "Oops_page_not_found": "Упс, страница није пронађена", "Open": "Отвори", - "Open_channel_user_search": "`% с` - Отворени канал / Тражење корисника", + "Open_channel_user_search": "`%s` - Отворени канал / Тражење корисника", "Open_days_of_the_week": "Отворени дани недеље", "Open_Livechats": "Отвори Ливецхатс", "Open_your_authentication_app_and_enter_the_code": "Отворите апликацију за потврду идентитета и унесите га. Такође можете да користите један од ваших резервних кодова.", @@ -1852,7 +1722,7 @@ "post-readonly_description": "Дозвола за објављивање поруке на каналу који је само за читање", "Post_as": "пост као", "Post_to_Channel": "Пост на канал", - "Post_to_s_as_s": "Пост %s као %s", + "Post_to_s_as_s": "Пост %s као %s", "Preferences": "преференцес", "Preferences_saved": "Подешавања су сачувана", "preview-c-room": "Превиев Публиц Цханнел", @@ -1892,7 +1762,6 @@ "Push_apn_dev_passphrase": "АПН Дев Приступна фраза", "Push_apn_key": "АПН ključ", "Push_apn_passphrase": "АПН приступна фраза", - "Push_debug": "дебуг", "Push_enable": "омогућити", "Push_enable_gateway": "Омогући Гатеваи", "Push_gateway": "пролаз", @@ -2536,10 +2405,10 @@ "Username_Change_Disabled": "Ваш Роцкет.Цхат Администратор је онемогућио промену корисничких имена", "Username_denied_the_OTR_session": "__усернаме__ негирао ОТР сесију", "Username_description": "Корисничко име се користи да би други могли да вас спомињу у порукама.", - "Username_doesnt_exist": "Корисничко име `% с` не постоји.", + "Username_doesnt_exist": "Корисничко име `%s` не постоји.", "Username_ended_the_OTR_session": "__усернаме__ завршио ОТР сесију", "Username_invalid": "%s није исправно корисничко име,
користите само слова, бројеве, тачке и повлаке", - "Username_is_already_in_here": "`@% С` је већ овде.", + "Username_is_already_in_here": "`@%s` је већ овде.", "Username_is_not_in_this_room": "Корисник `#%s` није у овој соби.", "Username_Placeholder": "Молимо унесите корисничка имена ...", "Username_title": "Направите корисничко име", @@ -2684,4 +2553,4 @@ "Your_push_was_sent_to_s_devices": "Ваш притиском је послат на %s уређајима", "Your_server_link": "Веза са сервером", "Your_workspace_is_ready": "Ваш радни простор је спреман за кориштење 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/sv.i18n.json b/packages/rocketchat-i18n/i18n/sv.i18n.json index 3781c2dddc49..eb5398c34929 100644 --- a/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -1961,8 +1961,6 @@ "Notification_Duration": "Anmälningsperiod", "Notification_Mobile_Default_For": "Tryck på Mobila meddelanden för", "Notifications": "Notifikationer", - "Notifications_Always_Notify_Mobile": "Meddela alltid mobilen", - "Notifications_Always_Notify_Mobile_Description": "Välj att alltid meddela mobilenhet oavsett närvaro status.", "Notifications_Duration": "Notifikationens tidslängd", "Notifications_Max_Room_Members": "Max Room Members innan deaktiverar alla meddelandemeddelanden", "Notifications_Max_Room_Members_Description": "Max antal medlemmar i rummet när meddelanden för alla meddelanden blir inaktiverade. Användare kan ändå ändra per rumsinställning för att få alla meddelanden individuellt. (0 för att inaktivera)", @@ -2133,7 +2131,6 @@ "Push_apn_dev_passphrase": "APN Dev Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Debug", "Push_enable": "Aktivera", "Push_enable_gateway": "Aktivera Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json index 21520234b607..51402f45436c 100644 --- a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json +++ b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "அறிவிப்பு காலம்", "Notification_Mobile_Default_For": "மொபைல் அறிவிப்புகளை அழுத்துக", "Notifications": "அறிவிப்புகள்", - "Notifications_Always_Notify_Mobile": "எப்போதும் மொபைல் தெரிவிக்கவும்", - "Notifications_Always_Notify_Mobile_Description": "எப்போதும் இருப்பிட நிலையை பொருட்படுத்தாமல் மொபைல் சாதனத்தை அறிவிக்கத் தேர்வுசெய்யவும்.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "அனைத்து செய்தி அறிவிப்புகளையும் முடக்குவதற்கு முன் மேக்ஸ் அறை உறுப்பினர்கள்", "Notifications_Max_Room_Members_Description": "அனைத்து செய்திகளுக்கான அறிவிப்புகளும் முடக்கப்பட்டால் அறையில் உறுப்பினர்களின் அதிகபட்ச எண்ணிக்கை. ஒரு தனிப்பட்ட அடிப்படையில் அனைத்து அறிவிப்புகளையும் பெறுவதற்கு ஒரு அறை அமைப்பை பயனர்கள் மாற்ற முடியும். (முடக்க 0)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "APN ஆனது தேவ் கடவுச்சொற்றொடர்", "Push_apn_key": "APN ஆனது முக்கிய", "Push_apn_passphrase": "APN ஆனது கடவுச்சொற்றொடர்", - "Push_debug": "பிழைதிருத்து", "Push_enable": "இயக்கு", "Push_enable_gateway": "நுழைவாயில் செயல்படுத்த", "Push_gateway": "நுழைவாயில்", diff --git a/packages/rocketchat-i18n/i18n/th-TH.i18n.json b/packages/rocketchat-i18n/i18n/th-TH.i18n.json index 57087d762ab6..bd0fb97ef00c 100644 --- a/packages/rocketchat-i18n/i18n/th-TH.i18n.json +++ b/packages/rocketchat-i18n/i18n/th-TH.i18n.json @@ -1886,8 +1886,6 @@ "Notification_Duration": "ระยะเวลาการแจ้งเตือน", "Notification_Mobile_Default_For": "Push Mobile Notifications สำหรับ", "Notifications": "การแจ้งเตือน", - "Notifications_Always_Notify_Mobile": "แจ้งโทรศัพท์มือถือเสมอ", - "Notifications_Always_Notify_Mobile_Description": "เลือกว่าจะแจ้งให้ทราบอุปกรณ์เคลื่อนที่ทุกครั้งโดยไม่คำนึงถึงสถานะการแสดงตน", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "สมาชิกห้องพักสูงสุดก่อนปิดใช้งานการแจ้งเตือนข้อความทั้งหมด", "Notifications_Max_Room_Members_Description": "จำนวนสมาชิกในห้องเมื่อการแจ้งเตือนสำหรับข้อความทั้งหมดถูกปิดใช้งาน ผู้ใช้ยังคงสามารถเปลี่ยนการตั้งค่าห้องต่อเพื่อรับการแจ้งเตือนทั้งหมดตามแต่ละบุคคล (0 เพื่อปิดการใช้งาน)", @@ -2055,7 +2053,6 @@ "Push_apn_dev_passphrase": "รหัสผ่านสำหรับ APN Dev", "Push_apn_key": "APN Key", "Push_apn_passphrase": "ข้อความรหัส APN", - "Push_debug": "ตรวจแก้จุดบกพร่อง", "Push_enable": "ทำให้สามารถ", "Push_enable_gateway": "เปิดใช้งานเกตเวย์", "Push_gateway": "ประตู", diff --git a/packages/rocketchat-i18n/i18n/tr.i18n.json b/packages/rocketchat-i18n/i18n/tr.i18n.json index c837a10a5854..d463c1eaa310 100644 --- a/packages/rocketchat-i18n/i18n/tr.i18n.json +++ b/packages/rocketchat-i18n/i18n/tr.i18n.json @@ -2230,8 +2230,6 @@ "Notification_Duration": "Bildirim süresi", "Notification_Mobile_Default_For": "Mobil Anlık Bildirimlerin Gösterileceği Durumlar", "Notifications": "Bildirimler", - "Notifications_Always_Notify_Mobile": "Mobil cihazlara her zaman haber ver", - "Notifications_Always_Notify_Mobile_Description": "Durumunu ne olursa olsun mobil cihaza her zaman bildirmeyi seçin.", "Notifications_Duration": "Bildirimler Süresi", "Notifications_Max_Room_Members": "Tüm İleti Bildirimlerini Devre Dışı Bırakmadan Önce Maksimum Oda Üyesi", "Notifications_Max_Room_Members_Description": "Tüm mesajlar için bildirimler devre dışı bırakıldığında odanın azami üye sayısı. Kullanıcılar, tüm bildirimleri tek tek almak üzere oda başına ayarlarını değiştirebilir. (Devre dışı bırakmak için 0)", @@ -2425,7 +2423,6 @@ "Push_apn_dev_passphrase": "APN Geliştirici Parolası", "Push_apn_key": "APN Anahtarı", "Push_apn_passphrase": "APN Parola", - "Push_debug": "Hata ayıklama", "Push_enable": "Etkin", "Push_enable_gateway": "Ağ geçidi etkin", "Push_gateway": "Ağ geçidi", diff --git a/packages/rocketchat-i18n/i18n/ug.i18n.json b/packages/rocketchat-i18n/i18n/ug.i18n.json index 17a770fa68c6..b856dc245d4b 100644 --- a/packages/rocketchat-i18n/i18n/ug.i18n.json +++ b/packages/rocketchat-i18n/i18n/ug.i18n.json @@ -853,7 +853,6 @@ "Push_apn_dev_passphrase": "مەخپىي ھەرپAPN Dev", "Push_apn_key": "APN ئاچقۇچ", "Push_apn_passphrase": "APN مەخپىي ھەرپ", - "Push_debug": "تەڭشەش", "Push_enable": "قوزغىتىش", "Push_enable_gateway": "تور ئۆتكىلى قوزغىتىلدى", "Push_gateway": "تور ئۆتكىلى", diff --git a/packages/rocketchat-i18n/i18n/uk.i18n.json b/packages/rocketchat-i18n/i18n/uk.i18n.json index 8d26be541749..7b4e77467295 100644 --- a/packages/rocketchat-i18n/i18n/uk.i18n.json +++ b/packages/rocketchat-i18n/i18n/uk.i18n.json @@ -2362,8 +2362,6 @@ "Notification_Duration": "Тривалість сповіщення", "Notification_Mobile_Default_For": "Натисніть Мобільні сповіщення для", "Notifications": "повідомлення", - "Notifications_Always_Notify_Mobile": "Завжди повідомляти про мобільний телефон", - "Notifications_Always_Notify_Mobile_Description": "Виберіть, щоб завжди повідомляти мобільний пристрій незалежно від статусу присутності.", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "Максимальна кількість членів перед відключенням всіх повідомлень повідомлень", "Notifications_Max_Room_Members_Description": "Максимальна кількість учасників в кімнаті, коли сповіщення для всіх повідомлень вимикаються. Користувачі все ще можуть змінювати параметри кімнати, щоб отримувати всі сповіщення на індивідуальній основі. (Відключити 0)", @@ -2539,7 +2537,6 @@ "Push_apn_dev_passphrase": "APN Дев Passphrase", "Push_apn_key": "APN Key", "Push_apn_passphrase": "APN Passphrase", - "Push_debug": "Відлагоджувати", "Push_enable": "включити", "Push_enable_gateway": "включити шлюз", "Push_gateway": "шлюз", diff --git a/packages/rocketchat-i18n/i18n/vi-VN.i18n.json b/packages/rocketchat-i18n/i18n/vi-VN.i18n.json index 1bb8b87b8fee..53b840cca3c3 100644 --- a/packages/rocketchat-i18n/i18n/vi-VN.i18n.json +++ b/packages/rocketchat-i18n/i18n/vi-VN.i18n.json @@ -1887,8 +1887,6 @@ "Notification_Duration": "Thời lượng thông báo", "Notification_Mobile_Default_For": "Push notification Di động cho", "Notifications": "Thông báo", - "Notifications_Always_Notify_Mobile": "Luôn báo cho điện thoại di động", - "Notifications_Always_Notify_Mobile_Description": "Chọn luôn thông báo cho thiết bị di động bất kể trạng thái hiện diện.", "Notifications_Duration": "Thời lượng thông báo", "Notifications_Max_Room_Members": "Thành viên kênh tối đa trước khi vô hiệu hoá tất cả thông báo", "Notifications_Max_Room_Members_Description": "Số thành viên tối đa trong phòng khi thông báo cho tất cả các tin nhắn bị tắt. Người dùng vẫn có thể thay đổi cài đặt mỗi phòng để nhận tất cả các thông báo trên cơ sở cá nhân. (0 để vô hiệu hóa)", @@ -2056,7 +2054,6 @@ "Push_apn_dev_passphrase": "Cụm mật khẩu APN Dev", "Push_apn_key": "Khóa APN", "Push_apn_passphrase": "Cụm mật khẩu APN", - "Push_debug": "Debug", "Push_enable": "Bật", "Push_enable_gateway": "Bật Gateway", "Push_gateway": "Gateway", diff --git a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json index 36489d72a17e..12d631424813 100644 --- a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json @@ -1914,8 +1914,6 @@ "Notification_Duration": "通知持续时间", "Notification_Mobile_Default_For": "推送移动通知", "Notifications": "通知", - "Notifications_Always_Notify_Mobile": "始终通知移动", - "Notifications_Always_Notify_Mobile_Description": "选择始终通知移动设备,无论在线状态如何。", "Notifications_Duration": "Notifications_Duration", "Notifications_Max_Room_Members": "最大房间成员禁用所有消息通知之前", "Notifications_Max_Room_Members_Description": "当所有消息的通知被禁用时,房间内的最大成员数量。用户仍然可以更改每个房间的设置,以单独接收所有通知。 (0禁用)", @@ -2083,7 +2081,6 @@ "Push_apn_dev_passphrase": "APN DEV 口令", "Push_apn_key": "APN密钥", "Push_apn_passphrase": "APN密码短语", - "Push_debug": "调试", "Push_enable": "启用", "Push_enable_gateway": "启用网关", "Push_gateway": "网关", diff --git a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json index 2bd6a4d2b727..263ebd612abe 100644 --- a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json @@ -69,7 +69,7 @@ "Accounts_Email_Deactivated": "[name]

您的帳戶已停用。

", "Accounts_Enrollment_Email_Default": "

歡迎來到[Site_Name]

轉到[Site_URL],並嘗試了當今最先進的開源聊天解決方案!

", "Accounts_Email_Deactivated_Subject": "帳戶停用", - "Accounts_EmailVerification": "電子郵件驗證", + "Accounts_EmailVerification": "只允許經過驗證的使用者登入", "Accounts_EmailVerification_Description": "確定 SMTP 設定無誤以使用本功能", "Accounts_Enrollment_Email_Subject_Default": "歡迎來到 [Site_Name]", "Accounts_Enrollment_Email": "註冊電子郵件", @@ -675,6 +675,7 @@ "Clear_filters": "清除過濾器", "clear_history": "清除對話記錄", "Click_here": "點擊這裡", + "Click_here_for_more_details_or_contact_sales_for_a_new_license": "點擊這裡了解更多詳細資料,或與__email__聯絡以得到新的授權。", "Click_here_for_more_info": "點擊這裡查看更多資訊", "Click_here_to_enter_your_encryption_password": "點擊這裡來輸入您的加密密碼", "Click_here_to_view_and_copy_your_password": "點擊這裡來檢視和複製您的密碼。", @@ -692,6 +693,7 @@ "close-others-livechat-room": "關閉客服 Room", "Cloud_workspace_connected_without_account": "您的工作區域已連上了 Rocket.Chat 雲端。假如您願意,您可以登入到 Rocket.Chat 雲端然後用您的雲端帳號來關聯您的工作區域。", "close-others-livechat-room_description": "有權限關閉其它客服聊天室", + "Close_room_description": "您即將關閉此聊天。您確定要繼續嗎?", "Closed": "已關閉", "Closed_At": "已關閉於", "Closed_by_visitor": "由訪客關閉", @@ -1410,6 +1412,8 @@ "error-password-policy-not-met-oneSpecial": "密碼不符合至少有一個特殊字符的伺服器策略", "error-password-policy-not-met-oneUppercase": "密碼不符合至少一個大寫字符的伺服器策略", "error-password-policy-not-met-repeatingCharacters": "密碼不符合禁止重複字符的伺服器策略(您有太多相同的字符相鄰)", + "error-pinning-message": "無法釘選訊息", + "error-unpinning-message": "無法取消釘選訊息", "error-push-disabled": "推播已停用", "error-remove-last-owner": "這是最後的擁有者。請刪除此人之前設置一個新的擁有者。", "error-role-in-use": "無法刪除的角色,因為它在使用", @@ -2115,6 +2119,7 @@ "leave-c": "保留 Channel", "leave-p": "離開私人群組", "Leave": "離開", + "Leave_a_comment": "發表評論", "Leave_Group_Warning": "你確定你要離開組 “%s” 嗎?", "Leave_Livechat_Warning": "你確定要離開 “%s” 的即時聊天嗎?", "Leave_Private_Warning": "你確定要離開 “%s” 的討論?", @@ -2155,7 +2160,9 @@ "Livechat_title_color": "即時聊天標題背景顏色", "Livechat_transcript_sent": "即時聊天副本傳送", "Livechat_transfer_to_agent": "__from__ 將聊天轉移到 __to__", + "Livechat_transfer_to_agent_with_a_comment": "__from__將聊天轉移到__to__並發表了評論:__comment__", "Livechat_transfer_to_department": "__from__ 將聊天轉移到 __to__ 部門", + "Livechat_transfer_to_department_with_a_comment": "__from__將聊天轉移到部門__to__並加了評論:__comment__", "Livechat_transfer_return_to_the_queue": "__from__ 將聊天轉回到佇列", "Livechat_Triggers": "觸發即時聊天", "Livechat_Users": "ID登錄用戶", @@ -2272,6 +2279,7 @@ "Max_number_incoming_livechats_displayed_description": "(可選) 顯示進入的即時訊息佇列的最大數量。", "Max_number_of_uses": "最大使用次數", "Maximum": "最大", + "Maximum_number_of_guests_reached": "達到最大訪客人數", "Media": "媒體", "Medium": "中間", "Members": "會員", @@ -2324,6 +2332,8 @@ "Message_GlobalSearch": "全域搜尋", "Message_GroupingPeriod": "分組週期(以秒為單位)", "Message_GroupingPeriodDescription": "如果兩個來自同一使用者並且經過的時間小於以秒為單位的通知時間,則訊息將與先前訊息分組。", + "Message_has_been_pinned": "訊息已釘選", + "Message_has_been_unpinned": "訊息已取消釘選", "Message_has_been_starred": "已標記訊息", "Message_has_been_unstarred": "已取消標記訊息", "Message_HideType_au": "隱藏“使用者已增加”訊息", @@ -2506,8 +2516,6 @@ "Notification_RequireInteraction_Description": "僅適用於 Chrome 瀏覽器版本>50。使用參數requireInteraction 將桌面通知無限期顯示直到使用者與其互動。", "Notification_Mobile_Default_For": "推送手機通知", "Notifications": "通知", - "Notifications_Always_Notify_Mobile": "始終通知手機", - "Notifications_Always_Notify_Mobile_Description": "選擇始終通知手機設備,無論在線狀態如何。", "Notifications_Duration": "通知間隔", "Notifications_Max_Room_Members": "最大 Room 成員禁止所有訊息通知之前", "Notifications_Max_Room_Members_Description": "當所有訊息通知被停用時,房間內的最大成員數量。使用者仍然可以更改每個房間的設定,以單獨接收所有通知。 (0是停用)", @@ -2623,6 +2631,7 @@ "Pin_Message": "釘選訊息", "Pinned_a_message": "已釘選訊息:", "Pinned_Messages": "已釘選訊息", + "pinning-not-allowed": "不允許釘選", "PiwikAdditionalTrackers": "額外的Piwik網站", "PiwikAdditionalTrackers_Description": "如果您想追蹤相同的資料到不同的網站,請輸入以下格式的 Piwik 網址和SiteIDs:[{ \"trackerURL\" : \"https://my.piwik.domain2/\", \"siteId\" : 42 },{ \"trackerURL\" : \"https://my.piwik.domain3/\", \"siteId\" : 15 }]", "PiwikAnalytics_cookieDomain": "所有子網域", @@ -2725,9 +2734,6 @@ "Push_apn_dev_passphrase": "APN Dev 密碼", "Push_apn_key": "APN 金鑰", "Push_apn_passphrase": "APN 密碼", - "Push_debug": "除錯", - "Push_send_interval": "確認佇列中是否有新的推送通知的時間間隔", - "Push_send_batch_size": "每次點擊來批次處理大小", "Push_enable": "啟用", "Push_enable_gateway": "啟用閘道", "Push_gateway": "閘道", @@ -3162,6 +3168,7 @@ "Statistics": "統計", "Statistics_reporting": "傳送統計資料至 Rocket.Chat", "Statistics_reporting_Description": "通過發送您的統計,你會幫助我們確定如何Rocket.Chat許多實例部署,以及系統是如何表現不錯,或是我們可以進一步改進。不要擔心,因為沒有使用者帳號的發送和收到的所有訊息都是保密的。", + "Stats_Active_Guests": "活躍的訪客", "Stats_Active_Users": "活躍使用者", "Stats_App_Users": "Rocket.Chat 應用程式使用者", "Stats_Avg_Channel_Users": "平均 Channel 人數", @@ -3445,6 +3452,7 @@ "Unnamed": "未命名", "Unpin": "取消釘選", "Unpin_Message": "解除釘選訊息", + "unpinning-not-allowed": "不允許取消釘選", "Unread": "未讀", "Unread_Count": "未讀數", "Unread_Count_DM": "直接訊息的未讀計數", @@ -3730,6 +3738,7 @@ "You_need_to_type_in_your_username_in_order_to_do_this": "您需要輸入使用者名稱才能執行此操作!", "You_need_to_verifiy_your_email_address_to_get_notications": "您需要驗證您的電子郵件地址以取得通知", "You_need_to_write_something": "你需要寫些東西!", + "You_reached_the_maximum_number_of_guest_users_allowed_by_your_license": "您已達到授權允許的最大訪客使用者數量。", "You_should_inform_one_url_at_least": "你應該確定至少一個網址。", "You_should_name_it_to_easily_manage_your_integrations": "你應該將其命名為輕鬆管理您的整合。", "You_will_not_be_able_to_recover": "您將無法恢復這則訊息!", @@ -3754,4 +3763,4 @@ "Your_server_link": "您的伺服器連接", "Your_temporary_password_is_password": "您的暫時密碼是 [password]。", "Your_workspace_is_ready": "您的工作區已準備好使用🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json index 5451e8d09fa6..c583accc2e93 100644 --- a/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -2380,8 +2380,6 @@ "Notification_RequireInteraction_Description": "仅适用于Chrome浏览器版本>50。使用requireInteraction 参数来无限期显示桌面通知,直到用户与其互动。", "Notification_Mobile_Default_For": "推送移动通知", "Notifications": "通知", - "Notifications_Always_Notify_Mobile": "始终通知移动", - "Notifications_Always_Notify_Mobile_Description": "选择始终通知移动设备,无论在线状态如何。", "Notifications_Duration": "通知持续时间", "Notifications_Max_Room_Members": "禁用所有消息通知之前的最大聊天室人数", "Notifications_Max_Room_Members_Description": "所有消息通知被禁用前的聊天室内的最大成员数量。用户仍然可以更改每个房间的设置,以单独接收所有通知。 (0禁用)", @@ -2581,9 +2579,6 @@ "Push_apn_dev_passphrase": "APN Dev 口令", "Push_apn_key": "APN 密钥", "Push_apn_passphrase": "APN 口令", - "Push_debug": "调试", - "Push_send_interval": "刷新推送消息的时间间隔", - "Push_send_batch_size": "每次批量处理的大小", "Push_enable": "启用", "Push_enable_gateway": "启用网关", "Push_gateway": "网关", From 90b591ac226676d0fee7130822e50546beb70fc1 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Mon, 4 May 2020 17:04:05 -0300 Subject: [PATCH 010/121] [NEW] Add Livechat website URL to the offline message e-mail (#17429) * Add Web Site URL from where the message has been sent. * Turn the host field mandatory. * Check the host variable beforing adding it to the html content. Co-authored-by: Marcos Spessatto Defendi --- app/livechat/server/api/v1/offlineMessage.js | 5 ++-- app/livechat/server/lib/Livechat.js | 30 +++++++++++--------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/livechat/server/api/v1/offlineMessage.js b/app/livechat/server/api/v1/offlineMessage.js index 1859b5dde3a8..6788c30e3d86 100644 --- a/app/livechat/server/api/v1/offlineMessage.js +++ b/app/livechat/server/api/v1/offlineMessage.js @@ -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/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 4050d17bfeaa..0b4cee820b4b 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -986,13 +986,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); @@ -1003,7 +1008,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); @@ -1013,15 +1018,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(() => { From 5182d59e8342b4a16bd8d2133733ba24effb8ec9 Mon Sep 17 00:00:00 2001 From: qwertiko GmbH Date: Mon, 4 May 2020 22:45:17 +0200 Subject: [PATCH 011/121] [FIX] Federation attachment URL for audio and video files (#16430) --- app/federation/server/endpoints/dispatch.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/federation/server/endpoints/dispatch.js b/app/federation/server/endpoints/dispatch.js index 43fd8641aa7b..468f21cb7265 100644 --- a/app/federation/server/endpoints/dispatch.js +++ b/app/federation/server/endpoints/dispatch.js @@ -225,10 +225,16 @@ const eventHandlers = { // Update the message's file denormalizedMessage.file._id = upload._id; - // Update the message's attachments + // Update the message's attachments dependent on type for (const attachment of denormalizedMessage.attachments) { attachment.title_link = attachment.title_link.replace(oldUploadId, upload._id); - attachment.image_url = attachment.image_url.replace(oldUploadId, upload._id); + if (/^image\/.+/.test(denormalizedMessage.file.type)) { + attachment.image_url = attachment.image_url.replace(oldUploadId, upload._id); + } else if (/^audio\/.+/.test(denormalizedMessage.file.type)) { + attachment.audio_url = attachment.audio_url.replace(oldUploadId, upload._id); + } else if (/^video\/.+/.test(denormalizedMessage.file.type)) { + attachment.video_url = attachment.video_url.replace(oldUploadId, upload._id); + } } } From 78c9a31116e4b1cf55437e089ca8de289485ecaa Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Mon, 4 May 2020 18:54:20 -0300 Subject: [PATCH 012/121] [NEW] Add the ability to send Livechat offline messages to a channel (#17442) * Add the ability to send livechat offline messages to channels. * Improvements on the codebase. - Add auto-complete to select the offline message channel - Add translations to the offline message sent * Add host origin. --- .../views/app/livechatDepartmentForm.html | 18 ++++++ .../views/app/livechatDepartmentForm.js | 44 +++++++++++++- app/livechat/server/config.js | 16 +++++ .../server/hooks/offlineMessageToChannel.js | 58 +++++++++++++++++++ app/livechat/server/index.js | 1 + packages/rocketchat-i18n/i18n/en.i18n.json | 8 ++- packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 8 ++- 7 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 app/livechat/server/hooks/offlineMessageToChannel.js diff --git a/app/livechat/client/views/app/livechatDepartmentForm.html b/app/livechat/client/views/app/livechatDepartmentForm.html index 6621458a767e..747de430b1ae 100644 --- a/app/livechat/client/views/app/livechatDepartmentForm.html +++ b/app/livechat/client/views/app/livechatDepartmentForm.html @@ -48,6 +48,24 @@ +
+ {{> livechatAutocompleteUser + onClickTag=onClickTagOfflineMessageChannel + list=selectedOfflineMessageChannel + onSelect=onSelectOfflineMessageChannel + collection='CachedChannelList' + endpoint='rooms.autocomplete.channelAndPrivate' + field='name' + sort='name' + label="Livechat_DepartmentOfflineMessageToChannel" + placeholder="Channel_name" + name="offlineMessageChannelName" + noMatchTemplate="roomSearchEmpty" + templateItem="popupList_item_channel" + modifier=offlineMessageChannelModifier + showLabel=true + }} +
diff --git a/app/livechat/client/views/app/livechatDepartmentForm.js b/app/livechat/client/views/app/livechatDepartmentForm.js index 59bbcec12286..1b3016f72af1 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) => `${ 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/server/config.js b/app/livechat/server/config.js index 4af508e8c01a..3b568b54a37f 100644 --- a/app/livechat/server/config.js +++ b/app/livechat/server/config.js @@ -366,6 +366,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/offlineMessageToChannel.js b/app/livechat/server/hooks/offlineMessageToChannel.js new file mode 100644 index 000000000000..8f63447e2f0f --- /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/index.js b/app/livechat/server/index.js index f6deeb0f8933..a42161d3b007 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -11,6 +11,7 @@ import './hooks/externalMessage'; import './hooks/leadCapture'; import './hooks/markRoomResponded'; import './hooks/offlineMessage'; +import './hooks/offlineMessageToChannel'; import './hooks/RDStation'; import './hooks/saveAnalyticsData'; import './hooks/sendToCRM'; diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 6eb99794b603..7b8156e97db5 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2137,6 +2137,7 @@ "Livechat_AllowedDomainsList": "Livechat Allowed Domains", "Livechat_Appearance": "Livechat Appearance", "Livechat_Dashboard": "Omnichannel Dashboard", + "Livechat_DepartmentOfflineMessageToChannel": "Send this department's Livechat offline messages to a channel", "Livechat_enabled": "Omnichannel enabled", "Livechat_Facebook_API_Key": "OmniChannel API Key", "Livechat_Facebook_API_Secret": "OmniChannel API Secret", @@ -2150,7 +2151,8 @@ "Livechat_Managers": "Managers", "Livechat_offline": "Omnichannel offline", "Livechat_online": "Omnichannel on-line", - "Livechat_offline_message_sent": "Omnichannel offline message sent", + "Livechat_OfflineMessageToChannel_enabled": "Send Livechat offline messages to a channel", + "Livechat_offline_message_sent": "Livechat offline message sent", "Livechat_Queue": "Omnichannel Queue", "Livechat_registration_form": "Registration Form", "Livechat_registration_form_message": "Registration Form Message", @@ -2456,6 +2458,7 @@ "New_discussion_first_message": "Usually, a discussion starts with a question, like \"How do I upload a picture?\"", "New_integration": "New integration", "New_line_message_compose_input": "`%s` - New line in message compose input", + "New_Livechat_offline_message_has_been_sent": "A new Livechat offline Message has been sent", "New_logs": "New logs", "New_Message_Notification": "New Message Notification", "New_messages": "New messages", @@ -3036,6 +3039,7 @@ "Send_your_JSON_payloads_to_this_URL": "Send your JSON payloads to this URL.", "Sending": "Sending...", "Sent_an_attachment": "Sent an attachment", + "Sent_from": "Sent from", "Served_By": "Served By", "Server_File_Path": "Server File Path", "Server_Folder_Path": "Server Folder Path", @@ -3662,7 +3666,9 @@ "Visible": "Visible", "Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today": "Visit __Site_URL__ and try the best open source chat solution available today!", "Visitor": "Visitor", + "Visitor_Email": "Visitor E-mail", "Visitor_Info": "Visitor Info", + "Visitor_Name": "Visitor Name", "Visitor_Navigation": "Visitor Navigation", "Visitor_page_URL": "Visitor page URL", "Visitor_time_on_site": "Visitor time on site", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index d93b11241dc8..d9d2106e9787 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -1958,6 +1958,7 @@ "Livechat_AllowedDomainsList": "Domínios permitidos em Livechat", "Livechat_Appearance": "Aparência do Livechat", "Livechat_Dashboard": "Painel Omnichannel", + "Livechat_DepartmentOfflineMessageToChannel": "Enviar as mensagens offline de Livechat deste departamento para um canal", "Livechat_enabled": "Omnichannel habilitado", "Livechat_Facebook_API_Key": "Chave da API OmniChannel", "Livechat_Facebook_API_Secret": "Secret da API OmniChannel", @@ -1970,7 +1971,8 @@ "Livechat_managers": "Gerentes do Omnichannel", "Livechat_offline": "Omnichannel offline", "Livechat_online": "Omnichannel on-line", - "Livechat_offline_message_sent": "Mensagem offline de Omnichannel enviada", + "Livechat_offline_message_sent": "Mensagem offline do Livechat enviada", + "Livechat_OfflineMessageToChannel_enabled": "Envie mensagens offline do Livechat para um canal", "Livechat_Queue": "Fila Omnichannel", "Livechat_registration_form": "Formulário de Registro", "Livechat_registration_form_message": "Mensagem do Formulário de Registro", @@ -2239,6 +2241,7 @@ "New_discussion_first_message": "Normalmente, uma discussão começa com uma pergunta, como \"Como faço o carregamento de uma imagem?\"", "New_integration": "Nova integração", "New_line_message_compose_input": "`%s` - Nova linha na mensagem compor a entrada", + "New_Livechat_offline_message_has_been_sent": "Uma nova mensagem offline de Livechat foi enviada", "New_logs": "Novos logs", "New_Message_Notification": "Notificação de nova mensagem", "New_messages": "Novas mensagens", @@ -2760,6 +2763,7 @@ "Send_your_JSON_payloads_to_this_URL": "Envie seu payload JSON para esta URL.", "Sending": "Enviando ...", "Sent_an_attachment": "Enviou um anexo", + "Sent_from": "Enviado de", "Served_By": "Atendido Por", "Server_Info": "Informações do servidor", "Server_Type": "Tipo de servidor", @@ -3319,7 +3323,9 @@ "Visible": "Visível", "Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today": "Visite __Site_URL__ e experimente a melhor solução de chat de código aberto disponível!", "Visitor": "Visitante", + "Visitor_Email": "E-mail do Visitante", "Visitor_Info": "Informações do Visitante", + "Visitor_Name": "Nome do Visitante", "Visitor_Navigation": "Navegação do Visitante", "Visitor_page_URL": "URL da página de visitante", "Visitor_time_on_site": "Tempo do visitante no site", From b3f2ce7ce856dfe504df63c46f5073b3d1f66411 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 4 May 2020 19:44:46 -0300 Subject: [PATCH 013/121] [FIX] Uncessary updates on Settings, Roles and Permissions on startup (#17160) --- app/api/server/api.js | 2 +- app/api/server/v1/chat.js | 2 +- app/api/server/v1/permissions.js | 2 +- app/api/server/v1/settings.js | 2 +- app/apps/server/orchestrator.js | 2 +- app/authorization/server/startup.js | 23 +- .../server/lib/startup.js | 4 +- app/channel-settings/server/startup.js | 6 +- app/cloud/server/index.js | 2 +- .../server/startup/permissions.js | 2 +- app/discussion/server/permissions.js | 4 +- app/lib/server/functions/createDirectRoom.js | 2 +- app/lib/server/startup/settings.js | 2 +- app/mail-messages/server/startup.js | 7 +- app/message-pin/server/settings.js | 6 +- .../server/startup/settings.js | 6 +- app/models/server/models/Permissions.js | 23 +- app/models/server/models/Roles.js | 29 +- app/settings/client/{index.js => index.ts} | 0 app/settings/client/lib/settings.js | 46 --- app/settings/client/lib/settings.ts | 50 +++ app/settings/index.js | 4 +- app/settings/lib/settings.js | 88 ----- app/settings/lib/settings.ts | 115 ++++++ app/settings/server/functions/settings.d.ts | 4 - app/settings/server/functions/settings.js | 309 --------------- .../server/functions/settings.mocks.ts | 33 ++ .../server/functions/settings.tests.ts | 319 +++++++++++++++ app/settings/server/functions/settings.ts | 364 ++++++++++++++++++ app/settings/server/{index.js => index.ts} | 0 .../client/models/CachedCollection.js | 16 +- client/main.d.ts | 12 + ee/app/auditing/server/index.js | 6 +- ee/app/canned-responses/server/permissions.js | 6 +- .../livechat-enterprise/server/permissions.js | 8 +- mocha.opts | 4 + package-lock.json | 76 ++++ package.json | 20 +- tsconfig.json | 3 +- 39 files changed, 1069 insertions(+), 540 deletions(-) rename app/settings/client/{index.js => index.ts} (100%) delete mode 100644 app/settings/client/lib/settings.js create mode 100644 app/settings/client/lib/settings.ts delete mode 100644 app/settings/lib/settings.js create mode 100644 app/settings/lib/settings.ts delete mode 100644 app/settings/server/functions/settings.d.ts delete mode 100644 app/settings/server/functions/settings.js create mode 100644 app/settings/server/functions/settings.mocks.ts create mode 100644 app/settings/server/functions/settings.tests.ts create mode 100644 app/settings/server/functions/settings.ts rename app/settings/server/{index.js => index.ts} (100%) create mode 100644 client/main.d.ts diff --git a/app/api/server/api.js b/app/api/server/api.js index d62382af8755..5a834be36553 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -374,7 +374,7 @@ export class APIClass extends Restivus { 'error-unauthorized': 'unauthorized', }[e.error] || 'failure'; - result = API.v1[apiMethod](typeof e === 'string' ? e : e.message, e.error, undefined, e); + result = API.v1[apiMethod](typeof e === 'string' ? e : e.message, e.error, process.env.TEST_MODE ? e.stack : undefined, e); } finally { delete Accounts._accountData[connection.id]; } diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js index 8e5ac669d229..69685a3f6f97 100644 --- a/app/api/server/v1/chat.js +++ b/app/api/server/v1/chat.js @@ -4,7 +4,7 @@ import { Match, check } from 'meteor/check'; import { Messages } from '../../../models'; import { canAccessRoom, hasPermission } from '../../../authorization'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; -import { processWebhookMessage } from '../../../lib'; +import { processWebhookMessage } from '../../../lib/server'; import { API } from '../api'; import Rooms from '../../../models/server/models/Rooms'; import Users from '../../../models/server/models/Users'; diff --git a/app/api/server/v1/permissions.js b/app/api/server/v1/permissions.js index c5d790533287..e74c95662517 100644 --- a/app/api/server/v1/permissions.js +++ b/app/api/server/v1/permissions.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { hasPermission } from '../../../authorization'; -import { Permissions, Roles } from '../../../models'; +import { Permissions, Roles } from '../../../models/server'; import { API } from '../api'; /** diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js index 1fd8dba80c0b..42db479903fe 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.js @@ -3,7 +3,7 @@ import { Match, check } from 'meteor/check'; import { ServiceConfiguration } from 'meteor/service-configuration'; import _ from 'underscore'; -import { Settings } from '../../../models'; +import { Settings } from '../../../models/server'; import { hasPermission } from '../../../authorization'; import { API } from '../api'; diff --git a/app/apps/server/orchestrator.js b/app/apps/server/orchestrator.js index 502ea2537a75..da9fca47fe61 100644 --- a/app/apps/server/orchestrator.js +++ b/app/apps/server/orchestrator.js @@ -24,7 +24,7 @@ class AppServerOrchestrator { initialize() { this._rocketchatLogger = new Logger('Rocket.Chat Apps'); - Permissions.createOrUpdate('manage-apps', ['admin']); + Permissions.create('manage-apps', ['admin']); this._marketplaceUrl = 'https://marketplace.rocket.chat'; diff --git a/app/authorization/server/startup.js b/app/authorization/server/startup.js index 16b2f8906580..0a9dba5a9ff0 100644 --- a/app/authorization/server/startup.js +++ b/app/authorization/server/startup.js @@ -1,7 +1,7 @@ /* eslint no-multi-spaces: 0 */ import { Meteor } from 'meteor/meteor'; -import { Roles, Permissions, Settings } from '../../models'; +import { Roles, Permissions, Settings } from '../../models/server'; import { settings } from '../../settings/server'; import { getSettingPermissionId, CONSTANTS } from '../lib'; import { clearCache } from './functions/hasPermission'; @@ -114,9 +114,7 @@ Meteor.startup(function() { ]; for (const permission of permissions) { - if (!Permissions.findOneById(permission._id)) { - Permissions.upsert(permission._id, { $set: permission }); - } + Permissions.create(permission._id, permission.roles); } const defaultRoles = [ @@ -134,7 +132,7 @@ Meteor.startup(function() { ]; for (const role of defaultRoles) { - Roles.upsert({ _id: role.name }, { $setOnInsert: { scope: role.scope, description: role.description || '', protected: true, mandatory2fa: false } }); + Roles.createOrUpdate(role.name, role.scope, role.description, true, false); } const getPreviousPermissions = function(settingId) { @@ -155,19 +153,17 @@ Meteor.startup(function() { const createSettingPermission = function(setting, previousSettingPermissions) { const permissionId = getSettingPermissionId(setting._id); const permission = { - _id: permissionId, level: CONSTANTS.SETTINGS_LEVEL, // copy those setting-properties which are needed to properly publish the setting-based permissions settingId: setting._id, group: setting.group, section: setting.section, sorter: setting.sorter, + roles: [], }; // copy previously assigned roles if available if (previousSettingPermissions[permissionId] && previousSettingPermissions[permissionId].roles) { permission.roles = previousSettingPermissions[permissionId].roles; - } else { - permission.roles = []; } if (setting.group) { permission.groupPermissionId = getSettingPermissionId(setting.group); @@ -175,7 +171,16 @@ Meteor.startup(function() { if (setting.section) { permission.sectionPermissionId = getSettingPermissionId(setting.section); } - Permissions.upsert(permission._id, { $set: permission }); + + const existent = Permissions.findOne({ + _id: permissionId, + ...permission, + }, { fields: { _id: 1 } }); + + if (!existent) { + Permissions.upsert({ _id: permissionId }, { $set: permission }); + } + delete previousSettingPermissions[permissionId]; }; diff --git a/app/channel-settings-mail-messages/server/lib/startup.js b/app/channel-settings-mail-messages/server/lib/startup.js index a04875ad90da..a079a2c4bc76 100644 --- a/app/channel-settings-mail-messages/server/lib/startup.js +++ b/app/channel-settings-mail-messages/server/lib/startup.js @@ -7,7 +7,5 @@ Meteor.startup(function() { _id: 'mail-messages', roles: ['admin'], }; - return Permissions.upsert(permission._id, { - $setOnInsert: permission, - }); + return Permissions.create(permission._id, permission.roles); }); diff --git a/app/channel-settings/server/startup.js b/app/channel-settings/server/startup.js index 698723ebe994..5e9aeb7baaf2 100644 --- a/app/channel-settings/server/startup.js +++ b/app/channel-settings/server/startup.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { Permissions } from '../../models'; Meteor.startup(function() { - Permissions.upsert('post-readonly', { $setOnInsert: { roles: ['admin', 'owner', 'moderator'] } }); - Permissions.upsert('set-readonly', { $setOnInsert: { roles: ['admin', 'owner'] } }); - Permissions.upsert('set-react-when-readonly', { $setOnInsert: { roles: ['admin', 'owner'] } }); + Permissions.create('post-readonly', ['admin', 'owner', 'moderator']); + Permissions.create('set-readonly', ['admin', 'owner']); + Permissions.create('set-react-when-readonly', ['admin', 'owner']); }); diff --git a/app/cloud/server/index.js b/app/cloud/server/index.js index 58bd467b4dfd..f783f4887923 100644 --- a/app/cloud/server/index.js +++ b/app/cloud/server/index.js @@ -11,7 +11,7 @@ import { Permissions } from '../../models'; import { settings } from '../../settings/server'; if (Permissions) { - Permissions.createOrUpdate('manage-cloud', ['admin']); + Permissions.create('manage-cloud', ['admin']); } const licenseCronName = 'Cloud Workspace Sync'; diff --git a/app/custom-sounds/server/startup/permissions.js b/app/custom-sounds/server/startup/permissions.js index 7a9ba36a56c7..9e66458ea367 100644 --- a/app/custom-sounds/server/startup/permissions.js +++ b/app/custom-sounds/server/startup/permissions.js @@ -4,6 +4,6 @@ import { Permissions } from '../../../models'; Meteor.startup(() => { if (Permissions) { - Permissions.createOrUpdate('manage-sounds', ['admin']); + Permissions.create('manage-sounds', ['admin']); } }); diff --git a/app/discussion/server/permissions.js b/app/discussion/server/permissions.js index 49bfe798b0b4..2f17e05d9414 100644 --- a/app/discussion/server/permissions.js +++ b/app/discussion/server/permissions.js @@ -10,8 +10,6 @@ Meteor.startup(() => { ]; for (const permission of permissions) { - if (!Permissions.findOneById(permission._id)) { - Permissions.upsert(permission._id, { $set: permission }); - } + Permissions.create(permission._id, permission.roles); } }); diff --git a/app/lib/server/functions/createDirectRoom.js b/app/lib/server/functions/createDirectRoom.js index 93aaa63a4602..5474e2d67473 100644 --- a/app/lib/server/functions/createDirectRoom.js +++ b/app/lib/server/functions/createDirectRoom.js @@ -1,5 +1,5 @@ import { Rooms, Subscriptions } from '../../../models/server'; -import { settings } from '../../../settings/lib/settings'; +import { settings } from '../../../settings/server'; import { getDefaultSubscriptionPref } from '../../../utils/server'; import { callbacks } from '../../../callbacks/server'; diff --git a/app/lib/server/startup/settings.js b/app/lib/server/startup/settings.js index 241db8151ea8..4b0c7bfc1135 100644 --- a/app/lib/server/startup/settings.js +++ b/app/lib/server/startup/settings.js @@ -2756,7 +2756,7 @@ settings.addGroup('Setup_Wizard', function() { secret: true, }); - this.add('Cloud_Workspace_Access_Token_Expires_At', new Date(), { + this.add('Cloud_Workspace_Access_Token_Expires_At', new Date(0), { type: 'date', hidden: true, readonly: true, diff --git a/app/mail-messages/server/startup.js b/app/mail-messages/server/startup.js index 10dd89d2b8c0..eadfc796e776 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/message-pin/server/settings.js b/app/message-pin/server/settings.js index c2af7eaf2761..c16f82a183c3 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 04047eb56bfc..15f8c349e8e6 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/models/server/models/Permissions.js b/app/models/server/models/Permissions.js index d1b17ce64698..009f29d37f9d 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 2715ab05d7c1..f14c0b9ac15e 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/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 bd824705baec..000000000000 --- 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 000000000000..712eadc6c37d --- /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 a67eca871efb..1f3257e45429 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 ab5595f90ac9..000000000000 --- 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 000000000000..f3aa1f2d0bf5 --- /dev/null +++ b/app/settings/lib/settings.ts @@ -0,0 +1,115 @@ +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, 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); + }); + } + + 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)); + } + }); + Object.keys(this.regexCallbacks).forEach((cbKey) => { + const cbValue = this.regexCallbacks.get(cbKey); + 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 aab7040d6ccd..000000000000 --- 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 7a7ae4a47c12..000000000000 --- 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 000000000000..16bad40274cb --- /dev/null +++ b/app/settings/server/functions/settings.mocks.ts @@ -0,0 +1,33 @@ +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); + this.upsertCalls++; + } +} + +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 000000000000..2cc47778628a --- /dev/null +++ b/app/settings/server/functions/settings.tests.ts @@ -0,0 +1,319 @@ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-env mocha */ +import { Meteor } from 'meteor/meteor'; +import { expect } from 'chai'; + +import { Settings } from './settings.mocks'; +import { settings } from './settings'; + +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', () => { + 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 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); + + 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); + }); +}); diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts new file mode 100644 index 000000000000..27a9881b8c36 --- /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); + } + options.processEnvValue = value; + options.valueSource = 'processEnvValue'; + } else if (typeof Meteor.settings[_id] !== 'undefined') { + if (Meteor.settings[_id] == null) { + return false; + } + + 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); + } + 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/ui-cached-collection/client/models/CachedCollection.js b/app/ui-cached-collection/client/models/CachedCollection.js index 8450ee6b879d..bcd1ffc5b6bb 100644 --- a/app/ui-cached-collection/client/models/CachedCollection.js +++ b/app/ui-cached-collection/client/models/CachedCollection.js @@ -120,11 +120,11 @@ 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, @@ -134,13 +134,13 @@ export class CachedCollection extends EventEmitter { 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/client/main.d.ts b/client/main.d.ts new file mode 100644 index 000000000000..fbcc7568aa94 --- /dev/null +++ b/client/main.d.ts @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/interface-name-prefix */ + +declare module 'meteor/reactive-dict' { + const ReactiveDict: ReactiveDictStatic; + interface ReactiveDictStatic { + new (name: string, initialValue?: T): ReactiveDict; + } + interface ReactiveDict { + get(name: string): T; + set(name: string, newValue: T): void; + } +} diff --git a/ee/app/auditing/server/index.js b/ee/app/auditing/server/index.js index dd531c96949b..a816e3a1bc2c 100644 --- a/ee/app/auditing/server/index.js +++ b/ee/app/auditing/server/index.js @@ -19,13 +19,11 @@ onLicense('auditing', () => { ]; permissions.forEach((permission) => { - if (!Permissions.findOneById(permission._id)) { - Permissions.upsert(permission._id, { $set: permission }); - } + Permissions.create(permission._id, permission.roles); }); defaultRoles.forEach((role) => - Roles.upsert({ _id: role.name }, { $setOnInsert: { scope: role.scope, description: role.description || '', protected: true } }), + Roles.createOrUpdate(role.name, role.scope, role.description), ); }); }); diff --git a/ee/app/canned-responses/server/permissions.js b/ee/app/canned-responses/server/permissions.js index 4adec4cd7c13..5c8100a514d0 100644 --- a/ee/app/canned-responses/server/permissions.js +++ b/ee/app/canned-responses/server/permissions.js @@ -4,8 +4,8 @@ import { Permissions } from '../../../../app/models'; Meteor.startup(() => { if (Permissions) { - Permissions.createOrUpdate('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); - Permissions.createOrUpdate('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); - Permissions.createOrUpdate('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); + Permissions.create('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); + Permissions.create('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); + Permissions.create('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']); } }); diff --git a/ee/app/livechat-enterprise/server/permissions.js b/ee/app/livechat-enterprise/server/permissions.js index c0a0e802842d..ee3b286fe6a2 100644 --- a/ee/app/livechat-enterprise/server/permissions.js +++ b/ee/app/livechat-enterprise/server/permissions.js @@ -39,8 +39,8 @@ export const createPermissions = () => { permissions.map((p) => Permissions.addRole(p, livechatMonitorRole)); - Permissions.createOrUpdate('manage-livechat-units', [adminRole, livechatManagerRole]); - Permissions.createOrUpdate('manage-livechat-monitors', [adminRole, livechatManagerRole]); - Permissions.createOrUpdate('manage-livechat-tags', [adminRole, livechatManagerRole]); - Permissions.createOrUpdate('manage-livechat-priorities', [adminRole, livechatManagerRole]); + Permissions.create('manage-livechat-units', [adminRole, livechatManagerRole]); + Permissions.create('manage-livechat-monitors', [adminRole, livechatManagerRole]); + Permissions.create('manage-livechat-tags', [adminRole, livechatManagerRole]); + Permissions.create('manage-livechat-priorities', [adminRole, livechatManagerRole]); }; diff --git a/mocha.opts b/mocha.opts index 98d12fa40c7b..a2b834891a7d 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1,4 +1,8 @@ +--require ts-node/register --require babel-mocha-es6-compiler --require babel-polyfill --reporter spec --ui bdd +--watch-extensions ts +--extension ts +app/**/*.tests.js app/**/*.tests.ts diff --git a/package-lock.json b/package-lock.json index 4732a19cc78b..053b53b46297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6217,6 +6217,12 @@ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==" }, + "@types/chai": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz", + "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -6345,6 +6351,21 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" }, + "@types/mocha": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", + "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", + "dev": true + }, + "@types/mock-require": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mock-require/-/mock-require-2.0.0.tgz", + "integrity": "sha512-nOgjoE5bBiDeiA+z41i95makyHUSMWQMOPocP+J67Pqx/68HAXaeWN1NFtrAYYV6LrISIZZ8vKHm/a50k0f6Sg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/mongodb": { "version": "3.5.8", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.8.tgz", @@ -7227,6 +7248,12 @@ "readable-stream": "^2.0.6" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -13071,6 +13098,12 @@ } } }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -20597,6 +20630,12 @@ "pify": "^3.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "make-plural": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.1.0.tgz", @@ -29727,6 +29766,37 @@ "integrity": "sha512-UGTRZu1evMw4uTPyYF66/KFd22XiU+jMaIuHrkIHQ2GivAXVlLV0v/vHrpOuTRf9BmpNHi/SO7Vd0rLu0y57jg==", "dev": true }, + "ts-node": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz", + "integrity": "sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "3.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, "ts-pnp": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.5.tgz", @@ -31319,6 +31389,12 @@ } } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "zip-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.0.1.tgz", diff --git a/package.json b/package.json index 3082257f2656..0997ebeb08cb 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,6 @@ "name": "Rocket.Chat", "url": "https://rocket.chat/" }, - "mocha": { - "tests": [ - "app/**/*.tests.js" - ], - "files": [ - "app/**/*.mocks.js", - "app/**/*.js", - "!app/**/*.tests.js" - ] - }, "keywords": [ "rocketchat", "rocket", @@ -32,11 +22,11 @@ "stylelint": "stylelint \"app/**/*.css\" \"client/**/*.css\" \"app/**/*.less\" \"client/**/*.less\" \"ee/**/*.less\"", "deploy": "npm run build && pm2 startOrRestart pm2.json", "postinstall": "node .scripts/npm-postinstall.js", - "testunit-watch": "mocha --watch --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"", - "coverage": "nyc -r html mocha --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"", + "coverage": "nyc -r html mocha --opts ./mocha.opts", "test": "node .scripts/start.js", "testui": "cypress run --project tests", - "testunit": "mocha --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"", + "testunit": "mocha --opts ./mocha.opts", + "testunit-watch": "mocha --watch --opts ./mocha.opts", "testapi": "mocha --opts ./mocha_api.opts", "testapps": "mocha --opts ./mocha_apps.opts", "testci": "npm run testapi && npm run testapps && npm run testui", @@ -73,7 +63,10 @@ "@storybook/addons": "^5.2.8", "@storybook/react": "^5.2.8", "@types/bcrypt": "^3.0.0", + "@types/chai": "^4.2.11", "@types/meteor": "^1.4.37", + "@types/mocha": "^7.0.2", + "@types/mock-require": "^2.0.0", "@types/mongodb": "^3.5.8", "@typescript-eslint/eslint-plugin": "^2.11.0", "@typescript-eslint/parser": "^2.11.0", @@ -116,6 +109,7 @@ "stylelint": "^9.9.0", "stylelint-order": "^2.0.0", "supertest": "^3.3.0", + "ts-node": "^8.8.2", "typescript": "^3.7.3", "webpack": "^4.29.3" }, diff --git a/tsconfig.json b/tsconfig.json index 3ac6fd928b03..ce035f036be6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "esNext", + "module": "CommonJS", "target": "es2018", "lib": ["esnext", "dom"], @@ -29,7 +29,6 @@ }, "moduleResolution": "node", "resolveJsonModule": true, - "types": ["node"], "esModuleInterop": true, "preserveSymlinks": true, From aaf9083633aecc71e707d780df5424bd380f78ea Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Tue, 5 May 2020 09:30:08 -0300 Subject: [PATCH 014/121] Allow to filter omnichannel analytics dashboards per departments. (#17463) Co-authored-by: Marcos Spessatto Defendi --- app/livechat/client/stylesheets/livechat.css | 3 +- .../app/analytics/livechatAnalytics.html | 22 ++++ .../views/app/analytics/livechatAnalytics.js | 49 +++++++- .../analytics/livechatRealTimeMonitoring.html | 21 ++++ .../analytics/livechatRealTimeMonitoring.js | 118 ++++++++++++++---- .../imports/server/rest/dashboards.js | 48 +++++-- app/livechat/server/lib/Analytics.js | 79 ++++++------ .../server/lib/analytics/dashboards.js | 6 +- app/models/server/models/LivechatRooms.js | 9 +- .../server/raw/LivechatAgentActivity.js | 2 +- app/models/server/raw/LivechatRooms.js | 40 +++--- app/models/server/raw/LivechatVisitors.js | 3 +- app/models/server/raw/Users.js | 2 +- 13 files changed, 297 insertions(+), 105 deletions(-) diff --git a/app/livechat/client/stylesheets/livechat.css b/app/livechat/client/stylesheets/livechat.css index 3a13815dbe11..f80d646c0b5c 100644 --- a/app/livechat/client/stylesheets/livechat.css +++ b/app/livechat/client/stylesheets/livechat.css @@ -220,8 +220,7 @@ .lc-analytics-table { display: flex; - height: ~"calc(100% - 280px)"; - height: ~"-webkit-calc-height(100% - 280px)"; + height: 100%; min-height: 300px; flex-flow: column; diff --git a/app/livechat/client/views/app/analytics/livechatAnalytics.html b/app/livechat/client/views/app/analytics/livechatAnalytics.html index 6ae03f99cb5f..35ace4bfd801 100644 --- a/app/livechat/client/views/app/analytics/livechatAnalytics.html +++ b/app/livechat/client/views/app/analytics/livechatAnalytics.html @@ -10,6 +10,28 @@
+ {{#if hasDepartments }} +
+ {{> livechatAutocompleteUser + onClickTag=onClickTagDepartment + list=selectedDepartments + onSelect=onSelectDepartments + collection='CachedDepartmentList' + endpoint='livechat/department.autocomplete' + field='name' + sort='name' + placeholder="Select_a_department" + name="department" + icon="queue" + noMatchTemplate="userSearchEmpty" + templateItem="popupList_item_channel" + template="roomSearch" + noMatchTemplate="roomSearchEmpty" + modifier=departmentModifier + }} +
+ {{/if}} +
{{#if showLeftNavButton}}
+ {{#if hasDepartments }} +
+ {{> livechatAutocompleteUser + onClickTag=onClickTagDepartment + list=selectedDepartments + onSelect=onSelectDepartments + collection='CachedDepartmentList' + endpoint='livechat/department.autocomplete' + field='name' + sort='name' + placeholder="Select_a_department" + name="department" + icon="queue" + noMatchTemplate="userSearchEmpty" + templateItem="popupList_item_channel" + template="roomSearch" + noMatchTemplate="roomSearchEmpty" + modifier=departmentModifier + }} +
+ {{/if}} {{#if isLoading}} {{> loading }} diff --git a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js b/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js index 153bb24440d7..584e0f003b1f 100644 --- a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js +++ b/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js @@ -102,6 +102,8 @@ const updateChartData = async (chartId, label, data) => { let timer; +const getChartDepartment = (department) => department?._id; + const getDaterange = () => { const today = moment(new Date()); return { @@ -110,8 +112,11 @@ const getDaterange = () => { }; }; -const loadConversationOverview = async ({ start, end }) => { - const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/conversation-totalizers?start=${ start }&end=${ end }`); +const parseAdditionalParams = (options = {}, prefix = '') => `${ prefix }${ Object.keys(options).map((key) => `${ key }=${ options[key] }`).join('&') }`; + +const loadConversationOverview = async ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/conversation-totalizers?start=${ start }&end=${ end }${ additionalParams }`); return totalizers; }; @@ -121,8 +126,9 @@ const updateConversationOverview = async (totalizers) => { } }; -const loadAgentsOverview = async ({ start, end }) => { - const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/agents-productivity-totalizers?start=${ start }&end=${ end }`); +const loadAgentsOverview = async ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/agents-productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`); return totalizers; }; @@ -131,8 +137,9 @@ const updateAgentsOverview = async (totalizers) => { templateInstance.agentsOverview.set(totalizers); } }; -const loadChatsOverview = async ({ start, end }) => { - const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/chats-totalizers?start=${ start }&end=${ end }`); +const loadChatsOverview = async ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/chats-totalizers?start=${ start }&end=${ end }${ additionalParams }`); return totalizers; }; @@ -142,8 +149,9 @@ const updateChatsOverview = async (totalizers) => { } }; -const loadProductivityOverview = async ({ start, end }) => { - const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/productivity-totalizers?start=${ start }&end=${ end }`); +const loadProductivityOverview = async ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`); return totalizers; }; @@ -153,7 +161,10 @@ const updateProductivityOverview = async (totalizers) => { } }; -const loadChatsChartData = ({ start, end }) => APIClient.v1.get(`livechat/analytics/dashboards/charts/chats?start=${ start }&end=${ end }`); +const loadChatsChartData = ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + return APIClient.v1.get(`livechat/analytics/dashboards/charts/chats?start=${ start }&end=${ end }${ additionalParams }`); +}; const updateChatsChart = async ({ open, closed, queued }) => { await updateChartData('lc-chats-chart', 'Open', [open]); @@ -161,19 +172,26 @@ const updateChatsChart = async ({ open, closed, queued }) => { await updateChartData('lc-chats-chart', 'Queue', [queued]); }; -const loadChatsPerAgentChartData = async ({ start, end }) => { - const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-agent?start=${ start }&end=${ end }`); +const loadChatsPerAgentChartData = async ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-agent?start=${ start }&end=${ end }${ additionalParams }`); delete result.success; return result; }; -const updateChatsPerAgentChart = (agents) => { +const updateChatsPerAgentChart = async (agents) => { + // this chart need to reset before new updates + chartContexts['lc-chats-per-agent-chart'] = await initChart['lc-chats-per-agent-chart'](); + Object .keys(agents) .forEach((agent) => updateChartData('lc-chats-per-agent-chart', agent, [agents[agent].open, agents[agent].closed])); }; -const loadAgentsStatusChartData = () => APIClient.v1.get('livechat/analytics/dashboards/charts/agents-status'); +const loadAgentsStatusChartData = ({ departmentId }) => { + const additionalParams = parseAdditionalParams({ departmentId }, '?'); + return APIClient.v1.get(`livechat/analytics/dashboards/charts/agents-status${ additionalParams }`); +}; const updateAgentStatusChart = async (statusData) => { if (!statusData) { @@ -186,19 +204,26 @@ const updateAgentStatusChart = async (statusData) => { await updateChartData('lc-agents-chart', 'Busy', [statusData.busy]); }; -const loadChatsPerDepartmentChartData = async ({ start, end }) => { - const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-department?start=${ start }&end=${ end }`); +const loadChatsPerDepartmentChartData = async ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-department?start=${ start }&end=${ end }${ additionalParams }`); delete result.success; return result; }; -const updateDepartmentsChart = (departments) => { +const updateDepartmentsChart = async (departments) => { + // this chart need to reset before new updates + chartContexts['lc-chats-per-dept-chart'] = await initChart['lc-chats-per-dept-chart'](); + Object .keys(departments) .forEach((department) => updateChartData('lc-chats-per-dept-chart', department, [departments[department].open, departments[department].closed])); }; -const loadTimingsChartData = ({ start, end }) => APIClient.v1.get(`livechat/analytics/dashboards/charts/timings?start=${ start }&end=${ end }`); +const loadTimingsChartData = ({ start, end, ...options }) => { + const additionalParams = parseAdditionalParams(options, '&'); + return APIClient.v1.get(`livechat/analytics/dashboards/charts/timings?start=${ start }&end=${ end }${ additionalParams }`); +}; const updateTimingsChart = async (timingsData) => { const hour = moment(new Date()).format('H'); @@ -229,9 +254,27 @@ Template.livechatRealTimeMonitoring.helpers({ isLoading() { return Template.instance().isLoading.get(); }, + departmentModifier() { + return (filter, text = '') => { + const f = filter.get(); + return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `${ part }`) }`; + }; + }, + onClickTagDepartment() { + return Template.instance().onClickTagDepartment; + }, + selectedDepartments() { + return Template.instance().selectedDepartments.get(); + }, + onSelectDepartments() { + return Template.instance().onSelectDepartments; + }, + hasDepartments() { + return Template.instance().hasDepartments.get(); + }, }); -Template.livechatRealTimeMonitoring.onCreated(function() { +Template.livechatRealTimeMonitoring.onCreated(async function() { templateInstance = Template.instance(); this.isLoading = new ReactiveVar(false); this.conversationsOverview = new ReactiveVar(); @@ -240,22 +283,43 @@ Template.livechatRealTimeMonitoring.onCreated(function() { this.agentsOverview = new ReactiveVar(); this.conversationTotalizers = new ReactiveVar([]); this.interval = new ReactiveVar(5); + this.selectedDepartments = new ReactiveVar([]); + this.hasDepartments = new ReactiveVar(false); + + this.onSelectDepartments = ({ item: department }) => { + department.text = department.name; + this.selectedDepartments.set([department]); + }; + + this.onClickTagDepartment = () => { + this.selectedDepartments.set([]); + }; + + const { departments } = await APIClient.v1.get('livechat/department?count=1'); + this.hasDepartments.set(departments?.length > 0); }); Template.livechatRealTimeMonitoring.onRendered(async function() { await initAllCharts(); this.updateDashboard = async () => { + const [department] = this.selectedDepartments.get(); + const departmentId = getChartDepartment(department); const daterange = getDaterange(); - updateConversationOverview(await loadConversationOverview(daterange)); - updateProductivityOverview(await loadProductivityOverview(daterange)); - updateChatsChart(await loadChatsChartData(daterange)); - updateChatsPerAgentChart(await loadChatsPerAgentChartData(daterange)); - updateAgentStatusChart(await loadAgentsStatusChartData()); - updateDepartmentsChart(await loadChatsPerDepartmentChartData(daterange)); - updateTimingsChart(await loadTimingsChartData(daterange)); - updateAgentsOverview(await loadAgentsOverview(daterange)); - updateChatsOverview(await loadChatsOverview(daterange)); + const filters = Object.assign( + { ...daterange }, + departmentId && { departmentId }, + ); + + updateConversationOverview(await loadConversationOverview(filters)); + updateProductivityOverview(await loadProductivityOverview(filters)); + updateChatsChart(await loadChatsChartData(filters)); + updateChatsPerAgentChart(await loadChatsPerAgentChartData(filters)); + updateAgentStatusChart(await loadAgentsStatusChartData(filters)); + updateDepartmentsChart(await loadChatsPerDepartmentChartData(filters)); + updateTimingsChart(await loadTimingsChartData(filters)); + updateAgentsOverview(await loadAgentsOverview(filters)); + updateChatsOverview(await loadChatsOverview(filters)); }; this.autorun(() => { if (timer) { diff --git a/app/livechat/imports/server/rest/dashboards.js b/app/livechat/imports/server/rest/dashboards.js index 0e1f8cedb277..2537942e74dc 100644 --- a/app/livechat/imports/server/rest/dashboards.js +++ b/app/livechat/imports/server/rest/dashboards.js @@ -1,4 +1,4 @@ -import { check } from 'meteor/check'; +import { Match, check } from 'meteor/check'; import { API } from '../../../../api'; import { hasPermission } from '../../../../authorization/server'; @@ -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/server/lib/Analytics.js b/app/livechat/server/lib/Analytics.js index 6832a8f982d6..b55486751f82 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/analytics/dashboards.js b/app/livechat/server/lib/analytics/dashboards.js index 62fa53e8ee42..e3d9c2db552c 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/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 7e9ceb876ce2..25e2e51c6be2 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -351,31 +351,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 +386,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/raw/LivechatAgentActivity.js b/app/models/server/raw/LivechatAgentActivity.js index 7a86cf1bac8a..9d48cf45a832 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 e996eac6e368..39fa94546222 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 4faadab58a7d..7ee3f02f2cca 100644 --- a/app/models/server/raw/LivechatVisitors.js +++ b/app/models/server/raw/LivechatVisitors.js @@ -1,12 +1,13 @@ 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 } }); diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index 803e1c82e128..245ba2b579df 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); From 36e72db200a1c12229844b7a160505c91dd4b391 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 5 May 2020 13:24:49 -0300 Subject: [PATCH 015/121] [FIX] Reactions may present empty names of who reacted when using Real Names (#17536) --- app/utils/server/lib/normalizeMessagesForUser.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/utils/server/lib/normalizeMessagesForUser.js b/app/utils/server/lib/normalizeMessagesForUser.js index 30cc42bfa188..bcd395fa640b 100644 --- a/app/utils/server/lib/normalizeMessagesForUser.js +++ b/app/utils/server/lib/normalizeMessagesForUser.js @@ -11,6 +11,10 @@ const filterStarred = (message, uid) => { // TODO: we should let clients get user names on demand instead of doing this +function getNameOfUsername(users, username) { + return users.get(username) || username; +} + export const normalizeMessagesForUser = (messages, uid) => { // if not using real names, there is nothing else to do if (!settings.get('UI_Use_Real_Name')) { @@ -33,7 +37,7 @@ export const normalizeMessagesForUser = (messages, uid) => { .forEach((reaction) => reaction.usernames.forEach((username) => usernames.add(username))); }); - const users = {}; + const names = new Map(); Users.findUsersByUsernames([...usernames.values()], { fields: { @@ -41,20 +45,19 @@ export const normalizeMessagesForUser = (messages, uid) => { name: 1, }, }).forEach((user) => { - users[user.username] = user.name; + names.set(user.username, user.name); }); messages.forEach((message) => { if (!message.u) { return; } - message.u.name = users[message.u.username]; + message.u.name = getNameOfUsername(names, message.u.username); - (message.mentions || []).forEach((mention) => { mention.name = users[mention.username]; }); + (message.mentions || []).forEach((mention) => { mention.name = getNameOfUsername(names, mention.username); }); Object.keys(message.reactions || {}).forEach((reaction) => { - const names = message.reactions[reaction].usernames.map((username) => users[username]); - message.reactions[reaction].names = names; + message.reactions[reaction].names = message.reactions[reaction].usernames.map((username) => getNameOfUsername(names, username)); }); }); From 6c0495e83be22cb05015349aaf8584d2b55f5daa Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Tue, 5 May 2020 14:18:06 -0300 Subject: [PATCH 016/121] [BREAK] Remove deprecated Omnichannel Knowledge Base feature (#17387) --- app/livechat/client/ui.js | 9 --- .../views/app/tabbar/externalSearch.html | 18 ----- .../client/views/app/tabbar/externalSearch.js | 57 --------------- app/livechat/client/views/regular.js | 1 - app/livechat/server/config.js | 25 ------- app/livechat/server/hooks/externalMessage.js | 72 ------------------- app/livechat/server/index.js | 1 - server/startup/migrations/index.js | 1 + server/startup/migrations/v189.js | 11 +++ 9 files changed, 12 insertions(+), 183 deletions(-) delete mode 100644 app/livechat/client/views/app/tabbar/externalSearch.html delete mode 100644 app/livechat/client/views/app/tabbar/externalSearch.js delete mode 100644 app/livechat/server/hooks/externalMessage.js create mode 100644 server/startup/migrations/v189.js diff --git a/app/livechat/client/ui.js b/app/livechat/client/ui.js index 3a4d3fb73f2b..23b945147195 100644 --- a/app/livechat/client/ui.js +++ b/app/livechat/client/ui.js @@ -45,15 +45,6 @@ TabBar.addGroup('uploaded-files-list', ['live']); TabBar.addGroup('push-notifications', ['live']); TabBar.addGroup('video', ['live']); -TabBar.addButton({ - groups: ['live'], - id: 'external-search', - i18nTitle: 'Knowledge_Base', - icon: 'book', - template: 'externalSearch', - order: 10, -}); - MessageTypes.registerType({ id: 'livechat-close', system: true, 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 a08f388c9d8a..000000000000 --- a/app/livechat/client/views/app/tabbar/externalSearch.html +++ /dev/null @@ -1,18 +0,0 @@ - \ 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 2bea37e8c91d..000000000000 --- 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/regular.js b/app/livechat/client/views/regular.js index 1fc7ddf9441a..30f1a72c3e14 100644 --- a/app/livechat/client/views/regular.js +++ b/app/livechat/client/views/regular.js @@ -5,7 +5,6 @@ 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/visitorForward'; import './app/tabbar/visitorHistory'; diff --git a/app/livechat/server/config.js b/app/livechat/server/config.js index 3b568b54a37f..ea72e65f271c 100644 --- a/app/livechat/server/config.js +++ b/app/livechat/server/config.js @@ -250,31 +250,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', diff --git a/app/livechat/server/hooks/externalMessage.js b/app/livechat/server/hooks/externalMessage.js deleted file mode 100644 index 41bb98396fa1..000000000000 --- 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/index.js b/app/livechat/server/index.js index a42161d3b007..72632b50670e 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -7,7 +7,6 @@ import './config'; import './roomType'; import './hooks/beforeCloseRoom'; import './hooks/beforeGetNextAgent'; -import './hooks/externalMessage'; import './hooks/leadCapture'; import './hooks/markRoomResponded'; import './hooks/offlineMessage'; diff --git a/server/startup/migrations/index.js b/server/startup/migrations/index.js index e6de5e3f475e..edee93ea383f 100644 --- a/server/startup/migrations/index.js +++ b/server/startup/migrations/index.js @@ -185,4 +185,5 @@ import './v185'; import './v186'; import './v187'; import './v188'; +import './v189'; import './xrun'; diff --git a/server/startup/migrations/v189.js b/server/startup/migrations/v189.js new file mode 100644 index 000000000000..1ceaa8071ba9 --- /dev/null +++ b/server/startup/migrations/v189.js @@ -0,0 +1,11 @@ +import { Migrations } from '../../../app/migrations/server'; +import { Settings } from '../../../app/models/server'; + +Migrations.add({ + version: 189, + up() { + Settings.remove({ _id: 'Livechat_Knowledge_Enabled' }); + Settings.remove({ _id: 'Livechat_Knowledge_Apiai_Key' }); + Settings.remove({ _id: 'Livechat_Knowledge_Apiai_Language' }); + }, +}); From eef240e624d88a6dfffdf03aa8588f1896806716 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Tue, 5 May 2020 15:07:03 -0300 Subject: [PATCH 017/121] [IMPROVE] Add new webhooks to the Omnichannel integration feature (#17503) --- .../livechatIntegrationWebhook.html | 24 ++++ .../livechatIntegrationWebhook.js | 28 +++++ app/livechat/server/api/lib/integrations.js | 2 +- app/livechat/server/config.js | 28 +++++ app/livechat/server/hooks/sendToCRM.js | 109 ++++++++++++++++-- app/livechat/server/lib/Helper.js | 1 + app/livechat/server/lib/Livechat.js | 12 +- app/livechat/server/lib/QueueManager.js | 1 + .../server/methods/saveIntegration.js | 16 +++ packages/rocketchat-i18n/i18n/en.i18n.json | 5 + packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 5 + 11 files changed, 220 insertions(+), 11 deletions(-) diff --git a/app/livechat/client/views/app/integrations/livechatIntegrationWebhook.html b/app/livechat/client/views/app/integrations/livechatIntegrationWebhook.html index 12959838304e..761aaac87b5f 100644 --- a/app/livechat/client/views/app/integrations/livechatIntegrationWebhook.html +++ b/app/livechat/client/views/app/integrations/livechatIntegrationWebhook.html @@ -29,12 +29,36 @@

{{_ "Webhooks"}}

+
+ +
+
+ +
+
+ +
+
+ +
{{#if data.hasScriptError }} diff --git a/app/integrations/client/views/integrationsOutgoing.html b/app/integrations/client/views/integrationsOutgoing.html index 18d6fc8d5791..d9dc03ae209e 100644 --- a/app/integrations/client/views/integrationsOutgoing.html +++ b/app/integrations/client/views/integrationsOutgoing.html @@ -18,7 +18,7 @@
{{_ "Webhook Details"}}
- +
@@ -148,8 +148,8 @@
{{> CodeMirror name="script" options=editorOptions code=data.script }}
- - + +
{{#if data.hasScriptError }} @@ -181,7 +181,7 @@
{{_ "Integration_Advanced_Settings"}}
- +
diff --git a/app/integrations/client/views/integrationsOutgoingHistory.html b/app/integrations/client/views/integrationsOutgoingHistory.html index cdd0d7f17933..b453924e1f9d 100644 --- a/app/integrations/client/views/integrationsOutgoingHistory.html +++ b/app/integrations/client/views/integrationsOutgoingHistory.html @@ -21,8 +21,8 @@ {{formatDate history._createdAt}}
- - +
diff --git a/app/livechat/client/views/app/integrations/livechatIntegrationFacebook.html b/app/livechat/client/views/app/integrations/livechatIntegrationFacebook.html index f7ff03d6b089..6686dbde984c 100644 --- a/app/livechat/client/views/app/integrations/livechatIntegrationFacebook.html +++ b/app/livechat/client/views/app/integrations/livechatIntegrationFacebook.html @@ -4,10 +4,10 @@
{{#if enabled}} - - + + {{else}} - + {{#unless hasToken}}

{{_ "You_have_to_set_an_API_token_first_in_order_to_use_the_integration"}}

diff --git a/app/livechat/client/views/app/livechatAgents.html b/app/livechat/client/views/app/livechatAgents.html index 063de6b19c25..bb57a86497e9 100644 --- a/app/livechat/client/views/app/livechatAgents.html +++ b/app/livechat/client/views/app/livechatAgents.html @@ -62,7 +62,7 @@
-
{{> avatar username=username}}
+
{{> avatar username=username}}
{{name}} diff --git a/app/livechat/client/views/app/livechatManagers.html b/app/livechat/client/views/app/livechatManagers.html index ec1468cd13b0..d35e2d33be44 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 e666828d5c33..2a3f926611e1 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/visitorInfo.html b/app/livechat/client/views/app/tabbar/visitorInfo.html index 0ba4aa7d6c9e..b802b5962612 100644 --- a/app/livechat/client/views/app/tabbar/visitorInfo.html +++ b/app/livechat/client/views/app/tabbar/visitorInfo.html @@ -64,10 +64,10 @@

{{_ "Conversation"}}

{{/if}} {{#if canForwardGuest}} - + {{/if}} {{#if canReturnQueue}} - + {{/if}} {{/if}} diff --git a/app/models/server/models/Settings.js b/app/models/server/models/Settings.js index 6cca3203f251..a03c81a04100 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/oauth2-server-config/client/admin/views/oauthApp.html b/app/oauth2-server-config/client/admin/views/oauthApp.html index 5bcab86d702b..360a4ff57c99 100644 --- a/app/oauth2-server-config/client/admin/views/oauthApp.html +++ b/app/oauth2-server-config/client/admin/views/oauthApp.html @@ -60,9 +60,9 @@
{{#if data.clientId}} - + {{/if}} - +
{{else}} diff --git a/app/oauth2-server-config/client/oauth/oauth2-client.html b/app/oauth2-server-config/client/oauth/oauth2-client.html index 49915dcf4738..917f44217918 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 @@

{{currentUser.username}}

- - - + + +
{{#unless Template.subscriptionsReady}} diff --git a/app/otr/client/rocketchat.otr.room.js b/app/otr/client/rocketchat.otr.room.js index fcb34184e376..08da47733d76 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 029972d1a1b5..2a44f16efe7c 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 e394b43cba61..6357caf196a9 100644 --- a/app/otr/client/views/otrFlexTab.html +++ b/app/otr/client/views/otrFlexTab.html @@ -9,15 +9,15 @@

{{_ "Off_the_record_conversation"}}

{{#if userIsOnline}} {{#if established}}
- +
- +
{{else}} {{#if establishing}} {{_ "Please_wait_while_OTR_is_being_established"}} {{else}} - + {{/if}} {{/if}} {{else}}

{{_ "OTR_is_only_available_when_both_users_are_online"}}

diff --git a/app/theme/client/imports/general/base.css b/app/theme/client/imports/general/base.css index 701478ff6849..e35353061bce 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 abd91f9efa6f..d2b2ace62a9b 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,65 +1065,6 @@ 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 6s infinite; } @@ -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 { @@ -1544,12 +1363,6 @@ rc-old select, line-height: 1.2rem; } - & .color-editor { - position: relative; - - width: 150px; - } - & .selected-rooms .remove-room { cursor: pointer; } @@ -1746,12 +1559,6 @@ rc-old select, margin-bottom: 5px; } } - - & .user-image { - float: right; - - margin-left: 12px; - } } } @@ -1970,14 +1777,6 @@ rc-old select, width: 50%; } - & .room-topic { - margin-left: 10px; - - opacity: 0.4; - - font-size: 14px; - } - & .wrapper { position: absolute; top: 0; @@ -1997,287 +1796,11 @@ rc-old select, flex-shrink: 0; } - & .message-form { - margin-bottom: 18px; - - & > .message-input { - position: relative; - - display: flex; - overflow: hidden; - - border-width: 1px; - border-radius: 5px; - - & > .share-items { - position: relative; - - display: flex; - - & input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - overflow: hidden; - - cursor: pointer; - - opacity: 0; - - &::-webkit-file-upload-button { - cursor: pointer; - } - } - - & i { - font-size: 18px; - } - - & > .message-buttons { - position: relative; - - display: flex; - overflow: hidden; - - width: 35px; - height: 35px; - - cursor: pointer; - text-align: center; - - border: 0; - align-items: center; - justify-content: center; - - & input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - overflow: hidden; - - cursor: pointer; - - opacity: 0; - - &::-webkit-file-upload-button { - cursor: pointer; - } - } - - & i { - font-size: 18px; - } - } - } - - & .input-message-container { - position: relative; - - width: 100%; - - & .inner-left-toolbar { - position: absolute; - top: 8px; - left: 13px; - } - } - - & > .message-buttons { - position: relative; - - display: flex; - flex: 0 0 35px; - - cursor: pointer; - transition: background-color 0.1s linear, color 0.1s linear; - text-align: center; - - border: 0; - align-items: center; - justify-content: center; - - & i { - transition: transform 0.3s ease-out; - transform: rotate(0deg); - - font-size: 18px; - } - - & input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - overflow: hidden; - - width: 100%; - - cursor: pointer; - - 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 { @@ -2373,84 +1896,30 @@ rc-old select, } .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; - - 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; + padding: 10px; +} - & i::before { - margin-right: 0; - } - } +.rc-message-box__icon.cancel-reply .rc-input__icon-svg--cross { + font-size: 1em; +} - & .room-title { - font-size: 15px; - line-height: 30px; - } +.rc-old .message-popup.popup-with-reply-preview { + border-radius: 5px 5px 0 0; +} - & .bold { - font-weight: 700; - } +.rc-old .message-popup { + position: absolute; + z-index: 101; + right: 0; + bottom: 0; + left: 0; - & .unread { - top: 8px; - } + overflow: hidden; - & .loading-animation { - position: relative; - } - } + border-radius: 5px; + box-shadow: + 0 -1px 10px 0 rgba(0, 0, 0, 0.2), + 0 1px 1px rgba(0, 0, 0, 0.16); } .rc-old .message-popup-title { @@ -2851,10 +2320,6 @@ rc-old select, } } - & .avatar-initials { - line-height: 40px; - } - & .body { transition: opacity 0.3s linear; @@ -3313,36 +2778,6 @@ rc-old select, } } } - - /* - & .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; - - & i { - display: inline-block; - - width: 18px; - height: 18px; - - font-size: 10px; - } - } - } - */ } & .flex-tab-bar { @@ -3623,12 +3058,6 @@ rc-old select, } } - & .contact-code { - margin: -5px 0 10px; - - font-size: 12px; - } - & .channels { & h3 { margin-bottom: 8px; @@ -3677,10 +3106,6 @@ rc-old select, } } } - - & .room-info-content > div { - margin: 0 0 20px; - } } .rc-old .edit-form { @@ -3733,156 +3158,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; @@ -3895,14 +3170,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; @@ -4234,15 +3501,6 @@ rc-old select, } } -.rc-old #particles-js { - position: fixed; - top: 0; - left: 0; - - width: 100%; - height: 100%; -} - .rc-old .highlight-text { padding: 2px; @@ -4347,36 +3605,6 @@ rc-old select, } } -.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 { @@ -4430,25 +3658,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; @@ -4610,24 +3819,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; @@ -4785,10 +3976,6 @@ rc-old select, } } -.sweet-alert .sa-input-error { - top: 19px; -} - .rc-old .collapse-switch { cursor: pointer; } @@ -4880,14 +4067,6 @@ rc-old select, min-height: 36px; padding: 0; - & .message-form { - margin-bottom: 0; - } - - & .message-input { - border-width: 0; - } - & .users-typing { display: none; } @@ -4922,10 +4101,6 @@ rc-old select, .rc-old.main-content { transform: translateX(0) !important; } - - .sweet-alert { - margin-left: -239px !important; - } } @media (width <= 780px) { @@ -4949,20 +4124,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; } @@ -4989,10 +4150,6 @@ rc-old select, } @media (width <= 500px) { - .rc-old .messages-container .message-form > .formatting-tips { - display: none; - } - .cms-page { padding: 0; @@ -5024,10 +4181,6 @@ rc-old select, .rc-old .oauth-login { margin-bottom: 6px; } - - .rc-old .message-form textarea { - max-height: 100px !important; - } } @media (width <= 440px) { @@ -5048,12 +4201,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 ba214e96548a..9516f91a2072 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; diff --git a/app/theme/client/imports/general/rtl.css b/app/theme/client/imports/general/rtl.css index 2be992c93dbe..dc230d84010d 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,13 +401,6 @@ margin-left: auto; } - & .user-image { - float: left; - - margin-right: 12px; - margin-left: auto; - } - & table thead th { text-align: right; } @@ -490,10 +430,6 @@ right: 0; } - & .logoutOthers { - text-align: left; - } - & .submit { text-align: left; } @@ -505,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 000000000000..657ebb4f3c41 --- /dev/null +++ b/app/theme/client/imports/general/theme_old.css @@ -0,0 +1,557 @@ +.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(--status-online); +} + +.status-bg-online { + background-color: var(--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); +} + +.account-box .status-offline .thumb::after, +.account-box .status.offline::after { + background-color: var(--transparent-lighter); +} + +i.status-away { + color: var(--status-away); +} + +.status-bg-away { + background-color: var(--status-away); +} + +.account-box .status-away .thumb::after, +.account-box .status.away::after, +.popup-user-status-away, +.status-away::after, +.status-pending::after { + border-color: var(--status-away-darken-10); + background-color: var(--status-away); +} + +i.status-busy { + color: var(--status-busy); +} + +.status-bg-busy { + background-color: var(--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); +} + +i.status-offline { + color: var(--status-offline); +} + +.status-bg-offline { + background-color: var(--status-offline); +} + +.popup-user-status-offline, +.status-offline::after { + border-color: var(--status-offline-darken-10); + background-color: var(--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 2cfb61fd812f..01325d295175 100644 --- a/app/theme/client/imports/general/variables.css +++ b/app/theme/client/imports/general/variables.css @@ -1,4 +1,66 @@ :root { + /* #region colors Colors */ + --rc-color-error: var(--color-red); + --rc-color-error-light: #e1364c; + --rc-color-alert: var(--color-yellow); + --rc-color-alert-light: var(--color-dark-yellow); + --rc-color-success: var(--color-green); + --rc-color-success-light: #25d198; + --rc-color-button-primary: var(--color-blue); + --rc-color-button-primary-light: var(--color-dark-blue); + --rc-color-alert-message-primary: var(--color-blue); + --rc-color-alert-message-primary-background: #f1f6ff; + --rc-color-alert-message-secondary: #7ca52b; + --rc-color-alert-message-secondary-background: #fafff1; + --rc-color-alert-message-warning: #d52d24; + --rc-color-alert-message-warning-background: #fff3f3; + --rc-color-primary: var(--color-dark); + --rc-color-primary-background: var(--color-dark); + --rc-color-primary-darkest: var(--color-darkest); + --rc-color-primary-dark: var(--color-dark-medium); + --rc-color-primary-light: var(--color-gray); + --rc-color-primary-light-medium: var(--color-gray-medium); + --rc-color-primary-lightest: var(--color-gray-lightest); + --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; + --success-color: #4dff4d; + --pending-color: #fcb316; + --error-color: #bc2031; + --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); + --status-online: var(--success-color); + --status-away: var(--pending-color); + --status-busy: var(--error-color); + --status-offline: 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 */ + /* * Color palette */ @@ -36,29 +98,6 @@ --color-gray-lightest: #f2f3f5; --color-black: #000000; --color-white: #ffffff; - --rc-color-error: var(--color-red); - --rc-color-error-light: #e1364c; - --rc-color-alert: var(--color-yellow); - --rc-color-alert-light: var(--color-dark-yellow); - --rc-color-success: var(--color-green); - --rc-color-success-light: #25d198; - --rc-color-button-primary: var(--color-blue); - --rc-color-button-primary-light: var(--color-dark-blue); - --rc-color-alert-message-primary: var(--color-blue); - --rc-color-alert-message-primary-background: #f1f6ff; - --rc-color-alert-message-secondary: #7ca52b; - --rc-color-alert-message-secondary-background: #fafff1; - --rc-color-alert-message-warning: #d52d24; - --rc-color-alert-message-warning-background: #fff3f3; - --rc-color-primary: var(--color-dark); - --rc-color-primary-background: var(--color-dark); - --rc-color-primary-darkest: var(--color-darkest); - --rc-color-primary-dark: var(--color-dark-medium); - --rc-color-primary-light: var(--color-gray); - --rc-color-primary-light-medium: var(--color-gray-medium); - --rc-color-primary-lightest: var(--color-gray-lightest); - --rc-color-content: var(--color-white); - --rc-color-link-active: var(--rc-color-button-primary); /* * General @@ -71,7 +110,6 @@ --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); diff --git a/app/theme/client/main.css b/app/theme/client/main.css index 56d02e89df83..fa98f893547c 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 9cde1fc67536..1b3ee5ed7435 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 cf4d7bd587f5..c95c243ffeb6 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/tokenpass/client/tokenpassChannelSettings.html b/app/tokenpass/client/tokenpassChannelSettings.html index 4f9ab15e9f37..f0e5490ab438 100644 --- a/app/tokenpass/client/tokenpassChannelSettings.html +++ b/app/tokenpass/client/tokenpassChannelSettings.html @@ -1,7 +1,7 @@ diff --git a/app/ui-account/client/avatar/prompt.html b/app/ui-account/client/avatar/prompt.html index 5a6d482dd20d..d1a8aaaf015f 100644 --- a/app/ui-account/client/avatar/prompt.html +++ b/app/ui-account/client/avatar/prompt.html @@ -4,7 +4,7 @@
- +
{{/if}} @@ -15,7 +15,7 @@
- +
{{/if}} @@ -35,7 +35,7 @@ {{> avatar username=initialsUsername }} {{#with service='initials'}}
- +
{{/with}}
@@ -43,11 +43,11 @@
-
{{_ "Select_file"}} +
{{_ "Select_file"}}
{{#with upload}} - + {{/with}}
@@ -57,7 +57,7 @@
- +
{{/with}} @@ -100,7 +100,7 @@ {{#if username.ready}}
- +
{{/if}}
diff --git a/app/ui-flextab/client/tabs/userInfo.html b/app/ui-flextab/client/tabs/userInfo.html index 69e6128cf614..69c85ecd4075 100644 --- a/app/ui-flextab/client/tabs/userInfo.html +++ b/app/ui-flextab/client/tabs/userInfo.html @@ -29,7 +29,7 @@

{{_ "User_Info"}}

- + {{#if 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 b1d6c4f3d8fb..549d1fafb753 100644 --- a/app/ui-login/client/reset-password/resetPassword.html +++ b/app/ui-login/client/reset-password/resetPassword.html @@ -23,7 +23,7 @@
- +
diff --git a/app/ui-master/.eslintrc b/app/ui-master/.eslintrc deleted file mode 100644 index 275f73194ad6..000000000000 --- 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 040c1eae7678..22cfc0b1d330 100644 --- a/app/ui-master/client/main.html +++ b/app/ui-master/client/main.html @@ -1,4 +1,4 @@ - +
diff --git a/app/ui-master/client/main.js b/app/ui-master/client/main.js index 0a7e0753ecc8..e5580d61ea27 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 62b08f405849..5f0ac8fcaed3 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/message.html b/app/ui-message/client/message.html index f6dccc66a445..d8624209a8dd 100644 --- a/app/ui-message/client/message.html +++ b/app/ui-message/client/message.html @@ -158,7 +158,7 @@
diff --git a/imports/client/mimic-fn b/imports/client/mimic-fn deleted file mode 120000 index 6e14eb82385a..000000000000 --- a/imports/client/mimic-fn +++ /dev/null @@ -1 +0,0 @@ -../../node_modules/mimic-fn \ No newline at end of file diff --git a/imports/client/mimic-fn/index.js b/imports/client/mimic-fn/index.js new file mode 120000 index 000000000000..3e62681b7465 --- /dev/null +++ b/imports/client/mimic-fn/index.js @@ -0,0 +1 @@ +../../../node_modules/mem/node_modules/mimic-fn/index.js \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7166d2061ff9..715b28d31286 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15831,28 +15831,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "resolved": false, "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, @@ -15863,14 +15863,14 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, @@ -15888,28 +15888,28 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true @@ -15926,21 +15926,21 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "resolved": false, "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true @@ -15957,14 +15957,14 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -15996,14 +15996,14 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "resolved": false, "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, @@ -16023,7 +16023,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -16041,14 +16041,14 @@ }, "ini": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "resolved": false, "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, @@ -16058,14 +16058,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, @@ -16075,7 +16075,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true @@ -16103,7 +16103,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -16158,7 +16158,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -16187,7 +16187,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -16200,21 +16200,21 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, @@ -16224,21 +16224,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -16249,7 +16249,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true @@ -16263,7 +16263,7 @@ }, "rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, @@ -16276,7 +16276,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -16285,7 +16285,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -16311,21 +16311,21 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "resolved": false, "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true @@ -16339,21 +16339,21 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, @@ -16365,7 +16365,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -16375,7 +16375,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, @@ -16385,7 +16385,7 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true @@ -16408,14 +16408,14 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "resolved": false, "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, @@ -16425,7 +16425,7 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true @@ -21459,7 +21459,7 @@ "dependencies": { "asn1.js": { "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "resolved": false, "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "requires": { "bn.js": "^4.0.0", @@ -21469,7 +21469,7 @@ }, "assert": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "resolved": false, "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", "requires": { "util": "0.10.3" @@ -21477,7 +21477,7 @@ "dependencies": { "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": false, "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "requires": { "inherits": "2.0.1" @@ -21487,22 +21487,22 @@ }, "base64-js": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "resolved": false, "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "bn.js": { "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "resolved": false, "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "brorand": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "resolved": false, "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": false, "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "requires": { "buffer-xor": "^1.0.3", @@ -21515,7 +21515,7 @@ }, "browserify-cipher": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "resolved": false, "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "requires": { "browserify-aes": "^1.0.4", @@ -21525,7 +21525,7 @@ }, "browserify-des": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "resolved": false, "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "requires": { "cipher-base": "^1.0.1", @@ -21536,7 +21536,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": false, "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "requires": { "bn.js": "^4.1.0", @@ -21545,7 +21545,7 @@ }, "browserify-sign": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "resolved": false, "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "requires": { "bn.js": "^4.1.1", @@ -21559,7 +21559,7 @@ }, "browserify-zlib": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "resolved": false, "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "requires": { "pako": "~1.0.5" @@ -21567,7 +21567,7 @@ }, "buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "resolved": false, "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", "requires": { "base64-js": "^1.0.2", @@ -21576,17 +21576,17 @@ }, "buffer-xor": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "resolved": false, "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "builtin-status-codes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "resolved": false, "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" }, "cipher-base": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "resolved": false, "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "requires": { "inherits": "^2.0.1", @@ -21595,7 +21595,7 @@ }, "console-browserify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "resolved": false, "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "requires": { "date-now": "^0.1.4" @@ -21603,17 +21603,17 @@ }, "constants-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-ecdh": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "resolved": false, "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "requires": { "bn.js": "^4.1.0", @@ -21622,7 +21622,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": false, "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "requires": { "cipher-base": "^1.0.1", @@ -21634,7 +21634,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": false, "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "requires": { "cipher-base": "^1.0.3", @@ -21647,7 +21647,7 @@ }, "crypto-browserify": { "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "resolved": false, "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "requires": { "browserify-cipher": "^1.0.0", @@ -21665,12 +21665,12 @@ }, "date-now": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "resolved": false, "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" }, "des.js": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "resolved": false, "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "requires": { "inherits": "^2.0.1", @@ -21679,7 +21679,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": false, "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "requires": { "bn.js": "^4.1.0", @@ -21689,12 +21689,12 @@ }, "domain-browser": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "resolved": false, "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" }, "elliptic": { "version": "6.4.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "resolved": false, "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", "requires": { "bn.js": "^4.4.0", @@ -21708,12 +21708,12 @@ }, "events": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "resolved": false, "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" }, "evp_bytestokey": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "resolved": false, "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "requires": { "md5.js": "^1.3.4", @@ -21722,7 +21722,7 @@ }, "hash-base": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "resolved": false, "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "requires": { "inherits": "^2.0.1", @@ -21731,7 +21731,7 @@ }, "hash.js": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "resolved": false, "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "requires": { "inherits": "^2.0.3", @@ -21740,14 +21740,14 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "hmac-drbg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "resolved": false, "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "requires": { "hash.js": "^1.0.3", @@ -21757,27 +21757,27 @@ }, "https-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "ieee754": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "resolved": false, "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "inherits": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "resolved": false, "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "md5.js": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "resolved": false, "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "requires": { "hash-base": "^3.0.0", @@ -21787,7 +21787,7 @@ }, "miller-rabin": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "resolved": false, "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "requires": { "bn.js": "^4.0.0", @@ -21796,27 +21796,27 @@ }, "minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "resolved": false, "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "resolved": false, "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "os-browserify": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "resolved": false, "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, "pako": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "resolved": false, "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" }, "parse-asn1": { "version": "5.1.4", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "resolved": false, "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", "requires": { "asn1.js": "^4.0.0", @@ -21829,12 +21829,12 @@ }, "path-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha512-Hkavx/nY4/plImrZPHRk2CL9vpOymZLgEbMNX1U0bjcBL7QN9wODxyx0yaMZURSQaUtSEvDrfAvxa9oPb0at9g==" }, "pbkdf2": { "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "resolved": false, "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "requires": { "create-hash": "^1.1.2", @@ -21846,17 +21846,17 @@ }, "process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "resolved": false, "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "public-encrypt": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "resolved": false, "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "requires": { "bn.js": "^4.1.0", @@ -21869,22 +21869,22 @@ }, "punycode": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "resolved": false, "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "querystring": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "resolved": false, "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "resolved": false, "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" }, "randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "resolved": false, "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "requires": { "safe-buffer": "^5.1.0" @@ -21892,7 +21892,7 @@ }, "randomfill": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "resolved": false, "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "requires": { "randombytes": "^2.0.5", @@ -21901,7 +21901,7 @@ }, "readable-stream": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "resolved": false, "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", "requires": { "inherits": "^2.0.3", @@ -21911,14 +21911,14 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "ripemd160": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "resolved": false, "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "requires": { "hash-base": "^3.0.0", @@ -21927,17 +21927,17 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "setimmediate": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "resolved": false, "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": false, "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "requires": { "inherits": "^2.0.1", @@ -21946,7 +21946,7 @@ }, "stream-browserify": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "resolved": false, "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "requires": { "inherits": "~2.0.1", @@ -21955,7 +21955,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -21969,14 +21969,14 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -21986,7 +21986,7 @@ }, "stream-http": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.0.0.tgz", + "resolved": false, "integrity": "sha512-JELJfd+btL9GHtxU3+XXhg9NLYrKFnhybfvRuDghtyVkOFydz3PKNT1df07AMr88qW03WHF+FSV0PySpXignCA==", "requires": { "builtin-status-codes": "^3.0.0", @@ -21997,7 +21997,7 @@ }, "string_decoder": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "resolved": false, "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", "requires": { "safe-buffer": "~5.1.0" @@ -22005,7 +22005,7 @@ }, "timers-browserify": { "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "resolved": false, "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", "requires": { "setimmediate": "^1.0.4" @@ -22013,12 +22013,12 @@ }, "tty-browserify": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "resolved": false, "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" }, "url": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "resolved": false, "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "requires": { "punycode": "1.3.2", @@ -22027,14 +22027,14 @@ "dependencies": { "punycode": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "resolved": false, "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" } } }, "util": { "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "resolved": false, "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "requires": { "inherits": "2.0.3" @@ -22042,24 +22042,24 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "vm-browserify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "resolved": false, "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" }, "xtend": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "resolved": false, "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } diff --git a/private/client/imports/general/variables.css b/private/client/imports/general/variables.css deleted file mode 100644 index 92c5124c4d22..000000000000 --- a/private/client/imports/general/variables.css +++ /dev/null @@ -1,380 +0,0 @@ -:root { - /* - * Color palette - */ - --color-dark-100: #0c0d0f; - --color-dark-90: #1e232a; - --color-dark-80: #2e343e; - --color-dark-70: #53585f; - --color-dark-30: #9da2a9; - --color-dark-20: #caced1; - --color-dark-10: #e0e5e8; - --color-dark-05: #f1f2f4; - --color-dark-blue: #175cc4; - --color-blue: #1d74f5; - --color-light-blue: #4eb2f5; - --color-lighter-blue: #e8f2ff; - --color-purple: #861da8; - --color-red: #f5455c; - --color-dark-red: #e0364d; - --color-orange: #f59547; - --color-yellow: #ffd21f; - --color-dark-yellow: #f6c502; - --color-green: #2de0a5; - --color-dark-green: #26d198; - - /* - * General Colors - */ - --color-darkest: #1f2329; - --color-dark: #2f343d; - --color-dark-medium: #414852; - --color-dark-light: #6c727a; - --color-gray: #9ea2a8; - --color-gray-medium: #cbced1; - --color-gray-light: #e1e5e8; - --color-gray-lightest: #f2f3f5; - --color-black: #000000; - --color-white: #ffffff; - --rc-color-error: var(--color-red); - --rc-color-error-light: #e1364c; - --rc-color-alert: var(--color-yellow); - --rc-color-alert-light: var(--color-dark-yellow); - --rc-color-success: var(--color-green); - --rc-color-success-light: #25d198; - --rc-color-button-primary: var(--color-blue); - --rc-color-button-primary-light: var(--color-dark-blue); - --rc-color-alert-message-primary: var(--color-blue); - --rc-color-alert-message-primary-background: #f1f6ff; - --rc-color-alert-message-secondary: #7ca52b; - --rc-color-alert-message-secondary-background: #fafff1; - --rc-color-alert-message-warning: #d52d24; - --rc-color-alert-message-warning-background: #fff3f3; - --rc-color-primary: var(--color-dark); - --rc-color-primary-background: var(--color-dark); - --rc-color-primary-darkest: var(--color-darkest); - --rc-color-primary-dark: var(--color-dark-medium); - --rc-color-primary-light: var(--color-gray); - --rc-color-primary-light-medium: var(--color-gray-medium); - --rc-color-primary-lightest: var(--color-gray-lightest); - --rc-color-content: var(--color-white); - --rc-color-link-active: var(--rc-color-button-primary); - - /* - * General - */ - --text-size: 0.875rem; - --header-min-height: 60px; - --toolbar-height: 55px; - --footer-min-height: 70px; - --rooms-box-width: 280px; - --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); - --default-padding: 1.5rem; - --default-small-padding: 1rem; - --status-bullet-size: 10px; - --status-bullet-radius: 50%; - --account-username-weight: 700; - --status-name-weight: 400; - --default-font-weight-header: 500; - - /* - * General Typography - */ - --text-default-size: 1rem; - --text-default-weight: 500; - --text-small-size: 0.875rem; - --text-small-weight: 500; - --text-heading-size: 1.375rem; - --text-heading-weight: 700; - --text-label-size: 075rem; - --text-label-weight: 600; - --text-tiny-size: 075rem; - --text-tiny-weight: 400; - --text-micro-size: 0.625rem; - --text-micro-weight: 700; - - /* - * Forms - */ - --gap-between-elements: 2.5rem; - --label-margin-bottom: 1rem; - - /* - * Forms - Button - */ - --button-square-size: 36px; - --button-padding: 0.782rem; - --button-padding-small: 0 0.5rem; - --button-height-small: 28px; - --button-text-size-small: 13px; - --button-text-size: var(--input-font-size); - --button-border-width: var(--border); - --button-border-radius: var(--border-radius); - --button-disabled-background: var(--color-gray-light); - --button-disabled-text-color: var(--color-white); - --button-primary-background: var(--rc-color-button-primary); - --button-primary-text-color: var(--color-white); - --button-cancel-color: var(--rc-color-error); - --button-secondary-background: var(--color-gray-medium); - --button-secondary-text-color: var(--color-dark-medium); - - /* - * Forms - Input - */ - --input-font-size: 0.875rem; - --input-title-text-size: var(--input-font-size); - --input-title-color: #2d343d; - --input-text-color: var(--color-dark-medium); - --input-placeholder-color: var(--color-gray-medium); - --input-icon-color: var(--color-dark); - --input-border-color: var(--color-gray-light); - --input-border-width: var(--border); - --input-border-radius: var(--border-radius); - --input-description-text-color: var(--color-gray); - --input-description-text-size: var(--input-font-size); - --input-error-color: var(--rc-color-error); - - /* - * Forms - popup list - */ - --popup-list-border-radius: var(--border-radius); - --popup-list-background: var(--color-white); - --popup-list-background-hover: var(--color-gray-lightest); - --popup-list-selected-background: var(--color-gray-lightest); - --popup-list-name-color: #2d343d; - --popup-list-name-size: 1rem; - - /* - * Forms - tags - */ - --tags-border-width: var(--border); - --tags-border-radius: var(--border-radius); - --tags-border-color: var(--color-gray-light); - --tags-text-color: var(--rc-color-primary); - --tags-background: #f2f3f5; - --tags-avatar-size: 20px; - - /* - * Forms - select avatar - */ - --select-avatar-size: 48px; - --select-avatar-preview-size: 150px; - --select-avatar-upload-background: var(--color-gray-light); - --select-avatar-upload-color: #2d343d; - - /* - * Sidebar - */ - --sidebar-width: 280px; - --sidebar-small-width: 90%; - --sidebar-background: var(--rc-color-primary); - --sidebar-background-hover: var(--rc-color-primary-dark); - --sidebar-background-light: var(--rc-color-primary-lightest); - --sidebar-background-light-hover: var(--rc-color-primary-light); - --sidebar-background-light-active: var(--rc-color-primary-light-medium); - --sidebar-default-padding: 24px; - --sidebar-small-default-padding: 16px; - --sidebar-extra-small-default-padding: 12px; - --sidebar-footer-height: 48px; - --sidebar-small-header-padding: var(--sidebar-small-default-padding); - - /* - * Sidebar flex - */ - --sidebar-flex-search-background: var(--color-white); - --sidebar-flex-search-placeholder-color: var(--color-gray); - - /* - * Sidebar Account - */ - --sidebar-account-thumb-size: 23px; - --sidebar-small-account-thumb-size: 40px; - --sidebar-account-status-bullet-size: 10px; - --sidebar-small-account-status-bullet-size: 8px; - --sidebar-account-status-bullet-radius: 50%; - --sidebar-account-username-size: 1rem; - --sidebar-account-username-weight: 700; - --sidebar-small-account-username-weight: 400; - --sidebar-account-username-color: var(--color-white); - --sidebar-account-username-color-darker: var(--color-dark); - --sidebar-account-status-font-size: 0.875rem; - --sidebar-account-status-color: var(--color-gray); - - /* - * Sidebar Item - */ - --sidebar-item-radius: 2px; - --sidebar-item-height: 24px; - --sidebar-item-height-medium: 34px; - --sidebar-item-height-extended: 52px; - --sidebar-item-thumb-size: 18px; - --sidebar-item-thumb-size-medium: 27px; - --sidebar-item-thumb-size-extended: 36px; - --sidebar-item-text-color: var(--rc-color-primary-light); - --sidebar-item-background: inherit; - --sidebar-item-hover-background: var(--rc-color-primary-darkest); - --sidebar-item-active-background: var(--rc-color-primary-dark); - --sidebar-item-active-color: var(--sidebar-item-text-color); - --sidebar-item-unread-color: var(--rc-color-content); - --sidebar-item-unread-font-weight: 600; - --sidebar-item-popup-background: var(--rc-color-primary-dark); - --sidebar-item-user-status-size: 6px; - --sidebar-item-user-status-radius: 50%; - --sidebar-item-text-size: 0.875rem; - - /* - * Modal - */ - --modal-wrapper-width: 650px; - --modal-wrapper-margin: 3rem; - --modal-back-button-color: var(--color-gray); - - /* - * Modal - Create Channel - */ - --create-channel-gap-between-elements: 1rem; - --create-channel-title-color: var(--color-darkest); - --create-channel-title-text-size: 1.375rem; - --create-channel-description-color: var(--color-gray); - --create-channel-description-text-size: 0.875rem; - - /* - * Toolbar - */ - --toolbar-placeholder-color: var(--color-gray); - - /* - * Rooms list - */ - --rooms-list-title-color: var(--rc-color-primary-light); - --rooms-list-title-text-size: 0.75rem; - --rooms-list-empty-text-color: var(--color-gray); - --rooms-list-empty-text-size: 0.75rem; - --rooms-list-padding: var(--sidebar-default-padding); - --rooms-list-small-padding: var(--sidebar-small-default-padding); - - /* - * Chip - */ - --chip-background: #dddddd; - - /* - * Avatar - */ - --avatar-radius: var(--border-radius); - --avatar-initials-text-size: 22px; - --avatar-initials-text-weight: 700; - - /* - * Badge - */ - --badge-text-color: var(--color-white); - --badge-radius: 12px; - --badge-text-size: 0.75rem; - --badge-background: var(--rc-color-primary-dark); - --badge-unread-background: var(--rc-color-primary-dark); - --badge-user-mentions-background: var(--color-dark-blue); - --badge-group-mentions-background: var(--rc-color-primary-dark); - - /* - * Mention link - */ - --mention-link-radius: 10px; - --mention-link-background: var(--color-lighter-blue); - --mention-link-text-color: var(--color-dark-blue); - --mention-link-me-background: var(--color-dark-blue); - --mention-link-me-text-color: var(--color-white); - --mention-link-group-background: var(--rc-color-primary-dark); - --mention-link-group-text-color: var(--color-white); - - /* - * Message box - */ - --message-box-text-size: var(--input-font-size); - --message-box-placeholder-color: var(--color-gray-medium); - --message-box-markdown-color: var(--color-gray); - --message-box-markdown-hover-color: var(--color-dark); - --message-box-user-typing-color: var(--color-gray); - --message-box-user-typing-text-size: 0.75rem; - --message-box-user-typing-user-color: var(--color-dark); - --message-box-container-border-color: var(--color-gray-medium); - --message-box-container-border-width: var(--border); - --message-box-container-border-radius: var(--border-radius); - --message-box-editing-color: #fff5df; - --message-box-popover-title-text-color: var(--color-gray); - --message-box-popover-title-text-size: 0.75rem; - - /* - * Header - */ - --header-height: 77px; - --header-padding: 16px; - --header-toggle-favorite-color: var(--color-gray-medium); - --header-toggle-favorite-star-color: var(--rc-color-alert-light); - --header-toggle-encryption-off-color: var(--color-gray-medium); - --header-toggle-encryption-on-color: var(--rc-color-alert-message-secondary); - --header-title-username-color-darker: var(--color-dark); - --header-title-font-size: var(--text-default-size); - --header-title-font-size--subtitle: var(--text-small-size); - --header-title-status-color: var(--color-gray); - --header-title-username-weight: 400; - --header-title-status-name-weight: 400; - --header-title-status-bullet-radius: var(--status-bullet-radius); - --header-title-status-bullet-size: var(--status-bullet-size); - --header-background-color: var(--color-white); - - /* - * Flex nav - */ - --flex-nav-background: var(--color-gray-lightest); - - /* - * Popover - */ - --popover-padding: 1rem; - --popover-radius: var(--border-radius); - --popover-background: var(--color-white); - --popover-column-min-width: 130px; - --popover-column-padding: 1rem; - --popover-title-color: var(--color-dark); - --popover-title-text-size: 0.75rem; - --popover-item-color: var(--color-dark); - --popover-item-text-size: 0.875rem; - --popover-divider-height: 2px; - --popover-divider-color: var(--color-gray-light); - - /* - * Tooltip - */ - --tooltip-background: var(--color-darkest); - --tooltip-text-color: var(--color-white); - --tooltip-text-size: 0.75rem; - --tooltip-radius: var(--border-radius); - - /* - * alert - */ - --alerts-padding: var(--sidebar-default-padding); - --alerts-padding-vertical: 10px; - --alerts-padding-vertical-large: 20px; - --alerts-background: #1d73f5; - --alerts-color: var(--color-white); - --alerts-font-size: var(--text-default-size); - --content-page-padding: 2.5rem; - - /* - * badge - */ - --badge-size: 14px; - --badge-font-size: 0.625rem; -} diff --git a/private/client/imports/general/variables.css b/private/client/imports/general/variables.css new file mode 120000 index 000000000000..5593377224c7 --- /dev/null +++ b/private/client/imports/general/variables.css @@ -0,0 +1 @@ +../../../../app/theme/client/imports/general/variables.css \ No newline at end of file diff --git a/private/server/colors.less b/private/server/colors.less deleted file mode 100755 index 638946d86c82..000000000000 --- a/private/server/colors.less +++ /dev/null @@ -1,893 +0,0 @@ -/** ---------------------------------------------------------------------------- - * Derivative colours (fixed variants of inherited variables) - */ -@default-action-color: darken(@secondary-background-color, 15%); -@default-action-contrast: contrast(@default-action-color, #444444); -@primary-background-contrast: contrast(@primary-background-color, #444444); -@primary-action-contrast: contrast(@primary-action-color, #444444); -@secondary-background-contrast: contrast(@secondary-background-color, #444444); -@secondary-action-contrast: contrast(@secondary-action-color, #444444); -@selection-background: lighten(@selection-color, 30%); -@success-background: lighten(@success-color, 45%); -@success-border: lighten(@success-color, 30%); -@error-background: lighten(@error-color, 45%); -@error-border: lighten(@error-color, 30%); -@error-contrast: contrast(@error-color); -@pending-background: lighten(@pending-color, 45%); -@pending-border: lighten(@pending-color, 30%); - -/** ---------------------------------------------------------------------------- - * Transparency variables - */ - -@transparent-darkest: rgba(17, 12, 12, 0.5); -@transparent-darker: rgba(0, 0, 0, 0.15); -@transparent-dark: rgba(15, 34, 0, 0.05); -@transparent-light: rgba(255, 255, 255, 0.1); -@transparent-lighter: rgba(255, 255, 255, 0.3); -@transparent-lightest: rgba(255, 255, 255, 0.6); - -:root { - --legacy-default-action-color: @default-action-color; - --legacy-default-action-contrast: @default-action-contrast; - --legacy-primary-background-contrast: @primary-background-contrast; - --legacy-primary-action-contrast: @primary-action-contrast; - --legacy-secondary-background-contrast: @secondary-background-contrast; - --legacy-secondary-action-contrast: @secondary-action-contrast; - --legacy-selection-background: @selection-background; - --legacy-success-background: @success-background; - --legacy-success-border: @success-border; - --legacy-error-background: @error-background; - --legacy-error-border: @error-border; - --legacy-error-contrast: @error-contrast; - --legacy-pending-background: @pending-background; - --legacy-pending-border: @pending-border; - --legacy-transparent-darkest: @transparent-darkest; - --legacy-transparent-darker: @transparent-darker; - --legacy-transparent-dark: @transparent-dark; - --legacy-transparent-light: @transparent-light; - --legacy-transparent-lighter: @transparent-lighter; - --legacy-transparent-lightest: @transparent-lightest; -} - -/** ---------------------------------------------------------------------------- - * Mixins - */ - -.buttonColors(@color, @bg) { - color: @color; - background-color: @bg; - - &:hover { - color: mix(@color, contrast(@bg), 60%); - background-color: mix(@bg, contrast(@color), 60%); - } -} - -/** ---------------------------------------------------------------------------- - * Classes for variables - */ - -// Major colors - -.content-background-color { - background-color: @content-background-color; -} - -.color-content-background-color { - color: @content-background-color; -} - -.primary-background-color { - background-color: @primary-background-color; -} - -.global-font-family { - font-family: @body-font-family; -} - -.color-primary-font-color { - color: @primary-font-color; -} - -.color-primary-action-color { - color: @primary-action-color; -} - -.background-primary-action-color { - background-color: @primary-action-color; -} - -.secondary-background-color { - background-color: @secondary-background-color; -} - -.border-secondary-background-color { - border-color: @secondary-background-color; -} - -.secondary-font-color { - color: @secondary-font-color; -} - -.border-component-color { - border-color: @component-color; -} - -.background-component-color { - background-color: @component-color; -} - -.color-component-color { - color: @component-color; -} - -.success-color { - color: @success-color; -} - -.pending-color { - color: @pending-color; -} - -.pending-background { - background-color: @pending-background; -} - -.pending-border { - border-color: @pending-border; -} - -.error-color { - color: @error-color; -} - -.background-error-color { - background-color: @error-color; -} - -.color-info-font-color { - color: @info-font-color; -} - -.background-info-font-color { - background-color: @info-font-color; -} - -.background-attention-color { - background-color: @attention-color; -} - -// Minor Colors - -.tertiary-background-color { - background-color: @tertiary-background-color; -} - -.border-tertiary-background-color { - border-color: @tertiary-background-color; -} - -// Derivative Colors - -.color-tertiary-font-color { - color: @tertiary-font-color; -} - -.error-background { - background-color: @error-background; -} - -.error-border { - border-color: @error-border; -} - -.color-error-contrast { - color: @error-contrast; -} - -// transparent - -.background-transparent-darkest { - background-color: @transparent-darkest; -} - -.background-transparent-darker { - background-color: @transparent-darker; -} - -.background-transparent-darker-hover:hover { - background-color: @transparent-darker; -} - -.background-transparent-darker-before::before { - background-color: @transparent-darker; -} - -.background-transparent-dark { - background-color: @transparent-dark; -} - -.background-transparent-dark-hover:hover { - background-color: @transparent-dark; -} - -.border-transparent-dark { - border-color: @transparent-dark; -} - -.background-transparent-light { - background-color: @transparent-light; -} - -.border-transparent-lighter { - border-color: @transparent-lighter; -} - -.background-transparent-lightest { - background-color: @transparent-lightest; -} - -// Derivative Colors -.color-primary-action-contrast { - color: @primary-action-contrast; -} - -/** ---------------------------------------------------------------------------- - * Special components - */ - -* { - -webkit-overflow-scrolling: touch; - - &::-webkit-scrollbar { - height: 8px; - width: 8px; - background: @transparent-dark; - } - - &::-webkit-scrollbar-thumb { - background-color: @custom-scrollbar-color; - -webkit-border-radius: 50px; - } - - &::-webkit-scrollbar-corner { - background-color: @transparent-dark; - } -} - -.filter-item { - &:hover { - border-color: @info-font-color; - } - - &.active { - border-color: @primary-background-color; - } -} - -/** ---------------------------------------------------------------------------- - * Document components - */ - -.burger i { - background-color: @primary-font-color; -} - -/** ---------------------------------------------------------------------------- - * Forms - */ - -input, -select, -textarea { - color: @primary-font-color; - background-color: transparent; - border-color: mix(contrast(@content-background-color), @content-background-color, 10%); - border-style: solid; - - &::placeholder { - color: mix(@primary-font-color, @content-background-color, 75%); - } - - &[disabled] { - background-color: mix(contrast(@content-background-color), @content-background-color, 10%); - } -} - -.disabled label, -[disabled] label { - color: mix(@primary-font-color, @content-background-color, 75%); -} - -.-autocomplete-container { - background-color: mix(contrast(@content-background-color), @content-background-color, 10%); -} - -.-autocomplete-item.selected { - background-color: mix(contrast(@content-background-color), @content-background-color, 20%); -} - -.rc-old input[type="button"], -.rc-old input[type="submit"] { - color: @primary-font-color; - background: mix(contrast(@content-background-color), @content-background-color, 10%); - border-color: mix(contrast(@content-background-color), @content-background-color, 10%); -} - -.toolbar-search__input { - &:focus { - border-color: fade(@primary-background-contrast, 50%); - } - - &::placeholder { - color: @transparent-lighter; - } -} - -.toolbar-search__buttons i:hover { - color: fade(@primary-background-contrast, 50%); -} - -// .flex-nav { -// input, -// select, -// textarea { -// color: @primary-background-contrast; -// background-color: transparent; -// border-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); -// border-style: solid; - -// &::placeholder { -// color: mix(@primary-background-contrast, @transparent-lighter, 75%); -// } - -// &[disabled] { -// background-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); -// } -// } - -// .disabled label, -// [disabled] label { -// color: mix(@primary-background-contrast, @transparent-lighter, 75%); -// } - -// .-autocomplete-container { -// background-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); -// } - -// .-autocomplete-item.selected { -// background-color: mix(contrast(@transparent-lighter), @transparent-lighter, 20%); -// } - -// input[type="button"], -// input[type="submit"] { -// color: @primary-background-contrast; -// background: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); -// border-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); -// } - -// input { -// &:focus { -// border-color: fade(@primary-background-contrast, 50%); -// } -// } - -// .input.checkbox.toggle { -// input:checked + label::before { -// background-color: @primary-action-color; -// } -// } -// } - -.input-line { - &.setting-changed > label { - color: @selection-color; - } -} - -input:-webkit-autofill { - color: @primary-font-color !important; - background-color: transparent !important; -} - -.input { - &.radio { - label { - &::before { - border-color: lighten(@secondary-background-contrast, 30%); - background-color: @content-background-color; - } - - &::after { - background-color: @secondary-background-contrast; - } - } - } - - &.checkbox.toggle { - input:checked + label::before { - background-color: @secondary-background-contrast; - } - - input:disabled + label::before { - background-color: lighten(@secondary-background-contrast, 50%) !important; - } - - label { - &::before { - background-color: lighten(@secondary-background-contrast, 30%); - } - - &::after { - background-color: @content-background-color; - } - - &:hover { - &::before { - background-color: lighten(@secondary-background-contrast, 20%); - } - } - } - } -} - -/** ---------------------------------------------------------------------------- - * Misc typography variants - */ - -// a:active, -// a:hover { -// color: @primary-action-color; -// } - -.message, -.flex-tab { - a i, - a[class^="icon-"] { - color: @primary-font-color; - - &:hover { - color: darken(@primary-font-color, 10%); - } - } -} - -.error { - border-color: @error-color; -} - -/** ---------------------------------------------------------------------------- - * Admin and settings styles - */ - -.page-list, -.page-settings { - a:not(.rc-button) { - color: @primary-font-color; - - &:hover { - color: @primary-action-color; - } - } -} - -.admin-table-row { - background-color: @transparent-light; - - &:nth-of-type(even) { - background-color: @transparent-lightest; - } -} - -.avatar-suggestion-item { - .question-mark::before { - color: @secondary-font-color; - } -} - -/** ---------------------------------------------------------------------------- - * Asides (external to main application views) - */ - -.full-page, -.page-loading { - a { - color: @tertiary-font-color; - } - - a:hover { - color: @primary-background-contrast; - } -} - -#login-card { - .input-text { - input:-webkit-autofill { - -webkit-box-shadow: 0 0 0 20px @content-background-color inset; - } - } -} - -/** ---------------------------------------------------------------------------- - * Room components - */ - -.toggle-favorite { - color: @component-color; -} - -.upload-progress-progress { - background-color: @success-background; -} - -.messages-container { - .footer { - background: @content-background-color; - } -} - -.message-form { - .message-buttons { - .buttonColors(lighten(@primary-font-color, 25%), @secondary-background-color); - - &:hover { - background-color: mix(@secondary-background-color, contrast(@primary-font-color), 20%); - } - } - - .message-form-text { - &.editing { - background-color: lighten(@pending-color, 40%); - } - } -} - -.message.editing { - background-color: lighten(@pending-color, 40%); -} - -.rc-old { - & .popup-item { - &.selected { - color: @primary-action-contrast; - background-color: @primary-action-color; - } - } -} - -.messages-box { - &.selectable .selected { - background-color: @selection-background; - } -} - -/** ---------------------------------------------------------------------------- - * Message content - */ - -.message { - &.new-day::before { - background-color: @content-background-color; - } - - &.new-day::after { - border-color: @component-color; - } - - .options-menu { - color: lighten(@primary-font-color, 13%); - - ul li:hover { - background-color: @tertiary-background-color; - } - } - - a { - color: @link-font-color; - - &:hover { - color: darken(@link-font-color, 10%); - } - } - - .highlight-text { - background-color: @selection-background; - } -} - -/** ---------------------------------------------------------------------------- - * Sidebar - */ -.sidebar-item__last-message { - a:not(.mention-link) { - color: @link-font-color; - - &:hover { - color: darken(@link-font-color, 10%); - } - } -} - -.message-popup.search-results-list { - background-color: lighten(@primary-background-color, 2.5%); - - .popup-item.selected { - background-color: @transparent-darker; - } -} - -/** ---------------------------------------------------------------------------- - * Flex tabs / admin fly-out panels - */ -.flex-tab { - .channel-settings { - .buttons { - .button { - .buttonColors(lighten(@primary-font-color, 25%), @secondary-background-color); - } - } - - .input.checkbox.toggle { - input:checked + label::before { - background-color: @primary-background-color; - } - } - } -} - -.flex-tab-bar { - .tab-button { - &:hover { - background-color: @secondary-background-color; - } - - &.active { - background-color: @secondary-background-color; - border-right-color: @selection-color; - } - - &.attention { - animation-duration: 1000ms; - animation-name: blink; - animation-iteration-count: infinite; - animation-direction: alternate; - } - } - - .counter { - background: @secondary-font-color; - color: white; - } -} - -/** ---------------------------------------------------------------------------- - * User status / user meta - */ -i.status-online { - color: @status-online; -} - -.status-bg-online { - background-color: @status-online; -} - -.account-box .status-online .thumb::after, -.account-box .status.online::after, -.popup-user-status-online, -.status-online::after, -.user-image.status-online .avatar::after { - background-color: @status-online; - border-color: darken(@status-online, 10%); -} - -.account-box .status-offline .thumb::after, -.account-box .status.offline::after { - background-color: @transparent-lighter; -} - -i.status-away { - color: @status-away; -} - -.status-bg-away { - background-color: @status-away; -} - -.account-box .status-away .thumb::after, -.account-box .status.away::after, -.popup-user-status-away, -.status-away::after, -.status-pending::after, -.user-image.status-away .avatar::after { - background-color: @status-away; - border-color: darken(@status-away, 10%); -} - -i.status-busy { - color: @status-busy; -} - -.status-bg-busy { - background-color: @status-busy; -} - -.account-box .status-busy .thumb::after, -.account-box .status.busy::after, -.popup-user-status-busy, -.status-busy::after, -.user-image.status-busy .avatar::after { - background-color: @status-busy; - border-color: darken(@status-busy, 10%); -} - -i.status-offline { - color: @status-offline; -} - -.status-bg-offline { - background-color: @status-offline; -} - -.popup-user-status-offline, -.status-offline::after, -.user-image.status-offline .avatar::after { - background-color: @status-offline; - border-color: darken(@status-offline, 10%); -} - -// .popup-user-status-system { -// border-color: transparent; -// } - -// .user-view { -// .box::after, -// .stats li, -// .tags li { -// background-color: @component-color; -// } -// } - -/** ---------------------------------------------------------------------------- - * Buttons! - */ -.actionLinks li .action-link { - .buttonColors(@primary-action-contrast, @primary-action-color); -} - -// new layout buttons - -.button { - .buttonColors(@default-action-contrast, @default-action-color); - - &.primary { - .buttonColors(@primary-action-contrast, @primary-action-color); - - &[disabled] { - background-color: lighten(desaturate(@primary-action-color, 50%), 30%); - } - } - - &.secondary { - .buttonColors(@secondary-action-contrast, @secondary-action-color); - - &[disabled] { - background-color: lighten(desaturate(@secondary-action-color, 50%), 30%); - } - } - - &.tertiary { - .buttonColors(@primary-action-contrast, @selection-color); - - &[disabled] { - background-color: lighten(desaturate(@selection-color, 50%), 30%); - } - } - - &.danger { - .buttonColors(@error-contrast, @error-color); - - &[disabled] { - background-color: lighten(desaturate(@error-color, 50%), 30%); - } - } -} - -/** ---------------------------------------------------------------------------- - * Feedback and overlay content - */ - -.alert-warning { - color: darken(@pending-color, 25%); - background-color: @pending-background; -} - -.alert-link { - color: @link-font-color; - - &:hover { - color: darken(@link-font-color, 10%); - } -} - -label.required::after { - color: @error-color; -} - -/** ---------------------------------------------------------------------------- - * Loading - */ - -.main-content, -.flex-tab { - .loading-animation > .bounce { - background-color: @primary-font-color; - } -} - -.loading-animation.loading-animation--primary > .bounce { - background-color: @primary-font-color; -} - -@keyframes blink { - from { - color: @selection-color; - } - - to { - opacity: inherit; - } -} - -/** ---------------------------------------------------------------------------- - * Input Range Slider - */ - -.range-slider-range::-webkit-slider-thumb { - background-color: @primary-background-color; -} - -.range-slider-range::-webkit-slider-thumb:hover { - background-color: darken(@success-color, 30%); -} - -.range-slider-range:active::-webkit-slider-thumb { - background-color: darken(@success-color, 10%); -} - -.range-slider-range::-moz-range-thumb { - background-color: @primary-background-color; -} - -.range-slider-range::-moz-range-thumb:hover { - background-color: darken(@success-color, 30%); -} - -.range-slider-range:active::-moz-range-thumb { - background-color: darken(@success-color, 10%); -} - -.range-slider-value { - color: lighten(@tertiary-background-color, 50%); - background-color: @primary-background-color; -} - -.range-slider-value::after { - border-top-color: transparent; - border-right-color: @primary-background-color; - border-bottom-color: transparent; -} - -.range-slider-range::-moz-range-track { - background-color: @tertiary-background-color; -} - -.announcement { - background-color: @primary-background-color; - &.warning { - background-color: var(--rc-color-alert); - } - &.error { - background-color: var(--rc-color-alert-message-warning); - } -} diff --git a/private/server/dynamic-css.js b/private/server/dynamic-css.js deleted file mode 100644 index 2a17c4c073a3..000000000000 --- a/private/server/dynamic-css.js +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable */ - -'use strict'; -(function() { - var debounce = function debounce(func, wait, immediate) { - var timeout = void 0; - return function () { - var _this = this; - - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - var later = function later() { - timeout = null; - !immediate && func.apply(_this, args); - }; - - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - callNow && func.apply(this, args); - }; - }; - - var cssVarPoly = { - test: function test() { - return window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)'); - }, - init: function init() { - if (this.test()) { - return; - } - - console.time('cssVarPoly'); - cssVarPoly.ratifiedVars = {}; - cssVarPoly.varsByBlock = []; - cssVarPoly.oldCSS = []; - cssVarPoly.findCSS(); - cssVarPoly.updateCSS(); - console.timeEnd('cssVarPoly'); - }, - findCSS: function findCSS() { - var styleBlocks = Array.prototype.concat.apply([], document.querySelectorAll('#css-variables, link[type="text/css"].__meteor-css__')); - var counter = 1; - styleBlocks.map(function (block) { - if (block.nodeName === 'STYLE') { - var theCSS = block.innerHTML; - cssVarPoly.findSetters(theCSS, counter); - cssVarPoly.oldCSS[counter++] = theCSS; - } else if (block.nodeName === 'LINK') { - var url = block.getAttribute('href'); - cssVarPoly.oldCSS[counter] = ''; - cssVarPoly.getLink(url, counter, function (counter, request) { - cssVarPoly.findSetters(request.responseText, counter); - cssVarPoly.oldCSS[counter++] = request.responseText; - cssVarPoly.updateCSS(); - }); - } - }); - }, - findSetters: function findSetters(theCSS, counter) { - cssVarPoly.varsByBlock[counter] = theCSS.match(/(--[^:; ]+:..*?;)/g); - }, - - - updateCSS: debounce(function () { - cssVarPoly.ratifySetters(cssVarPoly.varsByBlock); - cssVarPoly.oldCSS.filter(function (e) { - return e; - }).forEach(function (css, id) { - var newCSS = cssVarPoly.replaceGetters(css, cssVarPoly.ratifiedVars); - var el = document.querySelector('#inserted' + id); - - if (el) { - el.innerHTML = newCSS; - } else { - var style = document.createElement('style'); - style.type = 'text/css'; - style.innerHTML = newCSS; - style.classList.add('inserted'); - style.id = 'inserted' + id; - document.getElementsByTagName('head')[0].appendChild(style); - } - }); - }, 100), - - replaceGetters: function replaceGetters(oldCSS, varList) { - return oldCSS.replace(/var\((--.*?)\)/gm, function (all, variable) { - return varList[variable]; - }); - }, - ratifySetters: function ratifySetters(varList) { - varList.filter(function (curVars) { - return curVars; - }).forEach(function (curVars) { - curVars.forEach(function (theVar) { - var matches = theVar.split(/:\s*/); - cssVarPoly.ratifiedVars[matches[0]] = matches[1].replace(/;/, ''); - }); - }); - Object.keys(cssVarPoly.ratifiedVars).filter(function (key) { - return cssVarPoly.ratifiedVars[key].indexOf('var') > -1; - }).forEach(function (key) { - cssVarPoly.ratifiedVars[key] = cssVarPoly.ratifiedVars[key].replace(/var\((--.*?)\)/gm, function (all, variable) { - return cssVarPoly.ratifiedVars[variable]; - }); - }); - }, - getLink: function getLink(url, counter, success) { - var request = new XMLHttpRequest(); - request.open('GET', url, true); - request.overrideMimeType('text/css;'); - - request.onload = function () { - if (request.status >= 200 && request.status < 400) { - if (typeof success === 'function') { - success(counter, request); - } - } else { - console.warn('an error was returned from:', url); - } - }; - - request.onerror = function () { - console.warn('we could not get anything from:', url); - }; - - request.send(); - } - }; - var stateCheck = setInterval(function () { - if (document.readyState === 'complete' && typeof Meteor !== 'undefined') { - clearInterval(stateCheck); - cssVarPoly.init(); - } - }, 100); - - var DynamicCss = {}; - - window.DynamicCss = DynamicCss; - - DynamicCss.test = function () { - return window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)'); - }; - - DynamicCss.run = debounce(function () { - var replace = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; - var settings = arguments.length && arguments[1]; - - if (replace && !settings) { - console.error('You must provide settings to the "run" function in DynamicCss'); - } - - if (replace) { - var colors = settings.collection.find({ - _id: /theme-color-rc/i - }, { - fields: { - value: 1, - editor: 1 - } - }).fetch().filter(function (color) { - return color && color.value; - }); - - if (!colors) { - return; - } - - var css = colors.map(function (_ref) { - var _id = _ref._id, - value = _ref.value, - editor = _ref.editor; - - if (editor === 'expression') { - return '--' + _id.replace('theme-color-', '') + ': var(--' + value + ');'; - } - - return '--' + _id.replace('theme-color-', '') + ': ' + value + ';'; - }).join('\n'); - document.querySelector('#css-variables').innerHTML = ':root {' + css + '}'; - } - - cssVarPoly.init(); - }, 1000); -})(); diff --git a/tests/cypress/integration/04-main-elements-render.js b/tests/cypress/integration/04-main-elements-render.js index 1e71d695976b..6f9c19014fbe 100644 --- a/tests/cypress/integration/04-main-elements-render.js +++ b/tests/cypress/integration/04-main-elements-render.js @@ -133,10 +133,6 @@ describe('[Main Elements Render]', function() { mainContent.recordBtn.should('be.visible'); }); - it.skip('it should show the video call button', () => { - mainContent.videoCamBtn.should('be.visible'); - }); - it('it should show the emoji button', () => { mainContent.emojiBtn.should('be.visible'); }); diff --git a/tests/cypress/integration/12-settings.js b/tests/cypress/integration/12-settings.js index a8437627dfc7..be803c1f5c30 100644 --- a/tests/cypress/integration/12-settings.js +++ b/tests/cypress/integration/12-settings.js @@ -160,12 +160,6 @@ describe('[Api Settings Change]', () => { .end(done); }); - it.skip('it should not show the video file button', () => { - // the page needs a refresh to show the changes in the client - mainContent.videoCamBtn.waitForVisible(10000, true); - mainContent.videoCamBtn.should('not.be.visible'); - }); - it('it should change the message video files via api', (done) => { request.post(api('settings/Message_VideoRecorderEnabled')) .set(credentials) diff --git a/tests/cypress/pageobjects/main-content.page.js b/tests/cypress/pageobjects/main-content.page.js index d6d927443f4f..7ba9fe6fce88 100644 --- a/tests/cypress/pageobjects/main-content.page.js +++ b/tests/cypress/pageobjects/main-content.page.js @@ -19,8 +19,6 @@ class MainContent extends Page { get recordBtn() { return browser.element('.js-audio-message-record'); } - get videoCamBtn() { return browser.element('.message-buttons .icon-videocam'); } - get emojiBtn() { return browser.element('.rc-message-box__icon.emoji-picker-icon'); } get messagePopUp() { return browser.element('.message-popup'); } From 11a7ed87bb64fb57424d653e5a0389d87b17297a Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 15 May 2020 15:41:21 -0300 Subject: [PATCH 060/121] [NEW] Admin refactor Second phase (#17551) * [NEW] Redesign Administration > Connectivity Services (#17525) * [NEW] Admin Rewrite -> Integrations Page (#17505) * [NEW] Admin Rewrite -> OAuth Apps (#17646) Co-authored-by: Guilherme Gazzo Co-authored-by: Tasso Evangelista Co-authored-by: gabriellsh <40830821+gabriellsh@users.noreply.github.com> --- app/cloud/client/admin/callback.html | 16 - app/cloud/client/admin/callback.js | 46 --- app/cloud/client/admin/cloud.html | 145 ------- app/cloud/client/admin/cloud.js | 233 ------------ .../client/admin/cloudRegisterManually.css | 26 -- .../client/admin/cloudRegisterManually.html | 36 -- .../client/admin/cloudRegisterManually.js | 106 ------ app/cloud/client/admin/index.js | 2 - app/cloud/client/index.js | 33 -- app/integrations/client/getIntegration.js | 34 -- app/integrations/client/index.js | 3 - app/integrations/client/route.js | 75 ---- app/integrations/client/streamer.js | 3 - .../client/stylesheets/integrations.css | 75 ---- .../client/views/additional/zapier.html | 10 - app/integrations/client/views/index.js | 11 - .../client/views/integrations.html | 74 ---- app/integrations/client/views/integrations.js | 65 ---- .../client/views/integrationsIncoming.html | 138 ------- .../client/views/integrationsIncoming.js | 251 ------------- .../client/views/integrationsNew.html | 51 --- .../client/views/integrationsNew.js | 35 -- .../client/views/integrationsOutgoing.html | 241 ------------ .../client/views/integrationsOutgoing.js | 353 ------------------ .../views/integrationsOutgoingHistory.html | 143 ------- .../views/integrationsOutgoingHistory.js | 191 ---------- .../client/views/messageExample.js | 39 -- .../client/admin/route.js | 28 -- .../client/admin/views/index.js | 4 - .../client/admin/views/oauthApp.html | 72 ---- .../client/admin/views/oauthApp.js | 120 ------ .../client/admin/views/oauthApps.html | 38 -- .../client/admin/views/oauthApps.js | 33 -- app/oauth2-server-config/client/index.js | 1 - app/ui/client/components/GenericTable.js | 78 ++-- app/utils/client/lib/handleError.js | 4 +- client/admin/cloud/CloudPage.js | 146 ++++++++ client/admin/cloud/CloudRoute.js | 17 + client/admin/cloud/ConnectToCloudSection.js | 61 +++ .../cloud/ManualWorkspaceRegistrationModal.js | 183 +++++++++ client/admin/cloud/TroubleshootingSection.js | 63 ++++ client/admin/cloud/WhatIsItSection.js | 32 ++ client/admin/cloud/WorkspaceLoginSection.js | 108 ++++++ .../cloud/WorkspaceRegistrationSection.js | 128 +++++++ client/admin/cloud/constants.js | 3 + client/admin/customSounds/EditSound.js | 202 ++++++++++ client/admin/customSounds/NewSound.js | 101 +++++ .../admin/integrations/IncomingWebhookForm.js | 165 ++++++++ client/admin/integrations/IntegrationsPage.js | 56 +++ .../admin/integrations/IntegrationsRoute.js | 40 ++ .../admin/integrations/IntegrationsTable.js | 97 +++++ .../integrations/OutgoiongWebhookForm.js | 264 +++++++++++++ .../integrations/edit/EditIncomingWebhook.js | 105 ++++++ .../integrations/edit/EditIntegrationsPage.js | 82 ++++ .../integrations/edit/EditOutgoingWebhook.js | 134 +++++++ .../edit/OutgoingWebhookHistoryPage.js | 268 +++++++++++++ .../admin/integrations/exampleIncomingData.js | 20 + client/admin/integrations/new/NewBot.js | 9 + .../integrations/new/NewIncomingWebhook.js | 50 +++ .../integrations/new/NewIntegrationsPage.js | 58 +++ .../integrations/new/NewOutgoingWebhook.js | 68 ++++ client/admin/integrations/new/NewZapier.js | 43 +++ client/admin/oauthApps/OAuthAddApp.js | 87 +++++ client/admin/oauthApps/OAuthAppsPage.js | 39 ++ client/admin/oauthApps/OAuthAppsRoute.js | 15 + client/admin/oauthApps/OAuthAppsTable.js | 40 ++ client/admin/oauthApps/OAuthEditApp.js | 222 +++++++++++ client/admin/rooms/EditRoom.js | 8 +- client/admin/rooms/edit/EditRoom.js | 194 ++++++++++ client/admin/routes.js | 15 + client/admin/sidebarItems.js | 7 + client/admin/users/EditUser.js | 8 +- client/admin/users/InviteUsers.js | 4 +- client/admin/users/UserInfo.js | 8 +- client/components/setupWizard/StepHeader.js | 14 +- client/hooks/useForm.js | 28 ++ client/hooks/useHilightCode.js | 6 + client/importPackages.js | 3 +- package-lock.json | 23 +- package.json | 2 +- packages/rocketchat-i18n/i18n/ca.i18n.json | 7 +- packages/rocketchat-i18n/i18n/cs.i18n.json | 8 +- packages/rocketchat-i18n/i18n/da.i18n.json | 5 +- packages/rocketchat-i18n/i18n/de-IN.i18n.json | 5 +- packages/rocketchat-i18n/i18n/de.i18n.json | 8 +- packages/rocketchat-i18n/i18n/en.i18n.json | 22 +- packages/rocketchat-i18n/i18n/es.i18n.json | 7 +- packages/rocketchat-i18n/i18n/fa.i18n.json | 8 +- packages/rocketchat-i18n/i18n/fr.i18n.json | 4 +- packages/rocketchat-i18n/i18n/hr.i18n.json | 7 +- packages/rocketchat-i18n/i18n/hu.i18n.json | 7 +- packages/rocketchat-i18n/i18n/ja.i18n.json | 8 +- packages/rocketchat-i18n/i18n/km.i18n.json | 8 +- packages/rocketchat-i18n/i18n/ko.i18n.json | 7 +- packages/rocketchat-i18n/i18n/nl.i18n.json | 3 +- packages/rocketchat-i18n/i18n/pl.i18n.json | 8 +- packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 5 +- packages/rocketchat-i18n/i18n/pt.i18n.json | 7 +- packages/rocketchat-i18n/i18n/ru.i18n.json | 8 +- packages/rocketchat-i18n/i18n/sv.i18n.json | 3 +- packages/rocketchat-i18n/i18n/tr.i18n.json | 7 +- packages/rocketchat-i18n/i18n/uk.i18n.json | 8 +- packages/rocketchat-i18n/i18n/zh-TW.i18n.json | 6 +- packages/rocketchat-i18n/i18n/zh.i18n.json | 8 +- 104 files changed, 3304 insertions(+), 2909 deletions(-) delete mode 100644 app/cloud/client/admin/callback.html delete mode 100644 app/cloud/client/admin/callback.js delete mode 100644 app/cloud/client/admin/cloud.html delete mode 100644 app/cloud/client/admin/cloud.js delete mode 100644 app/cloud/client/admin/cloudRegisterManually.css delete mode 100644 app/cloud/client/admin/cloudRegisterManually.html delete mode 100644 app/cloud/client/admin/cloudRegisterManually.js delete mode 100644 app/cloud/client/admin/index.js delete mode 100644 app/cloud/client/index.js delete mode 100644 app/integrations/client/getIntegration.js delete mode 100644 app/integrations/client/index.js delete mode 100644 app/integrations/client/route.js delete mode 100644 app/integrations/client/streamer.js delete mode 100644 app/integrations/client/stylesheets/integrations.css delete mode 100644 app/integrations/client/views/additional/zapier.html delete mode 100644 app/integrations/client/views/index.js delete mode 100644 app/integrations/client/views/integrations.html delete mode 100644 app/integrations/client/views/integrations.js delete mode 100644 app/integrations/client/views/integrationsIncoming.html delete mode 100644 app/integrations/client/views/integrationsIncoming.js delete mode 100644 app/integrations/client/views/integrationsNew.html delete mode 100644 app/integrations/client/views/integrationsNew.js delete mode 100644 app/integrations/client/views/integrationsOutgoing.html delete mode 100644 app/integrations/client/views/integrationsOutgoing.js delete mode 100644 app/integrations/client/views/integrationsOutgoingHistory.html delete mode 100644 app/integrations/client/views/integrationsOutgoingHistory.js delete mode 100644 app/integrations/client/views/messageExample.js delete mode 100644 app/oauth2-server-config/client/admin/route.js delete mode 100644 app/oauth2-server-config/client/admin/views/index.js delete mode 100644 app/oauth2-server-config/client/admin/views/oauthApp.html delete mode 100644 app/oauth2-server-config/client/admin/views/oauthApp.js delete mode 100644 app/oauth2-server-config/client/admin/views/oauthApps.html delete mode 100644 app/oauth2-server-config/client/admin/views/oauthApps.js create mode 100644 client/admin/cloud/CloudPage.js create mode 100644 client/admin/cloud/CloudRoute.js create mode 100644 client/admin/cloud/ConnectToCloudSection.js create mode 100644 client/admin/cloud/ManualWorkspaceRegistrationModal.js create mode 100644 client/admin/cloud/TroubleshootingSection.js create mode 100644 client/admin/cloud/WhatIsItSection.js create mode 100644 client/admin/cloud/WorkspaceLoginSection.js create mode 100644 client/admin/cloud/WorkspaceRegistrationSection.js create mode 100644 client/admin/cloud/constants.js create mode 100644 client/admin/customSounds/EditSound.js create mode 100644 client/admin/customSounds/NewSound.js create mode 100644 client/admin/integrations/IncomingWebhookForm.js create mode 100644 client/admin/integrations/IntegrationsPage.js create mode 100644 client/admin/integrations/IntegrationsRoute.js create mode 100644 client/admin/integrations/IntegrationsTable.js create mode 100644 client/admin/integrations/OutgoiongWebhookForm.js create mode 100644 client/admin/integrations/edit/EditIncomingWebhook.js create mode 100644 client/admin/integrations/edit/EditIntegrationsPage.js create mode 100644 client/admin/integrations/edit/EditOutgoingWebhook.js create mode 100644 client/admin/integrations/edit/OutgoingWebhookHistoryPage.js create mode 100644 client/admin/integrations/exampleIncomingData.js create mode 100644 client/admin/integrations/new/NewBot.js create mode 100644 client/admin/integrations/new/NewIncomingWebhook.js create mode 100644 client/admin/integrations/new/NewIntegrationsPage.js create mode 100644 client/admin/integrations/new/NewOutgoingWebhook.js create mode 100644 client/admin/integrations/new/NewZapier.js create mode 100644 client/admin/oauthApps/OAuthAddApp.js create mode 100644 client/admin/oauthApps/OAuthAppsPage.js create mode 100644 client/admin/oauthApps/OAuthAppsRoute.js create mode 100644 client/admin/oauthApps/OAuthAppsTable.js create mode 100644 client/admin/oauthApps/OAuthEditApp.js create mode 100644 client/admin/rooms/edit/EditRoom.js create mode 100644 client/hooks/useForm.js create mode 100644 client/hooks/useHilightCode.js diff --git a/app/cloud/client/admin/callback.html b/app/cloud/client/admin/callback.html deleted file mode 100644 index 5d6c9c432f4c..000000000000 --- a/app/cloud/client/admin/callback.html +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/app/cloud/client/admin/callback.js b/app/cloud/client/admin/callback.js deleted file mode 100644 index de860026192e..000000000000 --- a/app/cloud/client/admin/callback.js +++ /dev/null @@ -1,46 +0,0 @@ -import './callback.html'; - -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; -import { Tracker } from 'meteor/tracker'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import queryString from 'query-string'; - -import { SideNav } from '../../../ui-utils/client'; - - -Template.cloudCallback.onCreated(function() { - const instance = this; - - instance.loading = new ReactiveVar(true); - instance.callbackError = new ReactiveVar({ error: false }); - - const params = queryString.parse(location.search); - - if (params.error_code) { - instance.callbackError.set({ error: true, errorCode: params.error_code }); - } else { - Meteor.call('cloud:finishOAuthAuthorization', params.code, params.state, (error) => { - if (error) { - console.warn('cloud:finishOAuthAuthorization', error); - return; - } - - FlowRouter.go('/admin/cloud'); - }); - } -}); - -Template.cloudCallback.helpers({ - callbackError() { - return Template.instance().callbackError.get(); - }, -}); - -Template.cloudCallback.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); diff --git a/app/cloud/client/admin/cloud.html b/app/cloud/client/admin/cloud.html deleted file mode 100644 index 78c1e053390d..000000000000 --- a/app/cloud/client/admin/cloud.html +++ /dev/null @@ -1,145 +0,0 @@ - diff --git a/app/cloud/client/admin/cloud.js b/app/cloud/client/admin/cloud.js deleted file mode 100644 index cd9edf0a41a6..000000000000 --- a/app/cloud/client/admin/cloud.js +++ /dev/null @@ -1,233 +0,0 @@ -import './cloud.html'; - -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; -import { Tracker } from 'meteor/tracker'; -import queryString from 'query-string'; -import toastr from 'toastr'; - -import { t } from '../../../utils'; -import { SideNav, modal } from '../../../ui-utils/client'; - - -Template.cloud.onCreated(function() { - const instance = this; - instance.info = new ReactiveVar(); - instance.loading = new ReactiveVar(true); - instance.isLoggedIn = new ReactiveVar(false); - - instance.loadRegStatus = function _loadRegStatus() { - Meteor.call('cloud:checkRegisterStatus', (error, info) => { - if (error) { - console.warn('cloud:checkRegisterStatus', error); - return; - } - - instance.info.set(info); - instance.loading.set(false); - }); - }; - - instance.getLoggedIn = function _getLoggedIn() { - Meteor.call('cloud:checkUserLoggedIn', (error, result) => { - if (error) { - console.warn(error); - return; - } - - instance.isLoggedIn.set(result); - }); - }; - - instance.oauthAuthorize = function _oauthAuthorize() { - Meteor.call('cloud:getOAuthAuthorizationUrl', (error, url) => { - if (error) { - console.warn(error); - return; - } - - window.location.href = url; - }); - }; - - instance.logout = function _logout() { - Meteor.call('cloud:logout', (error) => { - if (error) { - console.warn(error); - return; - } - - instance.getLoggedIn(); - }); - }; - - instance.connectWorkspace = function _connectWorkspace(token) { - Meteor.call('cloud:connectWorkspace', token, (error, success) => { - if (error) { - toastr.error(error); - instance.loadRegStatus(); - return; - } - - if (!success) { - toastr.error('An error occured connecting'); - instance.loadRegStatus(); - return; - } - - toastr.success(t('Connected')); - - instance.loadRegStatus(); - }); - }; - - instance.disconnectWorkspace = function _disconnectWorkspace() { - Meteor.call('cloud:disconnectWorkspace', (error, success) => { - if (error) { - toastr.error(error); - instance.loadRegStatus(); - return; - } - - if (!success) { - toastr.error('An error occured disconnecting'); - instance.loadRegStatus(); - return; - } - - toastr.success(t('Disconnected')); - - instance.loadRegStatus(); - }); - }; - - instance.syncWorkspace = function _syncWorkspace() { - Meteor.call('cloud:syncWorkspace', (error, success) => { - if (error) { - toastr.error(error); - instance.loadRegStatus(); - return; - } - - if (!success) { - toastr.error('An error occured syncing'); - instance.loadRegStatus(); - return; - } - - toastr.success(t('Sync Complete')); - - instance.loadRegStatus(); - }); - }; - - instance.registerWorkspace = function _registerWorkspace() { - Meteor.call('cloud:registerWorkspace', (error, success) => { - if (error) { - toastr.error(error); - instance.loadRegStatus(); - return; - } - - if (!success) { - toastr.error('An error occured'); - instance.loadRegStatus(); - return; - } - - return instance.syncWorkspace(); - }); - }; - - const params = queryString.parse(location.search); - - if (params.token) { - instance.connectWorkspace(params.token); - } else { - instance.loadRegStatus(); - } - - instance.getLoggedIn(); -}); - -Template.cloud.helpers({ - info() { - return Template.instance().info.get(); - }, - isLoggedIn() { - return Template.instance().isLoggedIn.get(); - }, -}); - -Template.cloud.events({ - 'click .js-register'() { - modal.open({ - template: 'cloudRegisterManually', - showCancelButton: false, - showConfirmButton: false, - showFooter: false, - closeOnCancel: true, - html: true, - confirmOnEnter: false, - }); - }, - 'click .update-email-btn'() { - const val = $('input[name=cloudEmail]').val(); - - Meteor.call('cloud:updateEmail', val, false, (error) => { - if (error) { - console.warn(error); - return; - } - - toastr.success(t('Saved')); - }); - }, - - 'click .resend-email-btn'() { - const val = $('input[name=cloudEmail]').val(); - - Meteor.call('cloud:updateEmail', val, true, (error) => { - if (error) { - console.warn(error); - return; - } - - toastr.success(t('Requested')); - }); - }, - - 'click .login-btn'(e, i) { - i.oauthAuthorize(); - }, - - 'click .logout-btn'(e, i) { - i.logout(); - }, - - 'click .connect-btn'(e, i) { - const token = $('input[name=cloudToken]').val(); - - i.connectWorkspace(token); - }, - - 'click .register-btn'(e, i) { - i.registerWorkspace(); - }, - - 'click .disconnect-btn'(e, i) { - i.disconnectWorkspace(); - }, - - 'click .sync-btn'(e, i) { - i.syncWorkspace(); - }, -}); - -Template.cloud.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); diff --git a/app/cloud/client/admin/cloudRegisterManually.css b/app/cloud/client/admin/cloudRegisterManually.css deleted file mode 100644 index 8cd3c5628e34..000000000000 --- a/app/cloud/client/admin/cloudRegisterManually.css +++ /dev/null @@ -1,26 +0,0 @@ -.rc-promtp { - display: flex; - - min-height: 188px; - padding: 1rem; - - border-radius: 2px; - background: #2f343d; - flex-flow: column wrap; - justify-content: space-between; - - &--element, - &--element[disabled] { - flex: 1 1 auto; - - resize: none; - - color: #cbced1; - border: none; - background: none; - - font-family: Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; - font-size: 14px; - line-height: 20px; - } -} diff --git a/app/cloud/client/admin/cloudRegisterManually.html b/app/cloud/client/admin/cloudRegisterManually.html deleted file mode 100644 index 738a36423501..000000000000 --- a/app/cloud/client/admin/cloudRegisterManually.html +++ /dev/null @@ -1,36 +0,0 @@ - diff --git a/app/cloud/client/admin/cloudRegisterManually.js b/app/cloud/client/admin/cloudRegisterManually.js deleted file mode 100644 index 223d9bc609ce..000000000000 --- a/app/cloud/client/admin/cloudRegisterManually.js +++ /dev/null @@ -1,106 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveDict } from 'meteor/reactive-dict'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import Clipboard from 'clipboard'; -import toastr from 'toastr'; - -import { APIClient } from '../../../utils/client'; -import { modal } from '../../../ui-utils/client'; - -import './cloudRegisterManually.html'; -import './cloudRegisterManually.css'; - -const CLOUD_STEPS = { - COPY: 0, - PASTE: 1, - DONE: 2, - ERROR: 3, -}; - -Template.cloudRegisterManually.events({ - 'submit form'(e) { - e.preventDefault(); - }, - 'input .js-cloud-key'(e, instance) { - instance.state.set('cloudKey', e.currentTarget.value); - }, - 'click .js-next'(event, instance) { - instance.state.set('step', CLOUD_STEPS.PASTE); - }, - 'click .js-back'(event, instance) { - instance.state.set('step', CLOUD_STEPS.COPY); - }, - 'click .js-finish'(event, instance) { - instance.state.set('loading', true); - - APIClient - .post('v1/cloud.manualRegister', {}, { cloudBlob: instance.state.get('cloudKey') }) - .then(() => modal.open({ - type: 'success', - title: TAPi18n.__('Success'), - text: TAPi18n.__('Cloud_register_success'), - confirmButtonText: TAPi18n.__('Ok'), - closeOnConfirm: false, - showCancelButton: false, - }, () => window.location.reload())) - .catch(() => modal.open({ - type: 'error', - title: TAPi18n.__('Error'), - text: TAPi18n.__('Cloud_register_error'), - })) - .then(() => instance.state.set('loading', false)); - }, -}); - -Template.cloudRegisterManually.helpers({ - cloudLink() { - return Template.instance().cloudLink.get(); - }, - copyStep() { - return Template.instance().state.get('step') === CLOUD_STEPS.COPY; - }, - clientKey() { - return Template.instance().state.get('clientKey'); - }, - isLoading() { - return Template.instance().state.get('loading'); - }, - step() { - return Template.instance().state.get('step'); - }, - disabled() { - const { state } = Template.instance(); - - const shouldDisable = state.get('cloudKey').trim().length === 0 || state.get('loading'); - - return shouldDisable && 'disabled'; - }, -}); - -Template.cloudRegisterManually.onRendered(function() { - const clipboard = new Clipboard('.js-copy'); - clipboard.on('success', function() { - toastr.success(TAPi18n.__('Copied')); - }); - - const btn = this.find('.cloud-console-btn'); - // After_copy_the_text_go_to_cloud - this.cloudLink.set(TAPi18n.__('Cloud_click_here').replace(/(\[(.*)\]\(\))/ig, (_, __, text) => btn.outerHTML.replace('', `${ text }`))); -}); - -Template.cloudRegisterManually.onCreated(function() { - this.cloudLink = new ReactiveVar(); - this.state = new ReactiveDict({ - step: CLOUD_STEPS.COPY, - loading: false, - clientKey: '', - cloudKey: '', - error: '', - }); - - Meteor.call('cloud:getWorkspaceRegisterData', (error, result) => { - this.state.set('clientKey', result); - }); -}); diff --git a/app/cloud/client/admin/index.js b/app/cloud/client/admin/index.js deleted file mode 100644 index 92cb6032c24e..000000000000 --- a/app/cloud/client/admin/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './cloud'; -import './callback'; diff --git a/app/cloud/client/index.js b/app/cloud/client/index.js deleted file mode 100644 index ce6ceecd3d5c..000000000000 --- a/app/cloud/client/index.js +++ /dev/null @@ -1,33 +0,0 @@ -import './admin/callback'; -import './admin/cloud'; -import './admin/cloudRegisterManually'; - -import { BlazeLayout } from 'meteor/kadira:blaze-layout'; - -import { registerAdminRoute, registerAdminSidebarItem } from '../../../client/admin'; -import { hasAtLeastOnePermission } from '../../authorization'; - -registerAdminRoute('/cloud', { - name: 'cloud', - async action() { - await import('./admin'); - BlazeLayout.render('main', { center: 'cloud', old: true }); - }, -}); - -registerAdminRoute('/cloud/oauth-callback', { - name: 'cloud-oauth-callback', - async action() { - await import('./admin'); - BlazeLayout.render('main', { center: 'cloudCallback', old: true }); - }, -}); - -registerAdminSidebarItem({ - icon: 'cloud-plus', - href: 'cloud', - i18nLabel: 'Connectivity_Services', - permissionGranted() { - return hasAtLeastOnePermission(['manage-cloud']); - }, -}); diff --git a/app/integrations/client/getIntegration.js b/app/integrations/client/getIntegration.js deleted file mode 100644 index d80c8ee690e5..000000000000 --- a/app/integrations/client/getIntegration.js +++ /dev/null @@ -1,34 +0,0 @@ -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import toastr from 'toastr'; - -import { hasAllPermission } from '../../authorization/client'; -import { APIClient } from '../../utils/client'; - -export async function getIntegration(integrationId, uid) { - if (!integrationId) { - return; - } - - const reqParams = { - integrationId, - }; - - if (!hasAllPermission('manage-outgoing-integrations')) { - if (!hasAllPermission('manage-own-outgoing-integrations')) { - toastr.error(TAPi18n.__('No_integration_found')); - FlowRouter.go('admin-integrations'); - return; - } - reqParams.createdBy = uid; - } - - try { - const { integration } = await APIClient.v1.get('integrations.get', reqParams); - - return integration; - } catch (e) { - toastr.error(TAPi18n.__('Error')); - console.error(e); - } -} diff --git a/app/integrations/client/index.js b/app/integrations/client/index.js deleted file mode 100644 index f7c2445d187d..000000000000 --- a/app/integrations/client/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import '../lib/rocketchat'; -import './startup'; -import './route'; diff --git a/app/integrations/client/route.js b/app/integrations/client/route.js deleted file mode 100644 index 08a7eb444653..000000000000 --- a/app/integrations/client/route.js +++ /dev/null @@ -1,75 +0,0 @@ -import { BlazeLayout } from 'meteor/kadira:blaze-layout'; - -import { registerAdminRoute } from '../../../client/admin'; -import { t } from '../../utils'; - -const dynamic = () => import('./views'); - -registerAdminRoute('/integrations', { - name: 'admin-integrations', - async action() { - await dynamic(); - return BlazeLayout.render('main', { - center: 'integrations', - pageTitle: t('Integrations'), - }); - }, -}); - -registerAdminRoute('/integrations/new', { - name: 'admin-integrations-new', - async action() { - await dynamic(); - return BlazeLayout.render('main', { - center: 'integrationsNew', - pageTitle: t('Integration_New'), - }); - }, -}); - -registerAdminRoute('/integrations/incoming/:id?', { - name: 'admin-integrations-incoming', - async action(params) { - await dynamic(); - return BlazeLayout.render('main', { - center: 'pageSettingsContainer', - pageTitle: t('Integration_Incoming_WebHook'), - pageTemplate: 'integrationsIncoming', - params, - }); - }, -}); - -registerAdminRoute('/integrations/outgoing/:id?', { - name: 'admin-integrations-outgoing', - async action(params) { - await dynamic(); - return BlazeLayout.render('main', { - center: 'integrationsOutgoing', - pageTitle: t('Integration_Outgoing_WebHook'), - params, - }); - }, -}); - -registerAdminRoute('/integrations/outgoing/:id?/history', { - name: 'admin-integrations-outgoing-history', - async action(params) { - await dynamic(); - return BlazeLayout.render('main', { - center: 'integrationsOutgoingHistory', - pageTitle: t('Integration_Outgoing_WebHook_History'), - params, - }); - }, -}); - -registerAdminRoute('/integrations/additional/zapier', { - name: 'admin-integrations-additional-zapier', - async action() { - await dynamic(); - BlazeLayout.render('main', { - center: 'integrationsAdditionalZapier', - }); - }, -}); diff --git a/app/integrations/client/streamer.js b/app/integrations/client/streamer.js deleted file mode 100644 index 6bc2d6dc1327..000000000000 --- a/app/integrations/client/streamer.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -export const integrationHistoryStreamer = new Meteor.Streamer('integrationHistory'); diff --git a/app/integrations/client/stylesheets/integrations.css b/app/integrations/client/stylesheets/integrations.css deleted file mode 100644 index 83ee9e64cfe6..000000000000 --- a/app/integrations/client/stylesheets/integrations.css +++ /dev/null @@ -1,75 +0,0 @@ -.admin-integrations-new-panel { - & .admin-integrations-new-item { - display: flex; - - padding: 20px 10px; - - cursor: pointer; - - color: #444444; - border-bottom: 1px solid #dddddd; - align-items: center; - - &:hover { - background-color: #fafafa; - } - - & > i { - color: #aaaaaa; - - font-size: 2rem; - } - - & .admin-integrations-new-item-body { - display: flex; - flex-direction: column; - - padding: 0 20px; - flex-grow: 1; - } - - & .admin-integrations-new-item-title { - font-size: 1.4rem; - font-weight: 500; - line-height: 2.1rem; - } - - & .admin-integrations-new-item-description { - color: #aaaaaa; - - font-size: 1rem; - line-height: 1.5rem; - } - } - - & > a:last-child > .admin-integrations-new-item { - border-bottom: none; - } -} - -.message-example { - & li { - list-style: none; - } -} - -.integrate-other-ways { - & p { - font-size: 1rem; - line-height: 1.5rem; - - & a { - color: #175cc4 !important; - } - } -} - -.content.zapier { - display: flex; - flex-direction: column; - align-items: center; - - #zapier-goes-here { - width: 95%; - } -} diff --git a/app/integrations/client/views/additional/zapier.html b/app/integrations/client/views/additional/zapier.html deleted file mode 100644 index d95f0ac50691..000000000000 --- a/app/integrations/client/views/additional/zapier.html +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/app/integrations/client/views/index.js b/app/integrations/client/views/index.js deleted file mode 100644 index f7c6282cbd35..000000000000 --- a/app/integrations/client/views/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import './integrations.html'; -import './integrations'; -import './integrationsNew.html'; -import './integrationsNew'; -import './integrationsIncoming.html'; -import './integrationsIncoming'; -import './integrationsOutgoing.html'; -import './integrationsOutgoing'; -import './integrationsOutgoingHistory.html'; -import './integrationsOutgoingHistory'; -import './additional/zapier.html'; diff --git a/app/integrations/client/views/integrations.html b/app/integrations/client/views/integrations.html deleted file mode 100644 index 0a39da5ae278..000000000000 --- a/app/integrations/client/views/integrations.html +++ /dev/null @@ -1,74 +0,0 @@ - diff --git a/app/integrations/client/views/integrations.js b/app/integrations/client/views/integrations.js deleted file mode 100644 index 0506b21e1697..000000000000 --- a/app/integrations/client/views/integrations.js +++ /dev/null @@ -1,65 +0,0 @@ -import { Template } from 'meteor/templating'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Tracker } from 'meteor/tracker'; -import moment from 'moment'; -import _ from 'underscore'; - -import { hasAtLeastOnePermission } from '../../../authorization'; -import { integrations } from '../../lib/rocketchat'; -import { SideNav } from '../../../ui-utils/client'; -import { APIClient } from '../../../utils/client'; - -const ITEMS_COUNT = 50; - -Template.integrations.helpers({ - hasPermission() { - return hasAtLeastOnePermission([ - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - 'manage-incoming-integrations', - 'manage-own-incoming-integrations', - ]); - }, - integrations() { - return Template.instance().integrations.get(); - }, - dateFormated(date) { - return moment(date).format('L LT'); - }, - eventTypeI18n(event) { - return TAPi18n.__(integrations.outgoingEvents[event].label); - }, -}); - -Template.integrations.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); - -Template.integrations.onCreated(async function() { - this.integrations = new ReactiveVar([]); - this.offset = new ReactiveVar(0); - this.total = new ReactiveVar(0); - - this.autorun(async () => { - const offset = this.offset.get(); - const { integrations, total } = await APIClient.v1.get(`integrations.list?sort={"type":1}&count=${ ITEMS_COUNT }&offset=${ offset }`); - this.total.set(total); - this.integrations.set(this.integrations.get().concat(integrations)); - }); -}); - -Template.integrations.events({ - 'scroll .content': _.throttle(function(e, instance) { - if (e.target.scrollTop >= (e.target.scrollHeight - e.target.clientHeight)) { - const integrations = instance.integrations.get(); - if (instance.total.get() <= integrations.length) { - return; - } - return instance.offset.set(instance.offset.get() + ITEMS_COUNT); - } - }, 200), -}); diff --git a/app/integrations/client/views/integrationsIncoming.html b/app/integrations/client/views/integrationsIncoming.html deleted file mode 100644 index 7a98d4ac9241..000000000000 --- a/app/integrations/client/views/integrationsIncoming.html +++ /dev/null @@ -1,138 +0,0 @@ - diff --git a/app/integrations/client/views/integrationsIncoming.js b/app/integrations/client/views/integrationsIncoming.js deleted file mode 100644 index 222205d64fa7..000000000000 --- a/app/integrations/client/views/integrationsIncoming.js +++ /dev/null @@ -1,251 +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 hljs from 'highlight.js'; -import toastr from 'toastr'; - -import { exampleMsg, exampleSettings, exampleUser } from './messageExample'; -import { hasAtLeastOnePermission } from '../../../authorization'; -import { modal, SideNav } from '../../../ui-utils/client'; -import { t, handleError } from '../../../utils'; -import { getIntegration } from '../getIntegration'; - -Template.integrationsIncoming.onCreated(async function _incomingIntegrationsOnCreated() { - const params = Template.instance().data.params ? Template.instance().data.params() : undefined; - this.integration = new ReactiveVar({}); - this.record = new ReactiveVar({ - username: 'rocket.cat', - }); - if (params && params.id) { - const integration = await getIntegration(params.id, Meteor.userId()); - if (integration) { - this.integration.set(integration); - } - } -}); - -Template.integrationsIncoming.helpers({ - exampleMsg, - exampleUser, - exampleSettings, - hasPermission() { - return hasAtLeastOnePermission([ - 'manage-incoming-integrations', - 'manage-own-incoming-integrations', - ]); - }, - - canDelete() { - return this.params && this.params() && typeof this.params().id !== 'undefined'; - }, - - data() { - const data = Template.instance().integration.get(); - if (data) { - const completeToken = `${ data._id }/${ data.token }`; - data.url = Meteor.absoluteUrl(`hooks/${ completeToken }`); - data.completeToken = completeToken; - data.hasScriptError = data.scriptEnabled && data.scriptError; - Template.instance().record.set(data); - return data; - } - - return Template.instance().record.curValue; - }, - exampleJson() { - const record = Template.instance().record.get(); - const data = { - username: record.alias, - icon_emoji: record.emoji, - icon_url: record.avatar, - text: 'Example message', - attachments: [{ - title: 'Rocket.Chat', - title_link: 'https://rocket.chat', - text: 'Rocket.Chat, the best open source chat', - image_url: '/images/integration-attachment-example.png', - color: '#764FA5', - }], - }; - - const invalidData = [null, '']; - Object.keys(data).forEach((key) => { - if (invalidData.includes(data[key])) { - delete data[key]; - } - }); - - return hljs.highlight('json', JSON.stringify(data, null, 2)).value; - }, - - curl() { - const record = Template.instance().record.get(); - - if (!record.url) { - return; - } - - const data = { - username: record.alias, - icon_emoji: record.emoji, - icon_url: record.avatar, - text: 'Example message', - attachments: [{ - title: 'Rocket.Chat', - title_link: 'https://rocket.chat', - text: 'Rocket.Chat, the best open source chat', - image_url: '/images/integration-attachment-example.png', - color: '#764FA5', - }], - }; - - const invalidData = [null, '']; - Object.keys(data).forEach((key) => { - if (invalidData.includes(data[key])) { - delete data[key]; - } - }); - - return `curl -X POST -H 'Content-Type: application/json' --data '${ JSON.stringify(data) }' ${ record.url }`; - }, - - editorOptions() { - return { - lineNumbers: true, - mode: 'javascript', - gutters: [ - // 'CodeMirror-lint-markers' - 'CodeMirror-linenumbers', - 'CodeMirror-foldgutter', - ], - // lint: true, - foldGutter: true, - // lineWrapping: true, - matchBrackets: true, - autoCloseBrackets: true, - matchTags: true, - showTrailingSpace: true, - highlightSelectionMatches: true, - }; - }, -}); - -Template.integrationsIncoming.events({ - 'blur input': (e, t) => { - const value = t.record.curValue || {}; - - value.name = $('[name=name]').val().trim(); - value.alias = $('[name=alias]').val().trim(); - value.emoji = $('[name=emoji]').val().trim(); - value.avatar = $('[name=avatar]').val().trim(); - value.channel = $('[name=channel]').val().trim(); - value.username = $('[name=username]').val().trim(); - - t.record.set(value); - }, - - 'click .rc-header__section-button > .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, - }, () => { - Meteor.call('deleteIncomingIntegration', params.id, (err) => { - if (err) { - return handleError(err); - } - modal.open({ - title: t('Deleted'), - text: t('Your_entry_has_been_deleted'), - type: 'success', - timer: 1000, - showConfirmButton: false, - }); - - FlowRouter.go('admin-integrations'); - }); - }); - }, - - 'click .button-fullscreen': () => { - const codeMirrorBox = $('.code-mirror-box'); - codeMirrorBox.addClass('code-mirror-box-fullscreen content-background-color'); - codeMirrorBox.find('.CodeMirror')[0].CodeMirror.refresh(); - }, - - 'click .button-restore': () => { - const codeMirrorBox = $('.code-mirror-box'); - codeMirrorBox.removeClass('code-mirror-box-fullscreen content-background-color'); - codeMirrorBox.find('.CodeMirror')[0].CodeMirror.refresh(); - }, - - 'click .rc-header__section-button > .save': () => { - const enabled = $('[name=enabled]:checked').val().trim(); - const name = $('[name=name]').val().trim(); - const alias = $('[name=alias]').val().trim(); - const emoji = $('[name=emoji]').val().trim(); - const avatar = $('[name=avatar]').val().trim(); - const channel = $('[name=channel]').val().trim(); - const username = $('[name=username]').val().trim(); - const scriptEnabled = $('[name=scriptEnabled]:checked').val().trim(); - const script = $('[name=script]').val().trim(); - - if (channel === '') { - return toastr.error(TAPi18n.__('The_channel_name_is_required')); - } - - if (username === '') { - return toastr.error(TAPi18n.__('The_username_is_required')); - } - - const integration = { - enabled: enabled === '1', - channel, - username, - alias: alias !== '' ? alias : undefined, - emoji: emoji !== '' ? emoji : undefined, - avatar: avatar !== '' ? avatar : undefined, - name: name !== '' ? name : undefined, - script: script !== '' ? script : undefined, - scriptEnabled: scriptEnabled === '1', - }; - - const params = Template.instance().data.params ? Template.instance().data.params() : undefined; - if (params && params.id) { - Meteor.call('updateIncomingIntegration', params.id, integration, (err) => { - if (err) { - return handleError(err); - } - - toastr.success(TAPi18n.__('Integration_updated')); - }); - } else { - Meteor.call('addIncomingIntegration', integration, (err, data) => { - if (err) { - return handleError(err); - } - - toastr.success(TAPi18n.__('Integration_added')); - FlowRouter.go('admin-integrations-incoming', { id: data._id }); - }); - } - }, -}); - -Template.integrationsIncoming.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); diff --git a/app/integrations/client/views/integrationsNew.html b/app/integrations/client/views/integrationsNew.html deleted file mode 100644 index 9c3ab5ee9ec3..000000000000 --- a/app/integrations/client/views/integrationsNew.html +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/app/integrations/client/views/integrationsNew.js b/app/integrations/client/views/integrationsNew.js deleted file mode 100644 index f5bd29c08e6f..000000000000 --- a/app/integrations/client/views/integrationsNew.js +++ /dev/null @@ -1,35 +0,0 @@ -import { Template } from 'meteor/templating'; -import { Tracker } from 'meteor/tracker'; - -import { hasAtLeastOnePermission } from '../../../authorization'; -import { SideNav } from '../../../ui-utils/client'; - -Template.integrationsNew.helpers({ - hasPermission() { - return hasAtLeastOnePermission([ - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - 'manage-incoming-integrations', - 'manage-own-incoming-integrations', - ]); - }, - canAddIncomingIntegration() { - return hasAtLeastOnePermission([ - 'manage-incoming-integrations', - 'manage-own-incoming-integrations', - ]); - }, - canAddOutgoingIntegration() { - return hasAtLeastOnePermission([ - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - ]); - }, -}); - -Template.integrationsNew.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); diff --git a/app/integrations/client/views/integrationsOutgoing.html b/app/integrations/client/views/integrationsOutgoing.html deleted file mode 100644 index d9dc03ae209e..000000000000 --- a/app/integrations/client/views/integrationsOutgoing.html +++ /dev/null @@ -1,241 +0,0 @@ - diff --git a/app/integrations/client/views/integrationsOutgoing.js b/app/integrations/client/views/integrationsOutgoing.js deleted file mode 100644 index dba874aca11b..000000000000 --- a/app/integrations/client/views/integrationsOutgoing.js +++ /dev/null @@ -1,353 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Random } from 'meteor/random'; -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 hljs from 'highlight.js'; -import toastr from 'toastr'; - -import { exampleMsg, exampleSettings, exampleUser } from './messageExample'; -import { hasAtLeastOnePermission } from '../../../authorization'; -import { modal, SideNav } from '../../../ui-utils'; -import { t, handleError } from '../../../utils/client'; -import { integrations } from '../../lib/rocketchat'; -import { getIntegration } from '../getIntegration'; - -Template.integrationsOutgoing.onCreated(async function _integrationsOutgoingOnCreated() { - const params = Template.instance().data.params ? Template.instance().data.params() : undefined; - this.record = new ReactiveVar({ - username: 'rocket.cat', - token: Random.id(24), - retryFailedCalls: true, - retryCount: 6, - retryDelay: 'powers-of-ten', - runOnEdits: true, - }); - - this.updateRecord = () => { - this.record.set({ - enabled: $('[name=enabled]:checked').val().trim() === '1', - event: $('[name=event]').val().trim(), - name: $('[name=name]').val().trim(), - alias: $('[name=alias]').val().trim(), - emoji: $('[name=emoji]').val().trim(), - avatar: $('[name=avatar]').val().trim(), - channel: $('[name=channel]').val() ? $('[name=channel]').val().trim() : undefined, - username: $('[name=username]').val().trim(), - triggerWords: $('[name=triggerWords]').val() ? $('[name=triggerWords]').val().trim() : undefined, - urls: $('[name=urls]').val().trim(), - token: $('[name=token]').val().trim(), - scriptEnabled: $('[name=scriptEnabled]:checked').val().trim() === '1', - script: $('[name=script]').val().trim(), - targetRoom: $('[name=targetRoom]').val() ? $('[name=targetRoom]').val().trim() : undefined, - triggerWordAnywhere: $('[name=triggerWordAnywhere]:checked').val().trim() === '1', - retryFailedCalls: $('[name=retryFailedCalls]:checked').val().trim() === '1', - retryCount: $('[name=retryCount]').val() ? $('[name=retryCount]').val().trim() : 6, - retryDelay: $('[name=retryDelay]').val() ? $('[name=retryDelay]').val().trim() : 'powers-of-ten', - runOnEdits: $('[name=runOnEdits]:checked').val().trim() === '1', - }); - }; - - const integration = await getIntegration(params.id, Meteor.userId()); - if (params.id && !integration) { - toastr.error(TAPi18n.__('No_integration_found')); - FlowRouter.go('admin-integrations'); - return; - } - - integration.hasScriptError = integration.scriptEnabled && integration.scriptError; - this.record.set(integration); -}); - -Template.integrationsOutgoing.helpers({ - exampleMsg, - exampleUser, - exampleSettings, - join(arr, sep) { - if (!arr || !arr.join) { - return arr; - } - - return arr.join(sep); - }, - - showHistoryButton() { - return this.params && this.params() && typeof this.params().id !== 'undefined'; - }, - - hasPermission() { - return hasAtLeastOnePermission([ - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - ]); - }, - - data() { - return Template.instance().record.get(); - }, - - canDelete() { - return this.params && this.params() && typeof this.params().id !== 'undefined'; - }, - - eventTypes() { - return Object.values(integrations.outgoingEvents); - }, - - hasTypeSelected() { - const record = Template.instance().record.get(); - - return typeof record.event === 'string' && record.event !== ''; - }, - - shouldDisplayChannel() { - const record = Template.instance().record.get(); - - return typeof record.event === 'string' && integrations.outgoingEvents[record.event].use.channel; - }, - - shouldDisplayTriggerWords() { - const record = Template.instance().record.get(); - - return typeof record.event === 'string' && integrations.outgoingEvents[record.event].use.triggerWords; - }, - - shouldDisplayTargetRoom() { - const record = Template.instance().record.get(); - - return typeof record.event === 'string' && integrations.outgoingEvents[record.event].use.targetRoom; - }, - - exampleJson() { - const record = Template.instance().record.get(); - const data = { - username: record.alias, - icon_emoji: record.emoji, - icon_url: record.avatar, - text: 'Response text', - attachments: [{ - title: 'Rocket.Chat', - title_link: 'https://rocket.chat', - text: 'Rocket.Chat, the best open source chat', - image_url: '/images/integration-attachment-example.png', - color: '#764FA5', - }], - }; - - const invalidData = [null, '']; - Object.keys(data).forEach((key) => { - if (invalidData.includes(data[key])) { - delete data[key]; - } - }); - - return hljs.highlight('json', JSON.stringify(data, null, 2)).value; - }, - - editorOptions() { - return { - lineNumbers: true, - mode: 'javascript', - gutters: [ - // "CodeMirror-lint-markers", - 'CodeMirror-linenumbers', - 'CodeMirror-foldgutter', - ], - // lint: true, - foldGutter: true, - // lineWrapping: true, - matchBrackets: true, - autoCloseBrackets: true, - matchTags: true, - showTrailingSpace: true, - highlightSelectionMatches: true, - }; - }, -}); - -Template.integrationsOutgoing.events({ - 'blur input': (e, t) => { - t.updateRecord(); - }, - - 'click input[type=radio]': (e, t) => { - t.updateRecord(); - }, - - 'change select[name=event]': (e, t) => { - const record = t.record.get(); - record.event = $('[name=event]').val().trim(); - - t.record.set(record); - }, - - 'click .rc-button.history': () => { - FlowRouter.go(`/admin/integrations/outgoing/${ FlowRouter.getParam('id') }/history`); - }, - - 'click .expand': (e) => { - $(e.currentTarget).closest('.section').removeClass('section-collapsed'); - $(e.currentTarget).closest('button').removeClass('expand').addClass('collapse').find('span').text(TAPi18n.__('Collapse')); - $('.CodeMirror').each((index, codeMirror) => codeMirror.CodeMirror.refresh()); - }, - - 'click .collapse': (e) => { - $(e.currentTarget).closest('.section').addClass('section-collapsed'); - $(e.currentTarget).closest('button').addClass('expand').removeClass('collapse').find('span').text(TAPi18n.__('Expand')); - }, - - 'click .rc-header__section-button > .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, - }, () => { - Meteor.call('deleteOutgoingIntegration', params.id, (err) => { - if (err) { - handleError(err); - } else { - modal.open({ - title: t('Deleted'), - text: t('Your_entry_has_been_deleted'), - type: 'success', - timer: 1000, - showConfirmButton: false, - }); - - FlowRouter.go('admin-integrations'); - } - }); - }); - }, - - 'click .button-fullscreen': () => { - $('.code-mirror-box').addClass('code-mirror-box-fullscreen content-background-color'); - $('.CodeMirror')[0].CodeMirror.refresh(); - }, - - 'click .button-restore': () => { - $('.code-mirror-box').removeClass('code-mirror-box-fullscreen content-background-color'); - $('.CodeMirror')[0].CodeMirror.refresh(); - }, - - 'click .rc-header__section-button > .save': () => { - const event = $('[name=event]').val().trim(); - const enabled = $('[name=enabled]:checked').val().trim(); - const name = $('[name=name]').val().trim(); - const impersonateUser = $('[name=impersonateUser]:checked').val().trim(); - const alias = $('[name=alias]').val().trim(); - const emoji = $('[name=emoji]').val().trim(); - const avatar = $('[name=avatar]').val().trim(); - const username = $('[name=username]').val().trim(); - const token = $('[name=token]').val().trim(); - const scriptEnabled = $('[name=scriptEnabled]:checked').val().trim(); - const script = $('[name=script]').val().trim(); - const retryFailedCalls = $('[name=retryFailedCalls]:checked').val().trim(); - let urls = $('[name=urls]').val().trim(); - - if (username === '' && impersonateUser === '0') { - return toastr.error(TAPi18n.__('The_username_is_required')); - } - - urls = urls.split('\n').filter((url) => url.trim() !== ''); - if (urls.length === 0) { - return toastr.error(TAPi18n.__('You_should_inform_one_url_at_least')); - } - - let triggerWords; - let triggerWordAnywhere; - let runOnEdits; - if (integrations.outgoingEvents[event].use.triggerWords) { - triggerWords = $('[name=triggerWords]').val().trim(); - triggerWords = triggerWords.split(',').filter((word) => word.trim() !== ''); - - triggerWordAnywhere = $('[name=triggerWordAnywhere]:checked').val().trim(); - runOnEdits = $('[name=runOnEdits]:checked').val().trim(); - } - - let channel; - if (integrations.outgoingEvents[event].use.channel) { - channel = $('[name=channel]').val().trim(); - - if (!channel || channel.trim() === '') { - return toastr.error(TAPi18n.__('error-the-field-is-required', { field: TAPi18n.__('Channel') })); - } - } - - let targetRoom; - if (integrations.outgoingEvents[event].use.targetRoom) { - targetRoom = $('[name=targetRoom]').val().trim(); - - if (!targetRoom || targetRoom.trim() === '') { - return toastr.error(TAPi18n.__('error-the-field-is-required', { field: TAPi18n.__('TargetRoom') })); - } - } - - let retryCount; - let retryDelay; - if (retryFailedCalls === '1') { - retryCount = parseInt($('[name=retryCount]').val().trim()); - retryDelay = $('[name=retryDelay]').val().trim(); - } - - const integration = { - event: event !== '' ? event : undefined, - enabled: enabled === '1', - username, - channel: channel !== '' ? channel : undefined, - targetRoom: targetRoom !== '' ? targetRoom : undefined, - alias: alias !== '' ? alias : undefined, - emoji: emoji !== '' ? emoji : undefined, - avatar: avatar !== '' ? avatar : undefined, - name: name !== '' ? name : undefined, - triggerWords: triggerWords !== '' ? triggerWords : undefined, - urls: urls !== '' ? urls : undefined, - token: token !== '' ? token : undefined, - script: script !== '' ? script : undefined, - scriptEnabled: scriptEnabled === '1', - impersonateUser: impersonateUser === '1', - retryFailedCalls: retryFailedCalls === '1', - retryCount: retryCount || 6, - retryDelay: retryDelay || 'powers-of-ten', - triggerWordAnywhere: triggerWordAnywhere === '1', - runOnEdits: runOnEdits === '1', - }; - - const params = Template.instance().data.params ? Template.instance().data.params() : undefined; - if (params && params.id) { - Meteor.call('updateOutgoingIntegration', params.id, integration, (err) => { - if (err) { - return handleError(err); - } - - toastr.success(TAPi18n.__('Integration_updated')); - }); - } else { - Meteor.call('addOutgoingIntegration', integration, (err, data) => { - if (err) { - return handleError(err); - } - - toastr.success(TAPi18n.__('Integration_added')); - FlowRouter.go('admin-integrations-outgoing', { id: data._id }); - }); - } - }, -}); - -Template.integrationsOutgoing.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); diff --git a/app/integrations/client/views/integrationsOutgoingHistory.html b/app/integrations/client/views/integrationsOutgoingHistory.html deleted file mode 100644 index b453924e1f9d..000000000000 --- a/app/integrations/client/views/integrationsOutgoingHistory.html +++ /dev/null @@ -1,143 +0,0 @@ - diff --git a/app/integrations/client/views/integrationsOutgoingHistory.js b/app/integrations/client/views/integrationsOutgoingHistory.js deleted file mode 100644 index 66384848d6a4..000000000000 --- a/app/integrations/client/views/integrationsOutgoingHistory.js +++ /dev/null @@ -1,191 +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 _ from 'underscore'; -import hljs from 'highlight.js'; -import moment from 'moment'; -import toastr from 'toastr'; - -import { handleError } from '../../../utils'; -import { hasAtLeastOnePermission } from '../../../authorization'; -import { integrations } from '../../lib/rocketchat'; -import { SideNav } from '../../../ui-utils/client'; -import { APIClient } from '../../../utils/client'; -import { getIntegration } from '../getIntegration'; -import { integrationHistoryStreamer } from '../streamer'; - -const HISTORY_COUNT = 25; - -Template.integrationsOutgoingHistory.onCreated(async function _integrationsOutgoingHistoryOnCreated() { - const params = Template.instance().data.params ? Template.instance().data.params() : undefined; - this.isLoading = new ReactiveVar(false); - this.history = new ReactiveVar([]); - this.offset = new ReactiveVar(0); - this.total = new ReactiveVar(0); - - if (params && params.id) { - integrationHistoryStreamer.on(params.id, ({ type, id, diff, data }) => { - const histories = this.history.get(); - - if (type === 'inserted') { - this.history.set([{ ...data }].concat(histories)); - return; - } - - if (type === 'updated') { - const history = histories.find(({ _id }) => _id === id); - Object.assign(history, diff); - this.history.set(histories); - return; - } - - if (type === 'removed') { - this.history.set([]); - } - }); - - const integration = await getIntegration(params.id, Meteor.userId()); - - if (!integration) { - toastr.error(TAPi18n.__('No_integration_found')); - return FlowRouter.go('admin-integrations'); - } - this.autorun(async () => { - this.isLoading.set(true); - const { history, total } = await APIClient.v1.get(`integrations.history?id=${ integration._id }&count=${ HISTORY_COUNT }&offset=${ this.offset.get() }`); - this.history.set(this.history.get().concat(history)); - this.total.set(total); - this.isLoading.set(false); - }); - } else { - toastr.error(TAPi18n.__('No_integration_found')); - FlowRouter.go('admin-integrations'); - } -}); - -Template.integrationsOutgoingHistory.helpers({ - hasPermission() { - return hasAtLeastOnePermission(['manage-outgoing-integrations', 'manage-own-outgoing-integrations']); - }, - - isLoading() { - return Template.instance().isLoading.get(); - }, - - histories() { - return Template.instance().history.get().sort((a, b) => { - if (+a._updatedAt < +b._updatedAt) { - return 1; - } - - if (+a._updatedAt > +b._updatedAt) { - return -1; - } - - return 0; - }); - }, - - hasProperty(history, property) { - return typeof history[property] !== 'undefined' || history[property] != null; - }, - - iconClass(history) { - if (typeof history.error !== 'undefined' && history.error) { - return 'icon-cancel-circled error-color'; - } if (history.finished) { - return 'icon-ok-circled success-color'; - } - return 'icon-help-circled'; - }, - - statusI18n(error) { - return typeof error !== 'undefined' && error ? TAPi18n.__('Failure') : TAPi18n.__('Success'); - }, - - formatDate(date) { - return moment(date).format('L LTS'); - }, - - formatDateDetail(date) { - return moment(date).format('L HH:mm:ss:SSSS'); - }, - - eventTypei18n(event) { - return TAPi18n.__(integrations.outgoingEvents[event].label); - }, - - jsonStringify(data) { - if (!data) { - return ''; - } if (typeof data === 'object') { - return hljs.highlight('json', JSON.stringify(data, null, 2)).value; - } - return hljs.highlight('json', data).value; - }, - - integrationId() { - return this.params && this.params() && this.params().id; - }, -}); - -Template.integrationsOutgoingHistory.events({ - 'click .expand': (e) => { - $(e.currentTarget).closest('.section').removeClass('section-collapsed'); - $(e.currentTarget).closest('button').removeClass('expand').addClass('collapse').find('span').text(TAPi18n.__('Collapse')); - $('.CodeMirror').each((index, codeMirror) => codeMirror.CodeMirror.refresh()); - }, - - 'click .collapse': (e) => { - $(e.currentTarget).closest('.section').addClass('section-collapsed'); - $(e.currentTarget).closest('button').addClass('expand').removeClass('collapse').find('span').text(TAPi18n.__('Expand')); - }, - - 'click .replay': (e, t) => { - if (!t || !t.data || !t.data.params || !t.data.params().id) { - return; - } - - const historyId = $(e.currentTarget).attr('data-history-id'); - - Meteor.call('replayOutgoingIntegration', { integrationId: t.data.params().id, historyId }, (e) => { - if (e) { - handleError(e); - } - }); - }, - - 'click .clear-history': (e, t) => { - if (!t || !t.data || !t.data.params || !t.data.params().id) { - return; - } - - Meteor.call('clearIntegrationHistory', t.data.params().id, (e) => { - if (e) { - handleError(e); - return; - } - - toastr.success(TAPi18n.__('Integration_History_Cleared')); - - t.history.set([]); - }); - }, - - 'scroll .content': _.throttle((e, instance) => { - const history = instance.history.get(); - if ((e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight) && instance.total.get() > history.length) { - instance.offset.set(instance.offset.get() + HISTORY_COUNT); - } - }, 200), -}); - -Template.integrationsOutgoingHistory.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); -}); diff --git a/app/integrations/client/views/messageExample.js b/app/integrations/client/views/messageExample.js deleted file mode 100644 index 2e553dae32b6..000000000000 --- a/app/integrations/client/views/messageExample.js +++ /dev/null @@ -1,39 +0,0 @@ -import { Template } from 'meteor/templating'; -import { Random } from 'meteor/random'; - -export const exampleMsg = () => { - const record = Template.instance().record.get(); - return { - _id: Random.id(), - alias: record.alias, - emoji: record.emoji, - avatar: record.avatar, - msg: 'Example message', - bot: { - i: Random.id(), - }, - groupable: false, - attachments: [{ - title: 'Rocket.Chat', - title_link: 'https://rocket.chat', - text: 'Rocket.Chat, the best open source chat', - image_url: '/images/integration-attachment-example.png', - color: '#764FA5', - }], - ts: new Date(), - u: { - _id: Random.id(), - username: record.username, - }, - }; -}; - -export const exampleUser = () => ({ - u: { - _id: Random.id(), - }, -}); - -export const exampleSettings = () => ({ - settings: {}, -}); 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 c274dbaa6087..000000000000 --- 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 '../../../../client/admin'; -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/views/index.js b/app/oauth2-server-config/client/admin/views/index.js deleted file mode 100644 index 071605a56b89..000000000000 --- 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 360a4ff57c99..000000000000 --- 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 fa866ba86a81..000000000000 --- 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 99a142127802..000000000000 --- a/app/oauth2-server-config/client/admin/views/oauthApps.html +++ /dev/null @@ -1,38 +0,0 @@ - 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 595af845a769..000000000000 --- 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 917437a0b53a..5911484bf1c4 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/ui/client/components/GenericTable.js b/app/ui/client/components/GenericTable.js index 9d50a456aff5..c3a9d0cf5161 100644 --- a/app/ui/client/components/GenericTable.js +++ b/app/ui/client/components/GenericTable.js @@ -1,4 +1,4 @@ -import React, { useMemo, useState, useEffect, useCallback } from 'react'; +import React, { useMemo, useState, useEffect, useCallback, forwardRef } from 'react'; import { Box, Pagination, Skeleton, Table, Flex, Tile, Scrollable } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; @@ -35,15 +35,15 @@ const LoadingRow = ({ cols }) => )} ; -export function GenericTable({ +export const GenericTable = forwardRef(function GenericTable({ results, total, - renderRow, + renderRow: RenderRow, header, setParams = () => { }, params: paramsDefault = '', FilterComponent = () => null, -}) { +}, ref) { const t = useTranslation(); const [filter, setFilter] = useState(paramsDefault); @@ -65,40 +65,38 @@ export function GenericTable({ const itemsPerPageLabel = useCallback(() => t('Items_per_page:'), []); return <> - <> - - {results && !results.length - ? - {t('No_data_found')} - - : <> - - - - { header && - - {header} - - } - - {results - ? results.map(renderRow) - : } - -
-
-
- - - } - + + {results && !results.length + ? + {t('No_data_found')} + + : <> + + + + { header && + + {header} + + } + + {results + ? results.map((props, index) => ) + : } + +
+
+
+ + + } ; -} +}); diff --git a/app/utils/client/lib/handleError.js b/app/utils/client/lib/handleError.js index 6e2fb92bb2e9..92117a95e216 100644 --- a/app/utils/client/lib/handleError.js +++ b/app/utils/client/lib/handleError.js @@ -22,11 +22,11 @@ export const handleError = function(error, useToastr = true) { } const details = Object.entries(error.details || {}) .reduce((obj, [key, value]) => ({ ...obj, [key]: s.escapeHTML(value) }), {}); - const message = TAPi18n.__(error.error, details); + const message = TAPi18n.__(error.error || error.message, details); const title = details.errorTitle && TAPi18n.__(details.errorTitle); return toastr.error(message, title); } - return s.escapeHTML(TAPi18n.__(error.error, error.details)); + return s.escapeHTML(TAPi18n.__(error.error || error.message, error.details)); }; diff --git a/client/admin/cloud/CloudPage.js b/client/admin/cloud/CloudPage.js new file mode 100644 index 000000000000..e4e285da741a --- /dev/null +++ b/client/admin/cloud/CloudPage.js @@ -0,0 +1,146 @@ +import { Box, Button, ButtonGroup, Margins } from '@rocket.chat/fuselage'; +import { useMutableCallback, useSafely } from '@rocket.chat/fuselage-hooks'; +import React, { useState, useEffect } from 'react'; + +import Page from '../../components/basic/Page'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod } from '../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { useQueryStringParameter, useRoute, useRouteParameter } from '../../contexts/RouterContext'; +import WhatIsItSection from './WhatIsItSection'; +import ConnectToCloudSection from './ConnectToCloudSection'; +import TroubleshootingSection from './TroubleshootingSection'; +import WorkspaceRegistrationSection from './WorkspaceRegistrationSection'; +import WorkspaceLoginSection from './WorkspaceLoginSection'; +import ManualWorkspaceRegistrationModal from './ManualWorkspaceRegistrationModal'; +import { cloudConsoleUrl } from './constants'; + +function CloudPage() { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const cloudRoute = useRoute('cloud'); + + const page = useRouteParameter('page'); + + const errorCode = useQueryStringParameter('error_code'); + const code = useQueryStringParameter('code'); + const state = useQueryStringParameter('state'); + const token = useQueryStringParameter('token'); + + const finishOAuthAuthorization = useMethod('cloud:finishOAuthAuthorization'); + const checkRegisterStatus = useMethod('cloud:checkRegisterStatus'); + const connectWorkspace = useMethod('cloud:connectWorkspace'); + + useEffect(() => { + const acceptOAuthAuthorization = async () => { + if (page !== 'oauth-callback') { + return; + } + + if (errorCode) { + dispatchToastMessage({ + type: 'error', + title: t('Cloud_error_in_authenticating'), + message: t('Cloud_error_code', { errorCode }), + }); + cloudRoute.push(); + return; + } + + try { + await finishOAuthAuthorization(code, state); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + cloudRoute.push(); + } + }; + + acceptOAuthAuthorization(); + }, [errorCode, code, state]); + + const [registerStatus, setRegisterStatus] = useSafely(useState()); + const [modal, setModal] = useState(null); + + const fetchRegisterStatus = useMutableCallback(async () => { + try { + const registerStatus = await checkRegisterStatus(); + setRegisterStatus(registerStatus); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }); + + useEffect(() => { + const acceptWorkspaceToken = async () => { + try { + if (token) { + const isConnected = await connectWorkspace(token); + + if (!isConnected) { + throw Error(t('An error occured connecting')); + } + + dispatchToastMessage({ type: 'success', message: t('Connected') }); + } + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + await fetchRegisterStatus(); + } + }; + + acceptWorkspaceToken(); + }, [token]); + + const handleManualWorkspaceRegistrationButtonClick = () => { + const handleModalClose = () => { + setModal(null); + fetchRegisterStatus(); + }; + setModal(); + }; + + const isConnectedToCloud = registerStatus?.connectToCloud; + const isWorkspaceRegistered = registerStatus?.workspaceRegistered; + + return + + + {!isWorkspaceRegistered && } + + + + + {modal} + + + + + {isConnectedToCloud && <> + {isWorkspaceRegistered + ? + : } + + + } + + {!isConnectedToCloud && } + + + + ; +} + +export default CloudPage; diff --git a/client/admin/cloud/CloudRoute.js b/client/admin/cloud/CloudRoute.js new file mode 100644 index 000000000000..53c71affae7f --- /dev/null +++ b/client/admin/cloud/CloudRoute.js @@ -0,0 +1,17 @@ +import React from 'react'; + +import { usePermission } from '../../contexts/AuthorizationContext'; +import NotAuthorizedPage from '../NotAuthorizedPage'; +import CloudPage from './CloudPage'; + +function CloudRoute() { + const canManageCloud = usePermission('manage-cloud'); + + if (!canManageCloud) { + return ; + } + + return ; +} + +export default CloudRoute; diff --git a/client/admin/cloud/ConnectToCloudSection.js b/client/admin/cloud/ConnectToCloudSection.js new file mode 100644 index 000000000000..2b1009d35ccc --- /dev/null +++ b/client/admin/cloud/ConnectToCloudSection.js @@ -0,0 +1,61 @@ +import { Box, Button, ButtonGroup, Throbber } from '@rocket.chat/fuselage'; +import { useSafely } from '@rocket.chat/fuselage-hooks'; +import React, { useState } from 'react'; + +import Subtitle from '../../components/basic/Subtitle'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod } from '../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; + +function ConnectToCloudSection({ + onRegisterStatusChange, + ...props +}) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const [isConnecting, setConnecting] = useSafely(useState(false)); + + const registerWorkspace = useMethod('cloud:registerWorkspace'); + const syncWorkspace = useMethod('cloud:syncWorkspace'); + + const handleRegisterButtonClick = async () => { + setConnecting(true); + + try { + const isRegistered = await registerWorkspace(); + + if (!isRegistered) { + throw Error(t('An error occured')); + } + + // TODO: sync on register? + const isSynced = await syncWorkspace(); + + if (!isSynced) { + throw Error(t('An error occured syncing')); + } + + dispatchToastMessage({ type: 'success', message: t('Sync Complete') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + await (onRegisterStatusChange && onRegisterStatusChange()); + setConnecting(false); + } + }; + + return + {t('Cloud_registration_required')} + +

{t('Cloud_registration_required_description')}

+
+ + + +
; +} + +export default ConnectToCloudSection; diff --git a/client/admin/cloud/ManualWorkspaceRegistrationModal.js b/client/admin/cloud/ManualWorkspaceRegistrationModal.js new file mode 100644 index 000000000000..7952a3aa7053 --- /dev/null +++ b/client/admin/cloud/ManualWorkspaceRegistrationModal.js @@ -0,0 +1,183 @@ +import { Box, Button, ButtonGroup, Icon, Scrollable, Throbber } from '@rocket.chat/fuselage'; +import Clipboard from 'clipboard'; +import React, { useEffect, useState, useRef } from 'react'; + +import { Modal } from '../../components/basic/Modal'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod, useEndpoint } from '../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import MarkdownText from '../../components/basic/MarkdownText'; +import { cloudConsoleUrl } from './constants'; + +function CopyStep({ onNextButtonClick }) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const [clientKey, setClientKey] = useState(''); + + const getWorkspaceRegisterData = useMethod('cloud:getWorkspaceRegisterData'); + + useEffect(() => { + const loadWorkspaceRegisterData = async () => { + const clientKey = await getWorkspaceRegisterData(); + setClientKey(clientKey); + }; + + loadWorkspaceRegisterData(); + }, []); + + const copyRef = useRef(); + + useEffect(function() { + const clipboard = new Clipboard(copyRef.current); + clipboard.on('success', () => { + dispatchToastMessage({ type: 'success', message: t('Copied') }); + }); + + return () => { + clipboard.destroy(); + }; + }, []); + + return <> + + +

{t('Cloud_register_offline_helper')}

+
+ + + + {clientKey} + + + + + +

+ {t('Cloud_click_here', { cloudConsoleUrl })} +

+
+
+ + + + + + ; +} + +function PasteStep({ onBackButtonClick, onFinish }) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const [isLoading, setLoading] = useState(false); + const [cloudKey, setCloudKey] = useState(''); + + const handleCloudKeyChange = (e) => { + setCloudKey(e.currentTarget.value); + }; + + const registerManually = useEndpoint('POST', 'cloud.manualRegister'); + + const handleFinishButtonClick = async () => { + setLoading(true); + + try { + await registerManually({}, { cloudBlob: cloudKey }); + dispatchToastMessage({ type: 'success', message: t('Cloud_register_success') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: t('Cloud_register_error') }); + } finally { + setLoading(false); + onFinish && onFinish(); + } + }; + + return <> + + +

{t('Cloud_register_offline_finish_helper')}

+
+ + + + + +
+ + + + + + + ; +} + +const Steps = { + COPY: 'copy', + PASTE: 'paste', +}; + +function ManualWorkspaceRegistrationModal({ onClose, props }) { + const t = useTranslation(); + + const [step, setStep] = useState(Steps.COPY); + + const handleNextButtonClick = () => { + setStep(Steps.PASTE); + }; + + const handleBackButtonClick = () => { + setStep(Steps.COPY); + }; + + return + + {t('Cloud_Register_manually')} + + + {(step === Steps.COPY && ) + || (step === Steps.PASTE && )} + ; +} + +export default ManualWorkspaceRegistrationModal; diff --git a/client/admin/cloud/TroubleshootingSection.js b/client/admin/cloud/TroubleshootingSection.js new file mode 100644 index 000000000000..4d1c8e7238d7 --- /dev/null +++ b/client/admin/cloud/TroubleshootingSection.js @@ -0,0 +1,63 @@ +import { Box, Button, ButtonGroup, Throbber } from '@rocket.chat/fuselage'; +import { useSafely } from '@rocket.chat/fuselage-hooks'; +import React, { useState } from 'react'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import Subtitle from '../../components/basic/Subtitle'; +import { useMethod } from '../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { statusPageUrl } from './constants'; + +function TroubleshootingSection({ + onRegisterStatusChange, + ...props +}) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const [isSyncing, setSyncing] = useSafely(useState(false)); + + const syncWorkspace = useMethod('cloud:syncWorkspace'); + + const handleSyncButtonClick = async () => { + setSyncing(true); + + try { + const isSynced = await syncWorkspace(); + + if (!isSynced) { + throw Error(t('An error occured syncing')); + } + + dispatchToastMessage({ type: 'success', message: t('Sync Complete') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + await (onRegisterStatusChange && onRegisterStatusChange()); + setSyncing(false); + } + }; + + return + {t('Cloud_troubleshooting')} + + +

{t('Cloud_workspace_support')}

+
+ + + + + + +

+ {t('Cloud_status_page_description')}:{' '} + {statusPageUrl} +

+
+
; +} + +export default TroubleshootingSection; diff --git a/client/admin/cloud/WhatIsItSection.js b/client/admin/cloud/WhatIsItSection.js new file mode 100644 index 000000000000..3bafb0e65f57 --- /dev/null +++ b/client/admin/cloud/WhatIsItSection.js @@ -0,0 +1,32 @@ +import { Box } from '@rocket.chat/fuselage'; +import React from 'react'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import Subtitle from '../../components/basic/Subtitle'; + +function WhatIsItSection(props) { + const t = useTranslation(); + + return + {t('Cloud_what_is_it')} + + +

{t('Cloud_what_is_it_description')}

+ +
+

{t('Cloud_what_is_it_services_like')}

+ +
    +
  • {t('Register_Server_Registered_Push_Notifications')}
  • +
  • {t('Register_Server_Registered_Livechat')}
  • +
  • {t('Register_Server_Registered_OAuth')}
  • +
  • {t('Register_Server_Registered_Marketplace')}
  • +
+ +

{t('Cloud_what_is_it_additional')}

+
+
+
; +} + +export default WhatIsItSection; diff --git a/client/admin/cloud/WorkspaceLoginSection.js b/client/admin/cloud/WorkspaceLoginSection.js new file mode 100644 index 000000000000..7e2bad6cb0dd --- /dev/null +++ b/client/admin/cloud/WorkspaceLoginSection.js @@ -0,0 +1,108 @@ +import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage'; +import { useSafely } from '@rocket.chat/fuselage-hooks'; +import React, { useState, useEffect } from 'react'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod } from '../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; + +function WorkspaceLoginSection({ + onRegisterStatusChange, + ...props +}) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const checkUserLoggedIn = useMethod('cloud:checkUserLoggedIn'); + const getOAuthAuthorizationUrl = useMethod('cloud:getOAuthAuthorizationUrl'); + const logout = useMethod('cloud:logout'); + const disconnectWorkspace = useMethod('cloud:disconnectWorkspace'); + + const [isLoggedIn, setLoggedIn] = useSafely(useState(false)); + const [isLoading, setLoading] = useSafely(useState(true)); + + const handleLoginButtonClick = async () => { + setLoading(true); + + try { + const url = await getOAuthAuthorizationUrl(); + window.location.href = url; + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + setLoading(false); + } + }; + + const handleLogoutButtonClick = async () => { + setLoading(true); + + try { + await logout(); + const isLoggedIn = await checkUserLoggedIn(); + setLoggedIn(isLoggedIn); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + setLoading(false); + } + }; + + const handleDisconnectButtonClick = async () => { + setLoading(true); + + try { + const success = await disconnectWorkspace(); + + if (!success) { + throw Error(t('An error occured disconnecting')); + } + + dispatchToastMessage({ type: 'success', message: t('Disconnected') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + await (onRegisterStatusChange && onRegisterStatusChange()); + setLoading(false); + } + }; + + useEffect(() => { + const checkLoginState = async () => { + setLoading(true); + + try { + const isLoggedIn = await checkUserLoggedIn(); + setLoggedIn(isLoggedIn); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + setLoading(false); + } + }; + + checkLoginState(); + }, []); + + return + +

{t('Cloud_workspace_connected')}

+
+ + + {isLoggedIn + ? + : } + + + +

{t('Cloud_workspace_disconnect')}

+
+ + + + +
; +} + +export default WorkspaceLoginSection; diff --git a/client/admin/cloud/WorkspaceRegistrationSection.js b/client/admin/cloud/WorkspaceRegistrationSection.js new file mode 100644 index 000000000000..8f2884afa63a --- /dev/null +++ b/client/admin/cloud/WorkspaceRegistrationSection.js @@ -0,0 +1,128 @@ +import { Box, Button, ButtonGroup, EmailInput, Field, Margins, TextInput } from '@rocket.chat/fuselage'; +import { useSafely, useUniqueId } from '@rocket.chat/fuselage-hooks'; +import React, { useState, useMemo } from 'react'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { useMethod } from '../../contexts/ServerContext'; +import { supportEmailAddress } from './constants'; + +function WorkspaceRegistrationSection({ + email: initialEmail, + token: initialToken, + workspaceId, + uniqueId, + onRegisterStatusChange, + ...props +}) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const updateEmail = useMethod('cloud:updateEmail'); + const connectWorkspace = useMethod('cloud:connectWorkspace'); + + const [isProcessing, setProcessing] = useSafely(useState(false)); + const [email, setEmail] = useState(initialEmail); + const [token, setToken] = useState(initialToken); + + const supportMailtoUrl = useMemo(() => { + const subject = encodeURIComponent('Self Hosted Registration'); + const body = encodeURIComponent([ + `WorkspaceId: ${ workspaceId }`, + `Deployment Id: ${ uniqueId }`, + 'Issue: ', + ].join('\r\n')); + return `mailto:${ supportEmailAddress }?subject=${ subject }&body=${ body }`; + }, [workspaceId, uniqueId]); + + const handleEmailChange = ({ currentTarget: { value } }) => { + setEmail(value); + }; + + const handleTokenChange = ({ currentTarget: { value } }) => { + setToken(value); + }; + + const handleUpdateEmailButtonClick = async () => { + setProcessing(true); + + try { + await updateEmail(email, false); + dispatchToastMessage({ type: 'success', message: t('Saved') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + setProcessing(false); + } + }; + + const handleResendEmailButtonClick = async () => { + setProcessing(true); + + try { + await updateEmail(email, true); + dispatchToastMessage({ type: 'success', message: t('Requested') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + setProcessing(false); + } + }; + + const handleConnectButtonClick = async () => { + setProcessing(true); + + try { + const isConnected = await connectWorkspace(token); + + if (!isConnected) { + throw Error(t('An error occured connecting')); + } + + dispatchToastMessage({ type: 'success', message: t('Connected') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + await (onRegisterStatusChange && onRegisterStatusChange()); + setProcessing(false); + } + }; + + const emailInputId = useUniqueId(); + const tokenInputId = useUniqueId(); + + return + + + {t('Email')} + + + + {t('Cloud_address_to_send_registration_to')} + + + + + + + + + {t('Token')} + + + + {t('Cloud_manually_input_token')} + + + + + + + +

{t('Cloud_connect_support')}: {supportEmailAddress}

+
+
+
; +} + +export default WorkspaceRegistrationSection; diff --git a/client/admin/cloud/constants.js b/client/admin/cloud/constants.js new file mode 100644 index 000000000000..c5ed79a1477e --- /dev/null +++ b/client/admin/cloud/constants.js @@ -0,0 +1,3 @@ +export const cloudConsoleUrl = 'https://cloud.rocket.chat'; +export const supportEmailAddress = 'support@rocket.chat'; +export const statusPageUrl = 'https://status.rocket.chat'; diff --git a/client/admin/customSounds/EditSound.js b/client/admin/customSounds/EditSound.js new file mode 100644 index 000000000000..0496a617fb0e --- /dev/null +++ b/client/admin/customSounds/EditSound.js @@ -0,0 +1,202 @@ +import React, { useCallback, useState, useMemo, useEffect } from 'react'; +import { Box, Button, ButtonGroup, Margins, TextInput, Field, Icon, Skeleton, Throbber, InputBox } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod } from '../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { Modal } from '../../components/basic/Modal'; +import { useFileInput } from '../../hooks/useFileInput'; +import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; +import { validate, createSoundData } from './lib'; + +const DeleteWarningModal = ({ onDelete, onCancel, ...props }) => { + const t = useTranslation(); + return + + + {t('Are_you_sure')} + + + + {t('Custom_Sound_Status_Delete_Warning')} + + + + + + + + ; +}; + +const SuccessModal = ({ onClose, ...props }) => { + const t = useTranslation(); + return + + + {t('Deleted')} + + + + {t('Custom_Sound_Has_Been_Deleted')} + + + + + + + ; +}; + +export function EditSound({ _id, cache, ...props }) { + const t = useTranslation(); + const query = useMemo(() => ({ + query: JSON.stringify({ _id }), + }), [_id]); + + const { data, state, error } = useEndpointDataExperimental('custom-sounds.list', query); + + + if (state === ENDPOINT_STATES.LOADING) { + return + + + + + + + + + + + + ; + } + + if (error || !data || data.sounds.length < 1) { + return {t('Custom_User_Status_Error_Invalid_User_Status')}; + } + + return ; +} + +export function EditCustomSound({ close, onChange, data, ...props }) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const { _id, name: previousName } = data || {}; + const previousSound = data || {}; + + const [name, setName] = useState(''); + const [sound, setSound] = useState(); + const [modal, setModal] = useState(); + + useEffect(() => { + setName(previousName || ''); + setSound(previousSound || ''); + }, [previousName, previousSound, _id]); + + const deleteCustomSound = useMethod('deleteCustomSound'); + const uploadCustomSound = useMethod('uploadCustomSound'); + const insertOrUpdateSound = useMethod('insertOrUpdateSound'); + + const handleChangeFile = (soundFile) => { + setSound(soundFile); + }; + + const hasUnsavedChanges = useMemo(() => previousName !== name || previousSound !== sound, [name, sound]); + + const saveAction = async (sound) => { + const soundData = createSoundData(sound, name, { previousName, previousSound, _id }); + const validation = validate(soundData, sound); + if (validation.length === 0) { + let soundId; + try { + soundId = await insertOrUpdateSound(soundData); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + + soundData._id = soundId; + soundData.random = Math.round(Math.random() * 1000); + + if (sound && sound !== previousSound) { + dispatchToastMessage({ type: 'success', message: t('Uploading_file') }); + + const reader = new FileReader(); + reader.readAsBinaryString(sound); + reader.onloadend = () => { + try { + uploadCustomSound(reader.result, sound.type, soundData); + return dispatchToastMessage({ type: 'success', message: t('File_uploaded') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }; + } + } + + validation.forEach((error) => dispatchToastMessage({ type: 'error', message: t('error-the-field-is-required', { field: t(error) }) })); + }; + + const handleSave = useCallback(async () => { + saveAction(sound); + onChange(); + }, [name, _id, sound]); + + const onDeleteConfirm = useCallback(async () => { + try { + await deleteCustomSound(_id); + setModal(() => { setModal(undefined); close(); onChange(); }}/>); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + onChange(); + } + }, [_id]); + + const openConfirmDelete = () => setModal(() => setModal(undefined)}/>); + + + const clickUpload = useFileInput(handleChangeFile, 'audio/mp3'); + + + return <> + + + + {t('Name')} + + setName(e.currentTarget.value)} placeholder={t('Name')} /> + + + + + {t('Sound_File_mp3')} + + + + {(sound && sound.name) || 'none'} + + + + + + + + + + + + + + + + + + + + + + { modal } + ; +} diff --git a/client/admin/customSounds/NewSound.js b/client/admin/customSounds/NewSound.js new file mode 100644 index 000000000000..b77671f1b07a --- /dev/null +++ b/client/admin/customSounds/NewSound.js @@ -0,0 +1,101 @@ +import React, { useState, useCallback } from 'react'; +import { Field, TextInput, Box, Icon, Margins, Button, ButtonGroup } from '@rocket.chat/fuselage'; + +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod } from '../../contexts/ServerContext'; +import { useFileInput } from '../../hooks/useFileInput'; +import { validate, createSoundData } from './lib'; + +export function NewSound({ goToNew, close, onChange, ...props }) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const [name, setName] = useState(''); + const [sound, setSound] = useState(); + + const uploadCustomSound = useMethod('uploadCustomSound'); + + const insertOrUpdateSound = useMethod('insertOrUpdateSound'); + + const handleChangeFile = (soundFile) => { + setSound(soundFile); + }; + + const clickUpload = useFileInput(handleChangeFile, 'audio/mp3'); + + const saveAction = async (name, soundFile) => { + const soundData = createSoundData(soundFile, name); + const validation = validate(soundData, sound); + if (validation.length === 0) { + let soundId; + try { + soundId = await insertOrUpdateSound(soundData); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + + soundData._id = soundId; + soundData.random = Math.round(Math.random() * 1000); + + if (soundId) { + dispatchToastMessage({ type: 'success', message: t('Uploading_file') }); + + const reader = new FileReader(); + reader.readAsBinaryString(soundFile); + reader.onloadend = () => { + try { + uploadCustomSound(reader.result, soundFile.type, soundData); + dispatchToastMessage({ type: 'success', message: t('File_uploaded') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }; + } + return soundId; + } + validation.forEach((error) => { throw new Error({ type: 'error', message: t('error-the-field-is-required', { field: t(error) }) }); }); + }; + + const handleSave = useCallback(async () => { + try { + const result = await saveAction( + name, + sound, + ); + dispatchToastMessage({ type: 'success', message: t('Custom_Sound_Updated_Successfully') }); + goToNew(result)(); + onChange(); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }, [name, sound]); + + return + + + {t('Name')} + + setName(e.currentTarget.value)} placeholder={t('Name')} /> + + + + {t('Sound_File_mp3')} + + + + {(sound && sound.name) || 'none'} + + + + + + + + + + + + + ; +} diff --git a/client/admin/integrations/IncomingWebhookForm.js b/client/admin/integrations/IncomingWebhookForm.js new file mode 100644 index 000000000000..f25ba8b304f0 --- /dev/null +++ b/client/admin/integrations/IncomingWebhookForm.js @@ -0,0 +1,165 @@ +import React, { useMemo } from 'react'; +import { Field, TextInput, Box, ToggleSwitch, Icon, TextAreaInput, FieldGroup, Margins } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import { useAbsoluteUrl } from '../../contexts/ServerContext'; +import { useHilightCode } from '../../hooks/useHilightCode'; +import { useExampleData } from './exampleIncomingData'; +import Page from '../../components/basic/Page'; + +export default function IncomingWebhookForm({ formValues, formHandlers, extraData = {}, append, ...props }) { + const t = useTranslation(); + + const hilightCode = useHilightCode(); + + const absoluteUrl = useAbsoluteUrl(); + + const { + enabled, + channel, + username, + name, + alias, + avatarUrl, + emoji, + scriptEnabled, + script, + } = formValues; + + const { + handleEnabled, + handleChannel, + handleUsername, + handleName, + handleAlias, + handleAvatarUrl, + handleEmoji, + handleScriptEnabled, + handleScript, + } = formHandlers; + + const url = absoluteUrl(`hooks/${ extraData._id }/${ extraData.token }`); + + const [exampleData, curlData] = useExampleData({ + aditionalFields: { + ...alias && { alias }, + ...emoji && { emoji }, + ...avatarUrl && { avatar: avatarUrl }, + }, + url, + }, [alias, emoji, avatarUrl]); + + const hilightedExampleJson = hilightCode('json', JSON.stringify(exampleData, null, 2)); + + return + + + {useMemo(() => + + {t('Enabled')} + + + , [enabled, handleEnabled])} + {useMemo(() => + {t('Name_optional')} + + + + {t('You_should_name_it_to_easily_manage_your_integrations')} + , [name, handleName])} + {useMemo(() => + {t('Post_to_Channel')} + + }/> + + {t('Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here')} + + , [channel, handleChannel])} + {useMemo(() => + {t('Post_as')} + + }/> + + {t('Choose_the_username_that_this_integration_will_post_as')} + {t('Should_exists_a_user_with_this_username')} + , [username, handleUsername])} + {useMemo(() => + {`${ t('Alias') } (${ t('optional') })`} + + }/> + + {t('Choose_the_alias_that_will_appear_before_the_username_in_messages')} + , [alias, handleAlias])} + {useMemo(() => + {`${ t('Avatar_URL') } (${ t('optional') })`} + + }/> + + {t('You_can_change_a_different_avatar_too')} + {t('Should_be_a_URL_of_an_image')} + , [avatarUrl, handleAvatarUrl])} + {useMemo(() => + {`${ t('Emoji') } (${ t('optional') })`} + + }/> + + {t('You_can_use_an_emoji_as_avatar')} + + , [emoji, handleEmoji])} + {useMemo(() => + + {t('Script_Enabled')} + + + , [scriptEnabled, handleScriptEnabled])} + {useMemo(() => + {t('Script')} + + }/> + + , [script, handleScript])} + {useMemo(() => !extraData._id && <> + {t('Webhook_URL')} + + } disabled/> + + {t('Send_your_JSON_payloads_to_this_URL')} + + + {t('Token')} + + } disabled/> + + , [extraData._id])} + {useMemo(() => extraData._id && <> + {t('Webhook_URL')} + + }/> + + {t('Send_your_JSON_payloads_to_this_URL')} + + + {t('Token')} + + }/> + + , [url, extraData._id, extraData.token])} + {useMemo(() => + {t('Example_payload')} + + +
+
+
+
, [hilightedExampleJson])} + {useMemo(() => extraData._id && + {t('Curl')} + + }/> + + , [curlData])} + { append } +
+
+
; +} diff --git a/client/admin/integrations/IntegrationsPage.js b/client/admin/integrations/IntegrationsPage.js new file mode 100644 index 000000000000..ec9dc1adb688 --- /dev/null +++ b/client/admin/integrations/IntegrationsPage.js @@ -0,0 +1,56 @@ +import { Button, ButtonGroup, Icon, Tabs } from '@rocket.chat/fuselage'; +import React, { useEffect, useCallback } from 'react'; + +import Page from '../../components/basic/Page'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useRoute, useRouteParameter } from '../../contexts/RouterContext'; +import IntegrationsTable from './IntegrationsTable'; +import NewZapier from './new/NewZapier'; +import NewBot from './new/NewBot'; + +function IntegrationsPage() { + const t = useTranslation(); + + const router = useRoute('admin-integrations'); + + const handleNewButtonClick = useCallback(() => { + router.push({ context: 'new', type: 'incoming' }); + }, []); + + const context = useRouteParameter('context'); + useEffect(() => { + if (!context) { + router.push({ context: 'webhook-incoming' }); + } + }, [context]); + + const showTable = !['zapier', 'bots'].includes(context); + + const goToIncoming = useCallback(() => router.push({ context: 'webhook-incoming' }), []); + const goToOutgoing = useCallback(() => router.push({ context: 'webhook-outgoing' }), []); + const goToZapier = useCallback(() => router.push({ context: 'zapier' }), []); + const goToBots = useCallback(() => router.push({ context: 'bots' }), []); + + return + + + + + + + {t('Incoming')} + {t('Outgoing')} + {t('Zapier')} + {t('Bots')} + + + {context === 'zapier' && } + {context === 'bots' && } + {showTable && } + + ; +} + +export default IntegrationsPage; diff --git a/client/admin/integrations/IntegrationsRoute.js b/client/admin/integrations/IntegrationsRoute.js new file mode 100644 index 000000000000..e1af75ed11e0 --- /dev/null +++ b/client/admin/integrations/IntegrationsRoute.js @@ -0,0 +1,40 @@ +import React from 'react'; + +import { useAtLeastOnePermission } from '../../contexts/AuthorizationContext'; +import { useRouteParameter } from '../../contexts/RouterContext'; +import NotAuthorizedPage from '../NotAuthorizedPage'; +import IntegrationsPage from './IntegrationsPage'; +import NewIntegrationsPage from './new/NewIntegrationsPage'; +import EditIntegrationsPage from './edit/EditIntegrationsPage'; +import OutgoingWebhookHistoryPage from './edit/OutgoingWebhookHistoryPage'; + +function IntegrationsRoute() { + const canViewIntegrationsPage = useAtLeastOnePermission([ + 'manage-incoming-integrations', + 'manage-outgoing-integrations', + 'manage-own-incoming-integrations', + 'manage-own-outgoing-integrations', + ]); + + const context = useRouteParameter('context'); + + if (!canViewIntegrationsPage) { + return ; + } + + if (context === 'new') { + return ; + } + + if (context === 'edit') { + return ; + } + + if (context === 'history') { + return ; + } + + return ; +} + +export default IntegrationsRoute; diff --git a/client/admin/integrations/IntegrationsTable.js b/client/admin/integrations/IntegrationsTable.js new file mode 100644 index 000000000000..582485154019 --- /dev/null +++ b/client/admin/integrations/IntegrationsTable.js @@ -0,0 +1,97 @@ +import { Box, Table, TextInput, Icon } from '@rocket.chat/fuselage'; +import { useDebouncedValue, useResizeObserver } from '@rocket.chat/fuselage-hooks'; +import React, { useMemo, useCallback, useState, useEffect } from 'react'; + +import { GenericTable, Th } from '../../../app/ui/client/components/GenericTable'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useRoute } from '../../contexts/RouterContext'; +import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; +import { useFormatDateAndTime } from '../../hooks/useFormatDateAndTime'; + +const style = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }; + +const FilterByTypeAndText = React.memo(({ setFilter, ...props }) => { + const t = useTranslation(); + + const [text, setText] = useState(''); + + const handleChange = useCallback((event) => setText(event.currentTarget.value), []); + + useEffect(() => { + setFilter({ text }); + }, [text]); + + return + } onChange={handleChange} value={text} /> + ; +}); + +const useQuery = (params, sort) => useMemo(() => ({ + query: JSON.stringify({ name: { $regex: params.text || '', $options: 'i' }, type: params.type }), + sort: JSON.stringify({ [sort[0]]: sort[1] === 'asc' ? 1 : -1 }), + ...params.itemsPerPage && { count: params.itemsPerPage }, + ...params.current && { offset: params.current }, +}), [JSON.stringify(params), JSON.stringify(sort)]); + +const useResizeInlineBreakpoint = (sizes = [], debounceDelay = 0) => { + const { ref, borderBoxSize } = useResizeObserver({ debounceDelay }); + const inlineSize = borderBoxSize ? borderBoxSize.inlineSize : 0; + sizes = useMemo(() => sizes.map((current) => (inlineSize ? inlineSize > current : true)), [inlineSize]); + return [ref, ...sizes]; +}; + +export function IntegrationsTable({ type }) { + const t = useTranslation(); + const formatDateAndTime = useFormatDateAndTime(); + const [ref, isBig] = useResizeInlineBreakpoint([700], 200); + + const [params, setParams] = useState({ text: '', current: 0, itemsPerPage: 25 }); + const [sort, setSort] = useState(['name', 'asc']); + + const debouncedText = useDebouncedValue(params.text, 500); + const debouncedSort = useDebouncedValue(sort, 500); + const query = useQuery({ ...params, text: debouncedText, type }, debouncedSort); + + const { data } = useEndpointDataExperimental('integrations.list', query); + + const router = useRoute('admin-integrations'); + + const onClick = (_id, type) => () => router.push({ + context: 'edit', + type: type === 'webhook-incoming' ? 'incoming' : 'outgoing', + id: _id, + }); + + const onHeaderClick = useCallback((id) => { + const [sortBy, sortDirection] = sort; + + if (sortBy === id) { + setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); + return; + } + setSort([id, 'asc']); + }, [sort]); + + const header = useMemo(() => [ + {t('Name')}, + {t('Post_to')}, + {t('Created_by')}, + isBig && {t('Created_at')}, + {t('Post_as')}, + ].filter(Boolean), [sort, isBig]); + + const renderRow = useCallback(({ name, _id, type, username, _createdAt, _createdBy: { username: createdBy }, channel }) => { + const handler = useMemo(() => onClick(_id, type), []); + return + {name} + {channel.join(', ')} + {createdBy} + {isBig && {formatDateAndTime(_createdAt)}} + {username} + ; + }, []); + + return ; +} + +export default IntegrationsTable; diff --git a/client/admin/integrations/OutgoiongWebhookForm.js b/client/admin/integrations/OutgoiongWebhookForm.js new file mode 100644 index 000000000000..2f6fe9fa4900 --- /dev/null +++ b/client/admin/integrations/OutgoiongWebhookForm.js @@ -0,0 +1,264 @@ +import { + Field, + TextInput, + Box, + ToggleSwitch, + Icon, + TextAreaInput, + FieldGroup, + Margins, + Select, + Accordion, +} from '@rocket.chat/fuselage'; +import React, { useMemo } from 'react'; + +import { useHilightCode } from '../../hooks/useHilightCode'; +import { useExampleData } from './exampleIncomingData'; +import { useTranslation } from '../../contexts/TranslationContext'; +import Page from '../../components/basic/Page'; +import { integrations as eventList } from '../../../app/integrations/lib/rocketchat'; + + +export default function OutgoingWebhookForm({ formValues, formHandlers, append, ...props }) { + const t = useTranslation(); + + const { + enabled, + impersonateUser, + event, + urls, + triggerWords, + targetRoom, + channel, + username, + name, + alias, + avatar: avatarUrl, + emoji, + token, + scriptEnabled, + script, + retryFailedCalls, + retryCount, + retryDelay, + triggerWordAnywhere, + } = formValues; + + const { + runOnEdits, + handleEvent, + handleEnabled, + handleName, + handleChannel, + handleTriggerWords, + handleTargetRoom, + handleUrls, + handleImpersonateUser, + handleUsername, + handleAlias, + handleAvatar, + handleEmoji, + handleToken, + handleScriptEnabled, + handleScript, + handleRetryFailedCalls, + handleRetryCount, + handleRetryDelay, + handleTriggerWordAnywhere, + handleRunOnEdits, + } = formHandlers; + + const retryDelayOptions = useMemo(() => [ + ['powers-of-ten', t('powers-of-ten')], + ['powers-of-two', t('powers-of-two')], + ['increments-of-two', t('increments-of-two')], + ], []); + + const { outgoingEvents } = eventList; + + const eventOptions = useMemo(() => Object.entries(outgoingEvents).map(([key, val]) => [key, t(val.label)]), []); + + const hilightCode = useHilightCode(); + + const showChannel = useMemo(() => outgoingEvents[event].use.channel, [event]); + const showTriggerWords = useMemo(() => outgoingEvents[event].use.triggerWords, [event]); + const showTargetRoom = useMemo(() => outgoingEvents[event].use.targetRoom, [event]); + + const [exampleData] = useExampleData({ + aditionalFields: { + ...alias && { alias }, + ...emoji && { emoji }, + ...avatarUrl && { avatar: avatarUrl }, + }, + url: null, + }, [alias, emoji, avatarUrl]); + + const hilightedExampleJson = hilightCode('json', JSON.stringify(exampleData, null, 2)); + + return + + + + { useMemo(() => + {t('Event_Trigger')} + + + + + , [retryDelay])} + { useMemo(() => event === 'sendMessage' && + + + {t('Integration_Word_Trigger_Placement')} + + + {t('Integration_Word_Trigger_Placement_Description')} + + + + {t('Integration_Word_Trigger_Placement')} + + + {t('Integration_Run_When_Message_Is_Edited_Description')} + + , [triggerWordAnywhere, runOnEdits])} + + + { append } + + + ; +} diff --git a/client/admin/integrations/edit/EditIncomingWebhook.js b/client/admin/integrations/edit/EditIncomingWebhook.js new file mode 100644 index 000000000000..2a6363d9f224 --- /dev/null +++ b/client/admin/integrations/edit/EditIncomingWebhook.js @@ -0,0 +1,105 @@ +import React, { useMemo, useState } from 'react'; +import { Field, Box, Headline, Skeleton, Margins, Button } from '@rocket.chat/fuselage'; + +import { SuccessModal, DeleteWarningModal } from './EditIntegrationsPage'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; +import { useMethod } from '../../../contexts/ServerContext'; +import { useEndpointAction } from '../../../hooks/useEndpointAction'; +import { useRoute } from '../../../contexts/RouterContext'; +import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; +import { useForm } from '../../../hooks/useForm'; +import IncomingWebhookForm from '../IncomingWebhookForm'; + +export default function EditIncomingWebhookWithData({ integrationId, ...props }) { + const t = useTranslation(); + const [cache, setCache] = useState(); + + const { data, state, error } = useEndpointDataExperimental('integrations.get', useMemo(() => ({ integrationId }), [integrationId, cache])); + + const onChange = () => setCache(new Date()); + + if (state === ENDPOINT_STATES.LOADING) { + return + + + + + + + ; + } + + if (error) { + return {t('Oops_page_not_found')}; + } + + return ; +} + +const getInitialValue = (data) => { + const initialValue = { + enabled: data.enabled, + channel: data.channel.join(', ') ?? '', + username: data.username ?? '', + name: data.name ?? '', + alias: data.alias ?? '', + avatarUrl: data.avatarUrl ?? '', + emoji: data.emoji ?? '', + scriptEnabled: data.scriptEnabled, + script: data.script, + }; + return initialValue; +}; + +function EditIncomingWebhook({ data, onChange, ...props }) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const { values: formValues, handlers: formHandlers, reset } = useForm(getInitialValue(data)); + const [modal, setModal] = useState(); + + const deleteQuery = useMemo(() => ({ type: 'webhook-incoming', integrationId: data._id }), [data._id]); + const deleteIntegration = useEndpointAction('POST', 'integrations.remove', deleteQuery); + const saveIntegration = useMethod('updateIncomingIntegration'); + + const router = useRoute('admin-integrations'); + + const handleDeleteIntegration = () => { + const closeModal = () => setModal(); + const onDelete = async () => { + const result = await deleteIntegration(); + if (result.success) { setModal( { closeModal(); router.push({}); }}/>); } + }; + + setModal(); + }; + + const handleSave = async () => { + try { + await saveIntegration(data._id, { ...formValues }); + dispatchToastMessage({ type: 'success', message: t('Integration_updated') }); + onChange(); + } catch (e) { + dispatchToastMessage({ type: 'error', message: e }); + } + }; + + const actionButtons = useMemo(() => + + + + + + + + + + ); + + + return <> + + { modal } + ; +} diff --git a/client/admin/integrations/edit/EditIntegrationsPage.js b/client/admin/integrations/edit/EditIntegrationsPage.js new file mode 100644 index 000000000000..3ffdb8168c79 --- /dev/null +++ b/client/admin/integrations/edit/EditIntegrationsPage.js @@ -0,0 +1,82 @@ +import { Button, ButtonGroup, Icon } from '@rocket.chat/fuselage'; +import React, { useCallback } from 'react'; + +import Page from '../../../components/basic/Page'; +import EditIncomingWebhookWithData from './EditIncomingWebhook'; +import EditOutgoingWebhookWithData from './EditOutgoingWebhook'; +import { Modal } from '../../../components/basic/Modal'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useRouteParameter, useRoute } from '../../../contexts/RouterContext'; + +export const DeleteWarningModal = ({ onDelete, onCancel, ...props }) => { + const t = useTranslation(); + return + + + {t('Are_you_sure')} + + + + {t('Integration_Delete_Warning')} + + + + + + + + ; +}; + +export const SuccessModal = ({ onClose, ...props }) => { + const t = useTranslation(); + return + + + {t('Deleted')} + + + + {t('Your_entry_has_been_deleted')} + + + + + + + ; +}; + +export default function NewIntegrationsPage({ ...props }) { + const t = useTranslation(); + + const router = useRoute('admin-integrations'); + + const type = useRouteParameter('type'); + const integrationId = useRouteParameter('id'); + + const handleClickReturn = useCallback(() => { + router.push({ }); + }, []); + + const handleClickHistory = useCallback(() => { + router.push({ context: 'history', type: 'outgoing', id: integrationId }); + }, [integrationId]); + + return + + + + {type === 'outgoing' && } + + + + { + (type === 'outgoing' && ) + || (type === 'incoming' && ) + } + + ; +} diff --git a/client/admin/integrations/edit/EditOutgoingWebhook.js b/client/admin/integrations/edit/EditOutgoingWebhook.js new file mode 100644 index 000000000000..7c20820b2a82 --- /dev/null +++ b/client/admin/integrations/edit/EditOutgoingWebhook.js @@ -0,0 +1,134 @@ +import React, { useMemo, useState } from 'react'; +import { + Field, + Box, + Headline, + Skeleton, + Margins, + Button, +} from '@rocket.chat/fuselage'; + +import { SuccessModal, DeleteWarningModal } from './EditIntegrationsPage'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; +import { useEndpointAction } from '../../../hooks/useEndpointAction'; +import { useRoute } from '../../../contexts/RouterContext'; +import { useMethod } from '../../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; +import OutgoingWebhookForm from '../OutgoiongWebhookForm'; +import { useForm } from '../../../hooks/useForm'; + +export default function EditOutgoingWebhookWithData({ integrationId, ...props }) { + const t = useTranslation(); + const [cache, setCache] = useState(); + + const { data, state, error } = useEndpointDataExperimental('integrations.get', useMemo(() => ({ integrationId }), [integrationId, cache])); + + const onChange = () => setCache(new Date()); + + if (state === ENDPOINT_STATES.LOADING) { + return + + + + + + + ; + } + + if (error) { + return {t('Oops_page_not_found')}; + } + + return ; +} + +const getInitialValue = (data) => { + const initialValue = { + enabled: data.enabled ?? true, + impersonateUser: data.impersonateUser, + event: data.event, + token: data.token, + urls: data.urls.join('\n') ?? '', + triggerWords: data.triggerWords?.join('; ') ?? '', + targetRoom: data.targetRoom ?? '', + channel: data.channel.join(', ') ?? '', + username: data.username ?? '', + name: data.name ?? '', + alias: data.alias ?? '', + avatarUrl: data.avatarUrl ?? '', + emoji: data.emoji ?? '', + scriptEnabled: data.scriptEnabled ?? false, + script: data.script ?? '', + retryFailedCalls: data.retryFailedCalls ?? true, + retryCount: data.retryCount ?? 5, + retryDelay: data.retryDelay ?? 'power-of-ten', + triggerrWordAnywhere: data.triggerrWordAnywhere ?? false, + runOnEdits: data.runOnEdits ?? true, + }; + return initialValue; +}; + +function EditOutgoingWebhook({ data, onChange, setSaveAction, ...props }) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const { handlers: formHandlers, values: formValues, reset } = useForm(getInitialValue(data)); + const [modal, setModal] = useState(); + + const saveIntegration = useMethod('updateOutgoingIntegration'); + + const router = useRoute('admin-integrations'); + + const deleteQuery = useMemo(() => ({ type: 'webhook-outgoing', integrationId: data._id }), [data._id]); + const deleteIntegration = useEndpointAction('POST', 'integrations.remove', deleteQuery); + + const handleDeleteIntegration = () => { + const closeModal = () => setModal(); + const onDelete = async () => { + const result = await deleteIntegration(); + if (result.success) { setModal( { closeModal(); router.push({}); }}/>); } + }; + + setModal(); + }; + + const { + urls, + triggerWords, + } = formValues; + + const handleSave = async () => { + try { + await saveIntegration(data._id, { + ...formValues, + triggerWords: triggerWords.split(';'), + urls: urls.split('\n'), + }); + + dispatchToastMessage({ type: 'success', message: t('Integration_updated') }); + onChange(); + } catch (e) { + dispatchToastMessage({ type: 'error', message: e }); + } + }; + + const actionButtons = useMemo(() => + + + + + + + + + + ); + + + return <> + + { modal } + ; +} diff --git a/client/admin/integrations/edit/OutgoingWebhookHistoryPage.js b/client/admin/integrations/edit/OutgoingWebhookHistoryPage.js new file mode 100644 index 000000000000..e931ade0b90d --- /dev/null +++ b/client/admin/integrations/edit/OutgoingWebhookHistoryPage.js @@ -0,0 +1,268 @@ +import { Button, ButtonGroup, Icon, Headline, Skeleton, Box, Accordion, Field, FieldGroup, Pagination } from '@rocket.chat/fuselage'; +import React, { useMemo, useCallback, useState, useEffect } from 'react'; + +import Page from '../../../components/basic/Page'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useHilightCode } from '../../../hooks/useHilightCode'; +import { integrations as eventList } from '../../../../app/integrations/lib/rocketchat'; +import { useMethod } from '../../../contexts/ServerContext'; +import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; +import { useRoute, useRouteParameter } from '../../../contexts/RouterContext'; +import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; +import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext'; + +function HistoryItem({ data, onChange, ...props }) { + const t = useTranslation(); + + const hilightCode = useHilightCode(); + + const replayOutgoingIntegration = useMethod('replayOutgoingIntegration'); + + const { + _id, + _createdAt, + _updatedAt, + httpResult, + event, + step, + httpCallData, + data: dataSentToTrigger, + prepareSentMessage, + processSentMessage, + url, + httpError, + errorStack, + error, + integration: { _id: integrationId }, + } = data; + + const handleClickReplay = useCallback((e) => { + e.stopPropagation(); + replayOutgoingIntegration({ integrationId, historyId: _id }); + onChange(); + }, [_id]); + + const formatDateAndTime = useFormatDateAndTime(); + + return + + {formatDateAndTime(_createdAt)} + + + + } + {...props} + > + + + {t('Status')} + + + {error ? t('Failure') : t('Success')} + + + + + {t('Integration_Outgoing_WebHook_History_Time_Triggered')} + + + {_createdAt} + + + + + {t('Integration_Outgoing_WebHook_History_Time_Ended_Or_Error')} + + + {_updatedAt} + + + + + {t('Event_Trigger')} + + + {t(eventList.outgoingEvents[event].label)} + + + + + {t('Integration_Outgoing_WebHook_History_Trigger_Step')} + + + {step} + + + + + {t('Integration_Outgoing_WebHook_History_Data_Passed_To_Trigger')} + + +
+
+
+
+ {prepareSentMessage && + {t('Integration_Outgoing_WebHook_History_Messages_Sent_From_Prepare_Script')} + + +
+
+
+
} + {processSentMessage && + {t('Integration_Outgoing_WebHook_History_Messages_Sent_From_Process_Script')} + + +
+
+
+
} + {url && + {t('URL')} + + + {url} + + + } + {httpCallData && + {t('Integration_Outgoing_WebHook_History_Data_Passed_To_URL')} + + +
+
+
+
} + {httpError && + {t('Integration_Outgoing_WebHook_History_Http_Response_Error')} + + +
+
+
+
} + {httpResult && + {t('Integration_Outgoing_WebHook_History_Http_Response')} + + +
+
+
+
} + {errorStack && + {t('Integration_Outgoing_WebHook_History_Error_Stacktrace')} + + +
+
+
+
} +
+
; +} + +function HistoryContent({ data, state, onChange, ...props }) { + const t = useTranslation(); + + const [loadedData, setLoadedData] = useState(); + useEffect(() => { + if (state === ENDPOINT_STATES.DONE) { setLoadedData(data); } + }, [state]); + + if (!loadedData || state === ENDPOINT_STATES.LOADING) { + return + + + + + + + ; + } + + if (loadedData.history.length < 1) { + return {t('Integration_Outgoing_WebHook_No_History')}; + } + + return <> + + {loadedData.history.map((current) => )} + + ; +} + +function OutgoingWebhookHistoryPage(props) { + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); + + const [cache, setCache] = useState(); + const [current, setCurrent] = useState(); + const [itemsPerPage, setItemsPerPage] = useState(); + const onChange = useCallback(() => { + setCache(new Date()); + }); + + const router = useRoute('admin-integrations'); + + const clearHistory = useMethod('clearIntegrationHistory'); + + const handleClearHistory = async () => { + try { + await clearHistory(); + dispatchToastMessage({ type: 'success', message: t('Integration_History_Cleared') }); + onChange(); + } catch (e) { + dispatchToastMessage({ type: 'error', message: e }); + } + }; + + const handleClickReturn = () => { + router.push({ }); + }; + + const id = useRouteParameter('id'); + + const query = useMemo(() => ({ + id, + cout: itemsPerPage, + offset: current, + }), [id, itemsPerPage, current, cache]); + + const { data, state } = useEndpointDataExperimental('integrations.history', query); + + const showingResultsLabel = useCallback(({ count, current, itemsPerPage }) => t('Showing results %s - %s of %s', current + 1, Math.min(current + itemsPerPage, count), count), []); + + return + + + + + + + + + + + ; +} + +export default OutgoingWebhookHistoryPage; diff --git a/client/admin/integrations/exampleIncomingData.js b/client/admin/integrations/exampleIncomingData.js new file mode 100644 index 000000000000..0226156f69fc --- /dev/null +++ b/client/admin/integrations/exampleIncomingData.js @@ -0,0 +1,20 @@ +import { useMemo } from 'react'; + +export function useExampleData({ aditionalFields, url }, dep) { + const exampleData = { + ...aditionalFields, + text: 'Example message', + attachments: [{ + title: 'Rocket.Chat', + title_link: 'https://rocket.chat', + text: 'Rocket.Chat, the best open source chat', + image_url: '/images/integration-attachment-example.png', + color: '#764FA5', + }], + }; + + return useMemo(() => [ + exampleData, + `curl -X POST -H 'Content-Type: application/json' --data '${ JSON.stringify(exampleData) }' ${ url }`, + ], dep); +} diff --git a/client/admin/integrations/new/NewBot.js b/client/admin/integrations/new/NewBot.js new file mode 100644 index 000000000000..54ff4615574e --- /dev/null +++ b/client/admin/integrations/new/NewBot.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { Box } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../../contexts/TranslationContext'; + +export default function NewBot() { + const t = useTranslation(); + return ; +} diff --git a/client/admin/integrations/new/NewIncomingWebhook.js b/client/admin/integrations/new/NewIncomingWebhook.js new file mode 100644 index 000000000000..99cdbc628276 --- /dev/null +++ b/client/admin/integrations/new/NewIncomingWebhook.js @@ -0,0 +1,50 @@ +import React, { useMemo } from 'react'; +import { Field, Box, Margins, Button } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useRoute } from '../../../contexts/RouterContext'; +import { useEndpointAction } from '../../../hooks/useEndpointAction'; +import { useForm } from '../../../hooks/useForm'; +import IncomingWebhookForm from '../IncomingWebhookForm'; + +const initialState = { + enabled: false, + channel: '', + username: '', + name: '', + alias: '', + avatarUrl: '', + emoji: '', + scriptEnabled: false, + script: '', +}; + +export default function NewIncomingWebhook(props) { + const t = useTranslation(); + + const router = useRoute('admin-integrations'); + + const { values: formValues, handlers: formHandlers, reset } = useForm(initialState); + + const saveAction = useEndpointAction('POST', 'integrations.create', useMemo(() => ({ ...formValues, type: 'webhook-incoming' }), [JSON.stringify(formValues)]), t('Integration_added')); + + const handleSave = async () => { + const result = await saveAction(); + if (result.success) { + router.push({ context: 'edit', type: 'incoming', id: result.integration._id }); + } + }; + + const actionButtons = useMemo(() => + + + + + + + + + ); + + return ; +} diff --git a/client/admin/integrations/new/NewIntegrationsPage.js b/client/admin/integrations/new/NewIntegrationsPage.js new file mode 100644 index 000000000000..70dfc441127d --- /dev/null +++ b/client/admin/integrations/new/NewIntegrationsPage.js @@ -0,0 +1,58 @@ +import { Tabs, Button, ButtonGroup, Icon } from '@rocket.chat/fuselage'; +import React, { useCallback } from 'react'; + +import Page from '../../../components/basic/Page'; +import NewIncomingWebhook from './NewIncomingWebhook'; +import NewOutgoingWebhook from './NewOutgoingWebhook'; +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useRouteParameter, useRoute } from '../../../contexts/RouterContext'; + + +export default function NewIntegrationsPage({ ...props }) { + const t = useTranslation(); + + const router = useRoute('admin-integrations'); + + const handleClickTab = (type) => () => { + router.push({ context: 'new', type }); + }; + + const handleClickReturn = useCallback(() => { + router.push({ }); + }, []); + + const tab = useRouteParameter('type'); + + const handleIncomingTab = useCallback(handleClickTab('incoming'), []); + const handleOutgoingTab = useCallback(handleClickTab('outgoing'), []); + + return + + + + + + + + {t('Incoming')} + + + {t('Outgoing')} + + + + { + (tab === 'incoming' && ) + || (tab === 'outgoing' && ) + } + + ; +} diff --git a/client/admin/integrations/new/NewOutgoingWebhook.js b/client/admin/integrations/new/NewOutgoingWebhook.js new file mode 100644 index 000000000000..f211f07d5ba1 --- /dev/null +++ b/client/admin/integrations/new/NewOutgoingWebhook.js @@ -0,0 +1,68 @@ +import React, { useMemo, useCallback } from 'react'; +import { Field, Button } from '@rocket.chat/fuselage'; +import { useUniqueId } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointAction } from '../../../hooks/useEndpointAction'; +import { useRoute } from '../../../contexts/RouterContext'; +import { useForm } from '../../../hooks/useForm'; +import OutgoingWebhookForm from '../OutgoiongWebhookForm'; + +const defaultData = { + type: 'webhook-outgoing', + enabled: true, + impersonateUser: false, + event: 'sendMessage', + urls: '', + triggerWords: '', + targetRoom: '', + channel: '', + username: '', + name: '', + alias: '', + avatar: '', + emoji: '', + scriptEnabled: false, + script: '', + retryFailedCalls: true, + retryCount: 6, + retryDelay: 'powers-of-ten', + triggerWordAnywhere: false, + runOnEdits: true, +}; + +export default function NewOutgoingWebhook({ data = defaultData, onChange, setSaveAction, ...props }) { + const t = useTranslation(); + const router = useRoute('admin-integrations'); + + const { values: formValues, handlers: formHandlers } = useForm({ ...data, token: useUniqueId() }); + + const { + urls, + triggerWords, + } = formValues; + + const query = useMemo(() => ({ + ...formValues, + urls: urls.split('\n'), + triggerWords: triggerWords.split(';'), + }), [JSON.stringify(formValues)]); + + const saveIntegration = useEndpointAction('POST', 'integrations.create', query, t('Integration_added')); + + const handleSave = useCallback(async () => { + const result = await saveIntegration(); + if (result.success) { + router.push({ id: result.integration._id, context: 'edit', type: 'outgoing' }); + } + }, [saveIntegration, router]); + + const saveButton = useMemo(() => + + + + ); + + + return ; +} diff --git a/client/admin/integrations/new/NewZapier.js b/client/admin/integrations/new/NewZapier.js new file mode 100644 index 000000000000..a9f9e1513cfc --- /dev/null +++ b/client/admin/integrations/new/NewZapier.js @@ -0,0 +1,43 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Skeleton, Margins } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../../contexts/TranslationContext'; + +const blogSpotStyleScriptImport = (src) => new Promise((resolve) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + document.body.appendChild(script); + + const resolveFunc = (event) => resolve(event.currentTarget); + + script.onreadystatechange = resolveFunc; + script.onload = resolveFunc; + script.src = src; +}); + +export default function NewZapier({ ...props }) { + const t = useTranslation(); + const [script, setScript] = useState(); + useEffect(() => { + const importZapier = async () => { + const scriptEl = await blogSpotStyleScriptImport('https://zapier.com/apps/embed/widget.js?services=rocketchat&html_id=zapier-goes-here'); + setScript(scriptEl); + }; + if (!script) { importZapier(); } + return () => script && script.parentNode.removeChild(script); + }, [script]); + + return <> + + {!script && + + + + + + + + } + + ; +} diff --git a/client/admin/oauthApps/OAuthAddApp.js b/client/admin/oauthApps/OAuthAddApp.js new file mode 100644 index 000000000000..a31a083288bd --- /dev/null +++ b/client/admin/oauthApps/OAuthAddApp.js @@ -0,0 +1,87 @@ +import React, { useCallback, useState } from 'react'; +import { + Button, + ButtonGroup, + TextInput, + Field, + TextAreaInput, + ToggleSwitch, + FieldGroup, +} from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod } from '../../contexts/ServerContext'; +import { useRoute } from '../../contexts/RouterContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import VerticalBar from '../../components/basic/VerticalBar'; + + +export default function OAuthAddApp(props) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const [newData, setNewData] = useState({ + name: '', + active: false, + redirectUri: '', + }); + + const saveApp = useMethod('addOAuthApp'); + + const router = useRoute('admin-oauth-apps'); + + const close = useCallback(() => router.push({}), [router]); + + const handleSave = useCallback(async () => { + try { + await saveApp( + newData, + ); + close(); + dispatchToastMessage({ type: 'success', message: t('Application_added') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }, [JSON.stringify(newData)]); + + const handleChange = (field, getValue = (e) => e.currentTarget.value) => (e) => setNewData({ ...newData, [field]: getValue(e) }); + + const { + active, + name, + redirectUri, + } = newData; + + return + + + + {t('Active')} + !active)}/> + + + + {t('Application_Name')} + + + + {t('Give_the_application_a_name_This_will_be_seen_by_your_users')} + + + {t('Redirect_URI')} + + + + {t('After_OAuth2_authentication_users_will_be_redirected_to_this_URL')} + + + + + + + + + + + ; +} diff --git a/client/admin/oauthApps/OAuthAppsPage.js b/client/admin/oauthApps/OAuthAppsPage.js new file mode 100644 index 000000000000..a42e5b863675 --- /dev/null +++ b/client/admin/oauthApps/OAuthAppsPage.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Button, Icon } from '@rocket.chat/fuselage'; + +import Page from '../../components/basic/Page'; +// import VerticalBar from '../../components/basic/VerticalBar'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useRouteParameter, useRoute } from '../../contexts/RouterContext'; +import OAuthAppsTable from './OAuthAppsTable'; +import OAuthEditAppWithData from './OAuthEditApp'; +import OAuthAddApp from './OAuthAddApp'; + +export function OAuthAppsPage() { + const t = useTranslation(); + + const router = useRoute('admin-oauth-apps'); + + const context = useRouteParameter('context'); + const id = useRouteParameter('id'); + + return + + + {context && } + {!context && } + + + {!context && } + {context === 'edit' && } + {context === 'new' && } + + + ; +} + +export default OAuthAppsPage; diff --git a/client/admin/oauthApps/OAuthAppsRoute.js b/client/admin/oauthApps/OAuthAppsRoute.js new file mode 100644 index 000000000000..8f163d0cb483 --- /dev/null +++ b/client/admin/oauthApps/OAuthAppsRoute.js @@ -0,0 +1,15 @@ +import React from 'react'; + +import { usePermission } from '../../contexts/AuthorizationContext'; +import NotAuthorizedPage from '../NotAuthorizedPage'; +import OAuthAppsPage from './OAuthAppsPage'; + +export default function MailerRoute() { + const canAccessOAuthApps = usePermission('manage-oauth-apps'); + + if (!canAccessOAuthApps) { + return ; + } + + return ; +} diff --git a/client/admin/oauthApps/OAuthAppsTable.js b/client/admin/oauthApps/OAuthAppsTable.js new file mode 100644 index 000000000000..91b1ec301b06 --- /dev/null +++ b/client/admin/oauthApps/OAuthAppsTable.js @@ -0,0 +1,40 @@ +import { Table } from '@rocket.chat/fuselage'; +import React, { useMemo, useCallback } from 'react'; + +import { GenericTable, Th } from '../../../app/ui/client/components/GenericTable'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useRoute } from '../../contexts/RouterContext'; +import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; +import { useFormatDateAndTime } from '../../hooks/useFormatDateAndTime'; + +export function OAuthAppsTable() { + const t = useTranslation(); + const formatDateAndTime = useFormatDateAndTime(); + + const { data } = useEndpointDataExperimental('oauth-apps.list', useMemo(() => ({}), [])); + + const router = useRoute('admin-oauth-apps'); + + const onClick = (_id) => () => router.push({ + context: 'edit', + id: _id, + }); + + const header = useMemo(() => [ + {t('Name')}, + {t('Created_by')}, + {t('Created_at')}, + ]); + + const renderRow = useCallback(({ _id, name, _createdAt, _createdBy: { username: createdBy } }) => + + {name} + {createdBy} + {formatDateAndTime(_createdAt)} + , + ); + + return ; +} + +export default OAuthAppsTable; diff --git a/client/admin/oauthApps/OAuthEditApp.js b/client/admin/oauthApps/OAuthEditApp.js new file mode 100644 index 000000000000..b525bbada583 --- /dev/null +++ b/client/admin/oauthApps/OAuthEditApp.js @@ -0,0 +1,222 @@ +import React, { useCallback, useState, useMemo } from 'react'; +import { + Box, + Button, + ButtonGroup, + TextInput, + Field, + Icon, + Skeleton, + Throbber, + InputBox, + TextAreaInput, + ToggleSwitch, + FieldGroup, +} from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../contexts/TranslationContext'; +import { useMethod, useAbsoluteUrl } from '../../contexts/ServerContext'; +import { useRoute } from '../../contexts/RouterContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { Modal } from '../../components/basic/Modal'; +import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; +import VerticalBar from '../../components/basic/VerticalBar'; + +const DeleteWarningModal = ({ onDelete, onCancel, ...props }) => { + const t = useTranslation(); + return + + + {t('Are_you_sure')} + + + + {t('Application_delete_warning')} + + + + + + + + ; +}; + +const SuccessModal = ({ onClose, ...props }) => { + const t = useTranslation(); + return + + + {t('Deleted')} + + + + {t('Your_entry_has_been_deleted')} + + + + + + + ; +}; + +export default function EditOauthAppWithData({ _id, ...props }) { + const t = useTranslation(); + + const [cache, setCache] = useState(); + + const onChange = useCallback(() => { + setCache(new Date()); + }, []); + + const query = useMemo(() => ({ + appId: _id, + }), [_id, cache]); + + const { data, state, error } = useEndpointDataExperimental('oauth-apps.get', query); + + if (state === ENDPOINT_STATES.LOADING) { + return + + + + + + + + + + + + ; + } + + if (error || !data || !_id) { + return {t('error-application-not-found')}; + } + + return ; +} + +function EditOauthApp({ onChange, data, ...props }) { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const [newData, setNewData] = useState({ + name: data.name, + active: data.active, + redirectUri: Array.isArray(data.redirectUri) ? data.redirectUri.join('\n') : data.redirectUri, + }); + const [modal, setModal] = useState(); + + const router = useRoute('admin-oauth-apps'); + + const close = useCallback(() => router.push({}), [router]); + + const absoluteUrl = useAbsoluteUrl(); + const authUrl = useMemo(() => absoluteUrl('oauth/authorize')); + const tokenUrl = useMemo(() => absoluteUrl('oauth/token')); + + const saveApp = useMethod('updateOAuthApp'); + const deleteApp = useMethod('deleteOAuthApp'); + + const handleSave = useCallback(async () => { + try { + await saveApp( + data._id, + newData, + ); + dispatchToastMessage({ type: 'success', message: t('Application_updated') }); + onChange(); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }, [JSON.stringify(newData)]); + + const onDeleteConfirm = useCallback(async () => { + try { + await deleteApp(data._id); + setModal(() => { setModal(); close(); }}/>); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }, [data._id]); + + const openConfirmDelete = () => setModal(() => setModal(undefined)}/>); + + const handleChange = (field, getValue = (e) => e.currentTarget.value) => (e) => setNewData({ ...newData, [field]: getValue(e) }); + + const { + active, + name, + redirectUri, + } = newData; + + return <> + + + + + {t('Active')} + !active)}/> + + + + {t('Application_Name')} + + + + {t('Give_the_application_a_name_This_will_be_seen_by_your_users')} + + + {t('Redirect_URI')} + + + + {t('After_OAuth2_authentication_users_will_be_redirected_to_this_URL')} + + + {t('Client_ID')} + + + + + + {t('Client_Secret')} + + + + + + {t('Authorization_URL')} + + + + + + {t('Access_Token_URL')} + + + + + + + + + + + + + + + + + + + + + + { modal } + ; +} diff --git a/client/admin/rooms/EditRoom.js b/client/admin/rooms/EditRoom.js index d60ca438a58d..f77b06334afb 100644 --- a/client/admin/rooms/EditRoom.js +++ b/client/admin/rooms/EditRoom.js @@ -1,5 +1,5 @@ import React, { useCallback, useState, useMemo } from 'react'; -import { Box, Headline, Button, Margins, TextInput, Skeleton, Field, ToggleSwitch, Divider, Icon, Callout } from '@rocket.chat/fuselage'; +import { Box, Button, Margins, TextInput, Skeleton, Field, ToggleSwitch, Divider, Icon, Callout } from '@rocket.chat/fuselage'; import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; @@ -22,11 +22,11 @@ function EditRoomWithData({ rid }) { if (state === ENDPOINT_STATES.LOADING) { return - + - + - + ; } diff --git a/client/admin/rooms/edit/EditRoom.js b/client/admin/rooms/edit/EditRoom.js new file mode 100644 index 000000000000..7638bebcc00c --- /dev/null +++ b/client/admin/rooms/edit/EditRoom.js @@ -0,0 +1,194 @@ +import React, { useCallback, useState, useMemo } from 'react'; +import { Box, Headline, Button, Margins, TextInput, Skeleton, Field, ToggleSwitch, Divider, Icon, Callout } from '@rocket.chat/fuselage'; + +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../hooks/useEndpointDataExperimental'; +import { roomTypes } from '../../../../app/utils/client'; +import { useMethod } from '../../../contexts/ServerContext'; +import { usePermission } from '../../../contexts/AuthorizationContext'; +import NotAuthorizedPage from '../../NotAuthorizedPage'; +import { useEndpointAction } from '../../../hooks/useEndpointAction'; +import Page from '../../../components/basic/Page'; + +export function EditRoomContextBar({ rid }) { + const canViewRoomAdministration = usePermission('view-room-administration'); + return canViewRoomAdministration ? : ; +} + +function EditRoomWithData({ rid }) { + const [cache, setState] = useState(); + + const { data = {}, state, error } = useEndpointDataExperimental('rooms.adminRooms.getRoom', useMemo(() => ({ rid }), [rid, cache])); + + if (state === ENDPOINT_STATES.LOADING) { + return + + + + + + + ; + } + + if (state === ENDPOINT_STATES.ERROR) { + return error.message; + } + + return setState(new Date())}/>; +} + +function EditRoom({ room, onChange }) { + const t = useTranslation(); + + const [deleted, setDeleted] = useState(false); + const [newData, setNewData] = useState({}); + const [changeArchivation, setChangeArchivation] = useState(false); + + const canDelete = usePermission(`delete-${ room.t }`); + + const hasUnsavedChanges = useMemo(() => Object.values(newData).filter((current) => current === null).length < Object.keys(newData).length, [JSON.stringify(newData)]); + const saveQuery = useMemo(() => ({ rid: room._id, ...Object.fromEntries(Object.entries(newData).filter(([, value]) => value !== null)) }), [room._id, JSON.stringify(newData)]); + + const archiveSelector = room.archived ? 'unarchive' : 'archive'; + const archiveMessage = archiveSelector === 'archive' ? 'Room_has_been_archived' : 'Room_has_been_archived'; + const archiveQuery = useMemo(() => ({ rid: room._id, action: room.archived ? 'unarchive' : 'archive' }), [room.rid, changeArchivation]); + + const saveAction = useEndpointAction('POST', 'rooms.saveRoomSettings', saveQuery, t('Room_updated_successfully')); + const archiveAction = useEndpointAction('POST', 'rooms.changeArchivationState', archiveQuery, t(archiveMessage)); + + const updateType = (type) => () => (type === 'p' ? 'c' : 'p'); + const areEqual = (a, b) => a === b || !(a || b); + + const handleChange = (field, currentValue, getValue = (e) => e.currentTarget.value) => (e) => setNewData({ ...newData, [field]: areEqual(getValue(e), currentValue) ? null : getValue(e) }); + const handleSave = async () => { + await Promise.all([hasUnsavedChanges && saveAction(), changeArchivation && archiveAction()].filter(Boolean)); + onChange('update'); + }; + + const deleteRoom = useMethod('eraseRoom'); + + const handleDelete = useCallback(async () => { + await deleteRoom(room._id); + setDeleted(true); + }, [room]); + + const roomName = room.t === 'd' ? room.usernames.join(' x ') : roomTypes.getRoomName(room.t, { type: room.t, ...room }); + const roomType = newData.roomType ?? room.t; + const readOnly = newData.readOnly ?? !!room.ro; + const isArchived = changeArchivation ? !room.archived : !!room.archived; + const isDefault = newData.default ?? !!room.default; + const isFavorite = newData.favorite ?? !!room.favorite; + const isFeatured = newData.featured ?? !!room.featured; + + return + + + {deleted && } + + + {t('Name')} + + + + + { room.t !== 'd' && <> + + {t('Owner')} + + {room.u?.username} + + + + {t('Topic')} + + + + + + + + + {t('Public')} + {t('All_users_in_the_channel_can_write_new_messages')} + + + + + + {t('Private')} + {t('Just_invited_people_can_access_this_channel')} + + + + + + + + + + {t('Collaborative')} + {t('All_users_in_the_channel_can_write_new_messages')} + + + !readOnly)}/> + + + {t('Read_only')} + {t('Only_authorized_users_can_write_new_messages')} + + + + + + + + + {t('Archived')} + setChangeArchivation(!changeArchivation)}/> + + + + + + + {t('Default')} + !isDefault)}/> + + + + + + + {t('Favorite')} + !isFavorite)}/> + + + + + + + {t('Featured')} + !isFeatured)}/> + + + + + + + + + + + + + + } + + + + + + + ; +} diff --git a/client/admin/routes.js b/client/admin/routes.js index 4df152660524..6ede81d1ef1d 100644 --- a/client/admin/routes.js +++ b/client/admin/routes.js @@ -72,6 +72,16 @@ registerAdminRoute('/mailer', { lazyRouteComponent: () => import('./mailer/MailerRoute'), }); +registerAdminRoute('/oauth-apps/:context?/:id?', { + name: 'admin-oauth-apps', + lazyRouteComponent: () => import('./oauthApps/OAuthAppsRoute'), +}); + +registerAdminRoute('/integrations/:context?/:type?/:id?', { + name: 'admin-integrations', + lazyRouteComponent: () => import('./integrations/IntegrationsRoute'), +}); + registerAdminRoute('/custom-user-status/:context?/:id?', { name: 'custom-user-status', lazyRouteComponent: () => import('./customUserStatus/CustomUserStatusRoute'), @@ -97,6 +107,11 @@ registerAdminRoute('/invites', { lazyRouteComponent: () => import('./invites/InvitesRoute'), }); +registerAdminRoute('/cloud/:page?', { + name: 'cloud', + lazyRouteComponent: () => import('./cloud/CloudRoute'), +}); + registerAdminRoute('/view-logs', { name: 'admin-view-logs', lazyRouteComponent: () => import('./viewLogs/ViewLogsRoute'), diff --git a/client/admin/sidebarItems.js b/client/admin/sidebarItems.js index a76b96d08dda..c5112049b571 100644 --- a/client/admin/sidebarItems.js +++ b/client/admin/sidebarItems.js @@ -42,6 +42,13 @@ registerAdminSidebarItem({ permissionGranted: () => hasPermission('create-invite-links'), }); +registerAdminSidebarItem({ + icon: 'cloud-plus', + href: 'cloud', + i18nLabel: 'Connectivity_Services', + permissionGranted: () => hasPermission('manage-cloud'), +}); + registerAdminSidebarItem({ href: 'admin-view-logs', i18nLabel: 'View_Logs', diff --git a/client/admin/users/EditUser.js b/client/admin/users/EditUser.js index a7b287f234ac..1feb1eb82c34 100644 --- a/client/admin/users/EditUser.js +++ b/client/admin/users/EditUser.js @@ -1,5 +1,5 @@ import React, { useMemo, useState } from 'react'; -import { Field, TextInput, Box, Headline, Skeleton, ToggleSwitch, Icon, TextAreaInput, MultiSelectFiltered, Margins, Button } from '@rocket.chat/fuselage'; +import { Field, TextInput, Box, Skeleton, ToggleSwitch, Icon, TextAreaInput, MultiSelectFiltered, Margins, Button } from '@rocket.chat/fuselage'; import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointData } from '../../hooks/useEndpointData'; @@ -18,11 +18,11 @@ export function EditUserWithData({ userId, ...props }) { if (state === ENDPOINT_STATES.LOADING) { return - + - + - + ; } diff --git a/client/admin/users/InviteUsers.js b/client/admin/users/InviteUsers.js index 5804f498c863..d47db76f7ff0 100644 --- a/client/admin/users/InviteUsers.js +++ b/client/admin/users/InviteUsers.js @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react'; -import { Box, Headline, Button, Icon, TextAreaInput } from '@rocket.chat/fuselage'; +import { Box, Button, Icon, TextAreaInput } from '@rocket.chat/fuselage'; import { useTranslation } from '../../contexts/TranslationContext'; import { useMethod } from '../../contexts/ServerContext'; @@ -24,7 +24,7 @@ export function InviteUsers({ data, ...props }) { }); }; return - {t('Send_invitation_email')} + {t('Send_invitation_email')} {t('Send_invitation_email_info')} setText(e.currentTarget.value)}/> + + + + , [reset, handleSave]); - return e.preventDefault(), [])} { ...props }> - - {t('Name')} - - - - - - {t('Username')} - - }/> - - - - {t('Email')} - - 0 ? 'error' : undefined} onChange={handleChange('email')} addon={}/> - - - - {t('Verified')} !verified)} /> - - - - - {t('StatusMessage')} - - }/> - - - - {t('Bio')} - - }/> - - - - {t('Password')} - - }/> - - - - - - {t('Require_password_change')} !requirePasswordChange)} /> - - - - - - - {t('Set_random_password_and_send_by_email')} !setRandomPassword)} /> - - - - - {t('Roles')} - - value)} placeholder={t('Select_role')} /> - - - - - - {t('Join_default_channels')} !joinDefaultChannels)} /> - - - - - - - {t('Send_welcome_email')} !sendWelcomeEmail)} /> - - - - - - - - - - - - - - ; + return ; } diff --git a/client/admin/users/CustomFieldsForm.js b/client/admin/users/CustomFieldsForm.js new file mode 100644 index 000000000000..e6ee3074fa5b --- /dev/null +++ b/client/admin/users/CustomFieldsForm.js @@ -0,0 +1,80 @@ +import React, { useMemo, useEffect } from 'react'; +import { TextInput, Select, Field, Divider, Box } from '@rocket.chat/fuselage'; + +import { useSetting } from '../../contexts/SettingsContext'; +import { useForm } from '../../hooks/useForm'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { capitalize } from '../../helpers/capitalize'; + +const CustomTextInput = (props) => { + const t = useTranslation(); + const { name, required, minLength, maxLength, setState, state } = props; + const verify = useMemo(() => { + const error = []; + if (!state && required) { error.push(t('Field_required')); } + if (state.length < minLength) { error.push(t('Min_length_is', minLength)); } + return error.join(', '); + }, [required, minLength, maxLength, state]); + + return useMemo(() => + {name} + + setState(e.currentTarget.value)}/> + + {verify} + , [name, verify, state, required]); +}; + +const CustomSelect = (props) => { + const t = useTranslation(); + const { name, required, options, setState, state } = props; + const mappedOptions = useMemo(() => Object.values(options).map((value) => [value, value]), [...options]); + const verify = useMemo(() => (!state.length && required ? t('Field_required') : ''), [required, state]); + + return useMemo(() => + {name} + + @@ -20,8 +20,8 @@ {{> icon block="rc-select__arrow" icon="arrow-down"}} + - From 96552f4fb986bd916e1746e78bf8b39e6be51672 Mon Sep 17 00:00:00 2001 From: ocanema Date: Wed, 20 May 2020 21:07:23 +0200 Subject: [PATCH 087/121] [NEW] Option to remove users from RocketChat if not found in Crowd (#17619) --- app/crowd/server/crowd.js | 8 ++++++++ app/crowd/server/settings.js | 1 + packages/rocketchat-i18n/i18n/en.i18n.json | 1 + packages/rocketchat-i18n/i18n/it.i18n.json | 3 ++- packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 1 + 5 files changed, 13 insertions(+), 1 deletion(-) 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/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 359c65494b51..c96c4f31b643 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1045,6 +1045,7 @@ "CRM_Integration": "CRM Integration", "CROWD_Allow_Custom_Username": "Allow custom username in Rocket.Chat", "CROWD_Reject_Unauthorized": "Reject Unauthorized", + "Crowd_Remove_Orphaned_Users": "Remove Orphaned Users", "Crowd_sync_interval_Description": "The interval between synchronizations. Example `every 24 hours` or `on the first day of the week`, more examples at [Cron Text Parser](http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Current Chats", "Current_File": "Current File", diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index cf93796c15a2..7c96af8a2699 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -831,6 +831,7 @@ "Created_at_s_by_s_triggered_by_s": "Creato alle %s da %s scatenato da %s", "CRM_Integration": "Integrazione CRM", "CROWD_Reject_Unauthorized": "Rifiuta non autorizzati", + "Crowd_Remove_Orphaned_Users": "Rimuovi utenti orfani", "Crowd_sync_interval_Description": "L'intervallo tra le sincronizzazioni. Esempio \"ogni 24 ore\" o \"il primo giorno della settimana\", altri esempi su [Cron Text Parser] (http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Chat attuali", "Current_Status": "Stato attuale", @@ -2888,4 +2889,4 @@ "Your_push_was_sent_to_s_devices": "La tua richiesta è stata inviata ai %s dispositivi.", "Your_server_link": "Il tuo collegamento al server", "Your_workspace_is_ready": "Il tuo spazio di lavoro è pronto per l'uso 🎉" -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 81d4439d78a9..be8eea22939f 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -1000,6 +1000,7 @@ "CRM_Integration": "Integração de CRM", "CROWD_Allow_Custom_Username": "Permitir nome de usuário personalizado no Rocket.Chat", "CROWD_Reject_Unauthorized": "Rejeitar não autorizado", + "Crowd_Remove_Orphaned_Users": "Remover usuários órfãos", "Crowd_sync_interval_Description": "O intervalo entre as sincronizações. Exemplo de \"todas as 24 horas\" ou \"no primeiro dia da semana\", mais exemplos em [Cron Text Parser] (http://bunkat.github.io/later/parsers.html#text)", "Current_Chats": "Bate-papos atuais", "Current_File": "Arquivo atual", From 239e35530aa33b041bb0a48e2c8a8298930c37ba Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Wed, 20 May 2020 16:55:34 -0300 Subject: [PATCH 088/121] [IMPROVE] Display status information in the Omnichannel Agents list (#17701) --- app/livechat/client/views/app/livechatAgents.html | 10 +++++++--- app/livechat/client/views/app/livechatAgents.js | 4 ++++ packages/rocketchat-i18n/i18n/en.i18n.json | 1 + packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) 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/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index c96c4f31b643..4fb371d1b723 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -3480,6 +3480,7 @@ "Unarchive": "Unarchive", "unarchive-room": "Unarchive Room", "unarchive-room_description": "Permission to unarchive channels", + "Unavailable": "Unavailable", "Unblock_User": "Unblock User", "Uncheck_All": "Uncheck All", "Undefined": "Undefined", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index be8eea22939f..f4bdd6bf0d33 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -3123,6 +3123,7 @@ "Unarchive": "Desarquivar", "unarchive-room": "Desarquivar Sala", "unarchive-room_description": "Permissão para desarchivar canais", + "Unavailable": "Indisponível", "Unblock_User": "Desbloquear Usuário", "Undefined": "Não definido", "Unfavorite": "Remover dos Favoritos", From c5016cf2903b252267fd16a6d78d407e9ca9607b Mon Sep 17 00:00:00 2001 From: huzaifahj <52999758+huzaifahj@users.noreply.github.com> Date: Wed, 20 May 2020 23:20:18 +0100 Subject: [PATCH 089/121] Fix typo "You aren't part of any channel yet" (#17498) --- packages/rocketchat-i18n/i18n/en.i18n.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 4fb371d1b723..bc44d4921e89 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2500,7 +2500,7 @@ "No_Limit": "No Limit", "No_available_agents_to_transfer": "No available agents to transfer", "No_channel_with_name_%s_was_found": "No channel with name \"%s\" was found!", - "No_channels_yet": "You aren't part of any channel yet", + "No_channels_yet": "You aren't part of any channels yet", "No_direct_messages_yet": "No Direct Messages.", "No_emojis_found": "No emojis found", "No_Encryption": "No Encryption", @@ -3815,4 +3815,4 @@ "Your_server_link": "Your server link", "Your_temporary_password_is_password": "Your temporary password is [password].", "Your_workspace_is_ready": "Your workspace is ready to use 🎉" -} \ No newline at end of file +} From 550d08a6e95c0a5e88601392dfac364f2387d714 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Wed, 20 May 2020 19:28:41 -0300 Subject: [PATCH 090/121] [NEW] API endpoint to fetch Omnichannel's room transfer history (#17694) --- app/livechat/server/api/lib/transfer.js | 27 +++++++++++++++++++ app/livechat/server/api/rest.js | 1 + app/livechat/server/api/v1/transfer.js | 36 +++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 app/livechat/server/api/lib/transfer.js create mode 100644 app/livechat/server/api/v1/transfer.js 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/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/transfer.js b/app/livechat/server/api/v1/transfer.js new file mode 100644 index 000000000000..e38277d5529d --- /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'; +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); + }, +}); From 74f087da708b335ffbbe36008d847359ac172b72 Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Wed, 20 May 2020 19:30:42 -0300 Subject: [PATCH 091/121] [IMPROVE] Always shows the exact match first on user's and room's autocomplete for mentions and on sidebar search (#16394) --- app/ui-sidenav/client/toolbar.js | 24 +++++++++++++++++++----- server/publications/spotlight.js | 17 ++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/app/ui-sidenav/client/toolbar.js b/app/ui-sidenav/client/toolbar.js index 97b11cfa24f6..3c59a62a7496 100644 --- a/app/ui-sidenav/client/toolbar.js +++ b/app/ui-sidenav/client/toolbar.js @@ -35,19 +35,33 @@ const getFromServer = (cb, type) => { return false; } + let exactUser = null; + let exactRoom = null; + if (results.users[0] && results.users[0].username === currentFilter) { + exactUser = results.users.shift(); + } + if (results.rooms[0] && results.rooms[0].username === currentFilter) { + exactRoom = results.rooms.shift(); + } + const resultsFromServer = []; - resultsFromServer.push(...results.users.map((user) => ({ + const roomFilter = (room) => !resultsFromClient.find((item) => [item.rid, item._id].includes(room._id)); + const userMap = (user) => ({ _id: user._id, t: 'd', name: user.username, fname: user.name, - }))); + }); - resultsFromServer.push(...results.rooms.filter((room) => !resultsFromClient.find((item) => [item.rid, item._id].includes(room._id)))); + resultsFromServer.push(...results.users.map(userMap)); + resultsFromServer.push(...results.rooms.filter(roomFilter)); - if (resultsFromServer.length) { - cb(resultsFromClient.concat(resultsFromServer)); + if (resultsFromServer.length || exactUser || exactRoom) { + exactRoom = exactRoom ? [roomFilter(exactRoom)] : []; + exactUser = exactUser ? [userMap(exactUser)] : []; + const combinedResults = exactUser.concat(exactRoom, resultsFromClient, resultsFromServer); + cb(combinedResults); } }); }; diff --git a/server/publications/spotlight.js b/server/publications/spotlight.js index 0da321b49dfd..702ed843d139 100644 --- a/server/publications/spotlight.js +++ b/server/publications/spotlight.js @@ -19,7 +19,7 @@ function fetchRooms(userId, rooms) { } Meteor.methods({ - spotlight(text, usernames, type = { users: true, rooms: true }, rid) { + spotlight(text, usernames = [], type = { users: true, rooms: true }, rid) { const searchForChannels = text[0] === '#'; const searchForDMs = text[0] === '@'; if (searchForChannels) { @@ -72,7 +72,12 @@ Meteor.methods({ if (hasPermission(userId, 'view-outside-room')) { if (type.users === true && hasPermission(userId, 'view-d-room')) { - result.users = Users.findByActiveUsersExcept(text, usernames, userOptions).fetch(); + const exactUser = Users.findOneByUsernameIgnoringCase(text, userOptions); + if (exactUser && !usernames.includes(exactUser.username)) { + result.users.push(exactUser); + usernames.push(exactUser.username); + } + result.users = result.users.concat(Users.findByActiveUsersExcept(text, usernames, userOptions).fetch()); } if (type.rooms === true && hasPermission(userId, 'view-c-room')) { @@ -81,7 +86,13 @@ Meteor.methods({ .map((roomType) => roomType[0]); const roomIds = Subscriptions.findByUserIdAndTypes(userId, searchableRoomTypes, { fields: { rid: 1 } }).fetch().map((s) => s.rid); - result.rooms = fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch()); + const exactRoom = Rooms.findOneByNameAndType(text, searchableRoomTypes, roomOptions); + if (exactRoom) { + result.exactRoom.push(exactRoom); + roomIds.push(exactRoom.rid); + } + + result.rooms = result.rooms.concat(fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch())); } } else if (type.users === true && rid) { const subscriptions = Subscriptions.find({ From 9029d830fb416696413a09b5077ec16633d35316 Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Wed, 20 May 2020 19:41:36 -0300 Subject: [PATCH 092/121] Regression: Click to join button not working (#17705) --- app/action-links/client/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/action-links/client/init.js b/app/action-links/client/init.js index 2865be4279c9..64b3fc4acb4c 100644 --- a/app/action-links/client/init.js +++ b/app/action-links/client/init.js @@ -8,7 +8,7 @@ import { actionLinks } from '../both/lib/actionLinks'; Template.room.events({ - 'click .action-link'(event, instance) { + 'click [data-actionlink]'(event, instance) { event.preventDefault(); event.stopPropagation(); From 0ab84649877e8feaf5395a89ef281701226317e5 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Wed, 20 May 2020 20:52:48 -0300 Subject: [PATCH 093/121] Update Apps-Engine version (#17706) --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d72f3451d7f..9ec9af78bac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2797,9 +2797,9 @@ } }, "@rocket.chat/apps-engine": { - "version": "1.15.0-alpha.3394", - "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.15.0-alpha.3394.tgz", - "integrity": "sha512-f1ZrVHqxQS4C/jaM4ES8JIzVU1zgHNGEMuxXVMT1wbk1NnMt40Uw2fEWYV7Ijy3ttEJ35ekMSSV+3ctunsyq1A==", + "version": "1.15.0-beta.3411", + "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.15.0-beta.3411.tgz", + "integrity": "sha512-e1ddaAfjWXWGyb2tlW8eZHgg6sBHN73n52i8b62GfqSJtf1cIM9VhLA4igq8Anaai5UtcxmmhdAQgmt0xhnNuw==", "requires": { "adm-zip": "^0.4.9", "cryptiles": "^4.1.3", diff --git a/package.json b/package.json index e42509b4fab1..55f6d62ff5cd 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "@nivo/heatmap": "^0.61.0", "@nivo/line": "^0.61.1", "@nivo/pie": "^0.61.1", - "@rocket.chat/apps-engine": "1.15.0-alpha.3394", + "@rocket.chat/apps-engine": "1.15.0-beta.3411", "@rocket.chat/fuselage": "^0.6.3-dev.45", "@rocket.chat/fuselage-hooks": "^0.6.3-dev.35", "@rocket.chat/fuselage-polyfills": "^0.6.3-dev.45", From cb46fb3e3979e57822030af43a084ce29a303e86 Mon Sep 17 00:00:00 2001 From: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Wed, 20 May 2020 21:37:28 -0300 Subject: [PATCH 094/121] [FIX] Remove a non working setting "Notification Duration" (#15737) --- app/api/server/v1/users.js | 1 - .../server/lib/sendNotificationsOnMessage.js | 2 - app/lib/server/startup/settings.js | 5 -- app/models/server/models/Subscriptions.js | 16 ------ .../views/pushNotificationsFlexTab.html | 14 ------ .../client/views/pushNotificationsFlexTab.js | 50 +------------------ .../methods/saveNotificationSettings.js | 12 ----- app/ui-account/client/accountPreferences.html | 10 ---- app/ui-account/client/accountPreferences.js | 9 ---- app/ui/client/lib/notification.js | 2 +- packages/rocketchat-i18n/i18n/en.i18n.json | 3 -- server/methods/saveUserPreferences.js | 1 - server/publications/subscription/index.js | 1 - server/startup/migrations/index.js | 1 + server/startup/migrations/v105.js | 1 - server/startup/migrations/v190.js | 19 +++++++ tests/cypress/integration/11-admin.js | 9 ---- .../pageobjects/administration.page.js | 4 -- tests/data/user.js | 1 - tests/end-to-end/api/00-miscellaneous.js | 1 - tests/end-to-end/api/09-rooms.js | 1 - 21 files changed, 22 insertions(+), 141 deletions(-) create mode 100644 server/startup/migrations/v190.js 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/lib/server/lib/sendNotificationsOnMessage.js b/app/lib/server/lib/sendNotificationsOnMessage.js index 834eac239b0e..f8db60fc1e77 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, 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/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/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}} - -
- -
- {{#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/client/lib/notification.js b/app/ui/client/lib/notification.js index 2ad74e0a7dd1..98dd113ab002 100644 --- a/app/ui/client/lib/notification.js +++ b/app/ui/client/lib/notification.js @@ -44,7 +44,7 @@ export const KonchatNotification = { requireInteraction: getUserPreference(Meteor.userId(), 'desktopNotificationRequireInteraction'), }); - const notificationDuration = notification.duration - 0 || getUserPreference(Meteor.userId(), 'desktopNotificationDuration') - 0; + const notificationDuration = notification.duration - 0 || 10; if (notificationDuration > 0) { setTimeout(() => n.close(), notificationDuration * 1000); } diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index bc44d4921e89..66795f3b3dd2 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1239,7 +1239,6 @@ "Duplicated_Email_address_will_be_ignored": "Duplicated email address will be ignored.", "Duplicate_file_name_found": "Duplicate file name found.", "Duplicate_private_group_name": "A Private Group with name '%s' exists", - "Duration": "Duration", "E2E Encryption": "E2E Encryption", "E2E_Enabled": "E2E Enabled", "E2E_Enable_alert": "This feature is currently in beta! Please report bugs to github.com/RocketChat/Rocket.Chat/issues and be aware of:
- Encrypted messages of encrypted rooms will not be found by search operations.
- The mobile apps may not support the encypted messages (they are implementing it).
- Bots may not be able to see encrypted messages until they implement support for it.
- Uploads will not be encrypted in this version.", @@ -2537,12 +2536,10 @@ "Nothing_found": "Nothing found", "Not_Imported_Messages_Title": "The following messages were not imported successfully", "Notification_Desktop_Default_For": "Show Desktop Notifications For", - "Notification_Duration": "Notification Duration", "Notification_RequireInteraction": "Require Interaction to Dismiss Desktop Notification", "Notification_RequireInteraction_Description": "Works only with Chrome browser versions > 50. Utilizes the parameter requireInteraction to show the desktop notification to indefinite until the user interacts with it.", "Notification_Mobile_Default_For": "Push Mobile Notifications For", "Notifications": "Notifications", - "Notifications_Duration": "Notifications Duration", "Notifications_Max_Room_Members": "Max Room Members Before Disabling All Message Notifications", "Notifications_Max_Room_Members_Description": "Max number of members in room when notifications for all messages gets disabled. Users can still change per room setting to receive all notifications on an individual basis. (0 to disable)", "Notifications_Muted_Description": "If you choose to mute everything, you won't see the room highlight in the list when there are new messages, except for mentions. Muting notifications will override notifications settings.", diff --git a/server/methods/saveUserPreferences.js b/server/methods/saveUserPreferences.js index 6d24205a6666..4feee329219a 100644 --- a/server/methods/saveUserPreferences.js +++ b/server/methods/saveUserPreferences.js @@ -22,7 +22,6 @@ Meteor.methods({ mobileNotifications: Match.Optional(String), enableAutoAway: Match.Optional(Boolean), highlights: Match.Optional([String]), - desktopNotificationDuration: Match.Optional(Number), messageViewMode: Match.Optional(Number), hideUsernames: Match.Optional(Boolean), hideRoles: Match.Optional(Boolean), diff --git a/server/publications/subscription/index.js b/server/publications/subscription/index.js index 8001eb21eeaf..59a080202fdc 100644 --- a/server/publications/subscription/index.js +++ b/server/publications/subscription/index.js @@ -24,7 +24,6 @@ export const fields = { audioNotifications: 1, audioNotificationValue: 1, desktopNotifications: 1, - desktopNotificationDuration: 1, mobilePushNotifications: 1, emailNotifications: 1, unreadAlert: 1, diff --git a/server/startup/migrations/index.js b/server/startup/migrations/index.js index edee93ea383f..c32aef2646df 100644 --- a/server/startup/migrations/index.js +++ b/server/startup/migrations/index.js @@ -186,4 +186,5 @@ import './v186'; import './v187'; import './v188'; import './v189'; +import './v190'; import './xrun'; diff --git a/server/startup/migrations/v105.js b/server/startup/migrations/v105.js index d68b1bdda5e4..39615b89bf79 100644 --- a/server/startup/migrations/v105.js +++ b/server/startup/migrations/v105.js @@ -15,7 +15,6 @@ Migrations.add({ Desktop_Notifications_Default_Alert: 'Accounts_Default_User_Preferences_desktopNotifications', Mobile_Notifications_Default_Alert: 'Accounts_Default_User_Preferences_mobileNotifications', Audio_Notifications_Default_Alert: 'Accounts_Default_User_Preferences_audioNotifications', - Desktop_Notifications_Duration: 'Accounts_Default_User_Preferences_desktopNotificationDuration', Audio_Notifications_Value: undefined, }; Settings.find({ _id: { $in: Object.keys(settingsMap) } }).forEach((oldSetting) => { diff --git a/server/startup/migrations/v190.js b/server/startup/migrations/v190.js new file mode 100644 index 000000000000..8c4da9b206b2 --- /dev/null +++ b/server/startup/migrations/v190.js @@ -0,0 +1,19 @@ +import { Migrations } from '../../../app/migrations'; +import { Settings, Subscriptions } from '../../../app/models/server/raw'; + +Migrations.add({ + version: 190, + up() { + // Remove unused settings + Promise.await(Settings.col.deleteOne({ _id: 'Accounts_Default_User_Preferences_desktopNotificationDuration' })); + Promise.await(Subscriptions.col.updateMany({ + desktopNotificationDuration: { + $exists: true, + }, + }, { + $unset: { + desktopNotificationDuration: 1, + }, + })); + }, +}); diff --git a/tests/cypress/integration/11-admin.js b/tests/cypress/integration/11-admin.js index 333d54514ed1..51fc46af08e1 100644 --- a/tests/cypress/integration/11-admin.js +++ b/tests/cypress/integration/11-admin.js @@ -683,15 +683,6 @@ describe('[Administration]', () => { admin.accountsidleTimeLimit.should('have.value', '300'); }); - it('it should show the notifications durations field', () => { - admin.accountsNotificationDuration.click(); - admin.accountsNotificationDuration.should('be.visible'); - }); - - it('the notification duration field value should be 0', () => { - admin.accountsNotificationDuration.should('have.value', '0'); - }); - it('it should show the audio notifications select field', () => { admin.accountsAudioNotifications.scrollIntoView(); admin.accountsAudioNotifications.should('be.visible'); diff --git a/tests/cypress/pageobjects/administration.page.js b/tests/cypress/pageobjects/administration.page.js index d2d30530be67..b34f3e83f2b2 100644 --- a/tests/cypress/pageobjects/administration.page.js +++ b/tests/cypress/pageobjects/administration.page.js @@ -231,10 +231,6 @@ class Administration extends Page { get accountsidleTimeLimitReset() { return browser.element('[data-qa-reset-setting-id="Accounts_Default_User_Preferences_idleTimeLimit"]'); } - get accountsNotificationDuration() { return browser.element('[data-qa-setting-id="Accounts_Default_User_Preferences_desktopNotificationDuration"]'); } - - get accountsNotificationDurationReset() { return browser.element('[data-qa-reset-setting-id="Accounts_Default_User_Preferences_desktopNotificationDuration"]'); } - get accountsAudioNotifications() { return browser.element('[data-qa-setting-id="Accounts_Default_User_Preferences_audioNotifications"]'); } get accountsAudioNotificationsReset() { return browser.element('[data-qa-reset-setting-id="Accounts_Default_User_Preferences_audioNotifications"]'); } diff --git a/tests/data/user.js b/tests/data/user.js index 02d805b138d8..6fb74b9862f7 100644 --- a/tests/data/user.js +++ b/tests/data/user.js @@ -23,7 +23,6 @@ export const preferences = { mobileNotifications: 'default', enableAutoAway: true, highlights: [], - desktopNotificationDuration: 0, desktopNotificationRequireInteraction: false, messageViewMode: 0, hideUsernames: false, diff --git a/tests/end-to-end/api/00-miscellaneous.js b/tests/end-to-end/api/00-miscellaneous.js index a514b3cd7b7c..9ddb510d9704 100644 --- a/tests/end-to-end/api/00-miscellaneous.js +++ b/tests/end-to-end/api/00-miscellaneous.js @@ -126,7 +126,6 @@ describe('miscellaneous', function() { 'mobileNotifications', 'enableAutoAway', // 'highlights', - 'desktopNotificationDuration', 'desktopNotificationRequireInteraction', 'messageViewMode', 'hideUsernames', diff --git a/tests/end-to-end/api/09-rooms.js b/tests/end-to-end/api/09-rooms.js index 841b9b0a4b88..38fa9a246146 100644 --- a/tests/end-to-end/api/09-rooms.js +++ b/tests/end-to-end/api/09-rooms.js @@ -58,7 +58,6 @@ describe('[Rooms]', function() { emailNotifications: 'nothing', audioNotificationValue: 'beep', desktopNotifications: 'nothing', - desktopNotificationDuration: '2', audioNotifications: 'all', mobilePushNotifications: 'mentions', }, From 6ed0e44722b5c5cdbf4a3dbc7e785423c082f7e6 Mon Sep 17 00:00:00 2001 From: Marcos Spessatto Defendi Date: Wed, 20 May 2020 21:38:40 -0300 Subject: [PATCH 095/121] [IMPROVE] Remove index files from action-links, accounts and assets (#17607) --- app/accounts/index.js | 1 - app/action-links/client/index.js | 3 +- app/action-links/client/init.js | 6 +- app/action-links/client/lib/actionLinks.js | 65 ++++++++++++++----- app/action-links/index.js | 8 --- app/action-links/server/actionLinkHandler.js | 2 +- app/action-links/server/index.js | 2 +- .../{both => server}/lib/actionLinks.js | 2 +- app/api/index.js | 1 - app/api/server/v1/assets.js | 2 +- app/assets/index.js | 1 - app/bigbluebutton/index.js | 1 - app/bigbluebutton/server/index.js | 1 + app/blockstack/server/routes.js | 2 +- app/bot-helpers/index.js | 1 - app/integrations/server/api/api.js | 2 +- app/livechat/client/index.js | 1 + app/livechat/client/lib/messageTypes.js | 5 ++ app/livechat/imports/server/rest/agent.js | 2 +- .../imports/server/rest/appearance.js | 2 +- .../imports/server/rest/dashboards.js | 2 +- .../imports/server/rest/departments.js | 2 +- app/livechat/imports/server/rest/facebook.js | 2 +- app/livechat/imports/server/rest/inquiries.js | 2 +- .../imports/server/rest/integrations.js | 2 +- app/livechat/imports/server/rest/messages.js | 2 +- .../imports/server/rest/officeHour.js | 2 +- app/livechat/imports/server/rest/queue.js | 2 +- app/livechat/imports/server/rest/rooms.js | 2 +- app/livechat/imports/server/rest/sms.js | 2 +- app/livechat/imports/server/rest/triggers.js | 2 +- app/livechat/imports/server/rest/upload.js | 2 +- app/livechat/imports/server/rest/users.js | 2 +- app/livechat/imports/server/rest/visitors.js | 2 +- app/livechat/lib/messageTypes.js | 32 --------- app/livechat/server/api/v1/agent.js | 2 +- app/livechat/server/api/v1/config.js | 2 +- app/livechat/server/api/v1/customField.js | 2 +- app/livechat/server/api/v1/message.js | 2 +- app/livechat/server/api/v1/offlineMessage.js | 2 +- app/livechat/server/api/v1/pageVisited.js | 2 +- app/livechat/server/api/v1/room.js | 2 +- app/livechat/server/api/v1/transcript.js | 2 +- app/livechat/server/api/v1/transfer.js | 2 +- app/livechat/server/api/v1/videoCall.js | 2 +- app/livechat/server/api/v1/visitor.js | 2 +- app/livechat/server/index.js | 1 + app/livechat/server/lib/messageTypes.js | 26 ++++++++ app/livestream/server/routes.js | 2 +- .../server/oauth/oauth2-server.js | 2 +- app/videobridge/client/actionLink.js | 2 +- app/videobridge/server/actionLink.js | 2 +- app/videobridge/server/methods/bbb.js | 4 +- client/importPackages.js | 2 +- .../api-enterprise/server/canned-responses.js | 2 +- .../server/api/channels.js | 2 +- .../server/api/messages.js | 2 +- .../engagement-dashboard/server/api/users.js | 2 +- .../imports/server/rest/departments.js | 2 +- .../livechat/imports/server/rest/inquiries.js | 2 +- ee/app/livechat/imports/server/rest/rooms.js | 2 +- ee/app/livechat/imports/server/rest/sms.js | 2 +- ee/app/livechat/imports/server/rest/upload.js | 2 +- server/importPackages.js | 12 ++-- server/startup/migrations/v036.js | 2 +- server/startup/migrations/v042.js | 2 +- 66 files changed, 143 insertions(+), 124 deletions(-) delete mode 100644 app/accounts/index.js delete mode 100644 app/action-links/index.js rename app/action-links/{both => server}/lib/actionLinks.js (93%) delete mode 100644 app/api/index.js delete mode 100644 app/assets/index.js delete mode 100644 app/bigbluebutton/index.js create mode 100644 app/bigbluebutton/server/index.js delete mode 100644 app/bot-helpers/index.js create mode 100644 app/livechat/client/lib/messageTypes.js create mode 100644 app/livechat/server/lib/messageTypes.js 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 64b3fc4acb4c..b5f218b1ca18 100644 --- a/app/action-links/client/init.js +++ b/app/action-links/client/init.js @@ -1,10 +1,10 @@ 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({ 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/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/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/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/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..c828e9552e58 100644 --- a/app/livechat/imports/server/rest/visitors.js +++ b/app/livechat/imports/server/rest/visitors.js @@ -1,7 +1,7 @@ import { check } from 'meteor/check'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findVisitorInfo, findVisitedPages, findChatHistory } from '../../../server/api/lib/visitors'; API.v1.addRoute('livechat/visitors.info', { authRequired: true }, { 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/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..50e4229fa40e 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', { 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 index e38277d5529d..aa3fb7facd0e 100644 --- a/app/livechat/server/api/v1/transfer.js +++ b/app/livechat/server/api/v1/transfer.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { LivechatRooms } from '../../../../models'; -import { API } from '../../../../api'; +import { API } from '../../../../api/server'; import { findLivechatTransferHistory } from '../lib/transfer'; API.v1.addRoute('livechat/transfer.history/:rid', { authRequired: true }, { 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/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/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/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/videobridge/client/actionLink.js b/app/videobridge/client/actionLink.js index 666059649d32..c1955690ddfb 100644 --- a/app/videobridge/client/actionLink.js +++ b/app/videobridge/client/actionLink.js @@ -2,7 +2,7 @@ import { Session } from 'meteor/session'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import toastr from 'toastr'; -import { actionLinks } from '../../action-links'; +import { actionLinks } from '../../action-links/client'; import { Rooms } from '../../models'; actionLinks.register('joinJitsiCall', function(message, params, instance) { diff --git a/app/videobridge/server/actionLink.js b/app/videobridge/server/actionLink.js index 01d5baa2ff4f..e3e42bee4c33 100644 --- a/app/videobridge/server/actionLink.js +++ b/app/videobridge/server/actionLink.js @@ -1,4 +1,4 @@ -import { actionLinks } from '../../action-links'; +import { actionLinks } from '../../action-links/server'; actionLinks.register('joinJitsiCall', function(/* message, params*/) { diff --git a/app/videobridge/server/methods/bbb.js b/app/videobridge/server/methods/bbb.js index 04b1d6030ed1..b06045271d54 100644 --- a/app/videobridge/server/methods/bbb.js +++ b/app/videobridge/server/methods/bbb.js @@ -2,11 +2,11 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; import xml2js from 'xml2js'; -import BigBlueButtonApi from '../../../bigbluebutton'; +import BigBlueButtonApi from '../../../bigbluebutton/server'; import { settings } from '../../../settings'; import { Rooms, Users } from '../../../models'; import { saveStreamingOptions } from '../../../channel-settings'; -import { API } from '../../../api'; +import { API } from '../../../api/server'; const parser = new xml2js.Parser({ explicitRoot: true, diff --git a/client/importPackages.js b/client/importPackages.js index 183ed71b41fc..7d95bc6f83e7 100644 --- a/client/importPackages.js +++ b/client/importPackages.js @@ -102,7 +102,7 @@ import '../app/notifications'; import '../app/promises/client'; import '../app/ui-utils'; import '../app/ui-cached-collection'; -import '../app/action-links'; +import '../app/action-links/client'; import '../app/reactions/client'; import '../app/livechat/client'; import '../app/meteor-autocomplete/client'; diff --git a/ee/app/api-enterprise/server/canned-responses.js b/ee/app/api-enterprise/server/canned-responses.js index 36b1175406a1..99f41f2709c6 100644 --- a/ee/app/api-enterprise/server/canned-responses.js +++ b/ee/app/api-enterprise/server/canned-responses.js @@ -1,4 +1,4 @@ -import { API } from '../../../../app/api'; +import { API } from '../../../../app/api/server'; import { findAllCannedResponses } from './lib/canned-responses'; API.v1.addRoute('canned-responses.get', { authRequired: true }, { diff --git a/ee/app/engagement-dashboard/server/api/channels.js b/ee/app/engagement-dashboard/server/api/channels.js index 405161d3e0c6..e97dd3995df0 100644 --- a/ee/app/engagement-dashboard/server/api/channels.js +++ b/ee/app/engagement-dashboard/server/api/channels.js @@ -1,6 +1,6 @@ import { check } from 'meteor/check'; -import { API } from '../../../../../app/api'; +import { API } from '../../../../../app/api/server'; import { findAllChannelsWithNumberOfMessages } from '../lib/channels'; import { transformDatesForAPI } from './helpers/date'; diff --git a/ee/app/engagement-dashboard/server/api/messages.js b/ee/app/engagement-dashboard/server/api/messages.js index b51f393e0091..94b6428c011e 100644 --- a/ee/app/engagement-dashboard/server/api/messages.js +++ b/ee/app/engagement-dashboard/server/api/messages.js @@ -1,6 +1,6 @@ import { check } from 'meteor/check'; -import { API } from '../../../../../app/api'; +import { API } from '../../../../../app/api/server'; import { findWeeklyMessagesSentData, findMessagesSentOrigin, findTopFivePopularChannelsByMessageSentQuantity } from '../lib/messages'; import { transformDatesForAPI } from './helpers/date'; diff --git a/ee/app/engagement-dashboard/server/api/users.js b/ee/app/engagement-dashboard/server/api/users.js index aaf33b4c0f28..8a675146237a 100644 --- a/ee/app/engagement-dashboard/server/api/users.js +++ b/ee/app/engagement-dashboard/server/api/users.js @@ -1,6 +1,6 @@ import { check } from 'meteor/check'; -import { API } from '../../../../../app/api'; +import { API } from '../../../../../app/api/server'; import { findWeeklyUsersRegisteredData, findActiveUsersMonthlyData, diff --git a/ee/app/livechat/imports/server/rest/departments.js b/ee/app/livechat/imports/server/rest/departments.js index 4ec594c9051e..1521dc7656a9 100644 --- a/ee/app/livechat/imports/server/rest/departments.js +++ b/ee/app/livechat/imports/server/rest/departments.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/inquiries.js b/ee/app/livechat/imports/server/rest/inquiries.js index 3c9e6c314414..cab34ca9b355 100644 --- a/ee/app/livechat/imports/server/rest/inquiries.js +++ b/ee/app/livechat/imports/server/rest/inquiries.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/rooms.js b/ee/app/livechat/imports/server/rest/rooms.js index 7d15bf85e2f9..76578268b288 100644 --- a/ee/app/livechat/imports/server/rest/rooms.js +++ b/ee/app/livechat/imports/server/rest/rooms.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/sms.js b/ee/app/livechat/imports/server/rest/sms.js index 982ddd7b2438..211f5e444a40 100644 --- a/ee/app/livechat/imports/server/rest/sms.js +++ b/ee/app/livechat/imports/server/rest/sms.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/ee/app/livechat/imports/server/rest/upload.js b/ee/app/livechat/imports/server/rest/upload.js index 8f87096af200..e8c4ad4e81ba 100644 --- a/ee/app/livechat/imports/server/rest/upload.js +++ b/ee/app/livechat/imports/server/rest/upload.js @@ -1,4 +1,4 @@ -import { API } from '../../../../../../app/api'; +import { API } from '../../../../../../app/api/server'; import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser'; const endpointsToRunAsUser = { diff --git a/server/importPackages.js b/server/importPackages.js index 96073ce4fa3f..aa56233f2bf5 100644 --- a/server/importPackages.js +++ b/server/importPackages.js @@ -1,14 +1,14 @@ import '../app/cors/server'; import '../app/sms'; import '../app/2fa/server'; -import '../app/accounts'; +import '../app/accounts/server'; import '../app/analytics/server'; -import '../app/api'; -import '../app/assets'; +import '../app/api/server'; +import '../app/assets/server'; import '../app/authorization'; import '../app/autolinker/server'; import '../app/autotranslate/server'; -import '../app/bot-helpers'; +import '../app/bot-helpers/server'; import '../app/cas/server'; import '../app/channel-settings'; import '../app/channel-settings-mail-messages/server'; @@ -98,7 +98,7 @@ import '../app/version-check/server'; import '../app/search/server'; import '../app/chatpal-search/server'; import '../app/discussion/server'; -import '../app/bigbluebutton'; +import '../app/bigbluebutton/server'; import '../app/mail-messages/server'; import '../app/user-status'; import '../app/utils'; @@ -109,6 +109,6 @@ import '../app/callbacks'; import '../app/notifications'; import '../app/promises/server'; import '../app/ui-utils'; -import '../app/action-links'; +import '../app/action-links/server'; import '../app/reactions/server'; import '../app/livechat/server'; diff --git a/server/startup/migrations/v036.js b/server/startup/migrations/v036.js index a4f97e7bf80f..ba22dbdfa8db 100644 --- a/server/startup/migrations/v036.js +++ b/server/startup/migrations/v036.js @@ -5,7 +5,7 @@ import { HTTP } from 'meteor/http'; import { Migrations } from '../../../app/migrations'; import { Settings } from '../../../app/models'; -import { RocketChatAssets } from '../../../app/assets'; +import { RocketChatAssets } from '../../../app/assets/server'; Migrations.add({ version: 36, diff --git a/server/startup/migrations/v042.js b/server/startup/migrations/v042.js index dc95a49cbae2..d17a90026692 100644 --- a/server/startup/migrations/v042.js +++ b/server/startup/migrations/v042.js @@ -2,7 +2,7 @@ import { Mongo } from 'meteor/mongo'; import { Migrations } from '../../../app/migrations'; import { settings } from '../../../app/settings'; -import { RocketChatAssets } from '../../../app/assets'; +import { RocketChatAssets } from '../../../app/assets/server'; Migrations.add({ version: 42, From 5a6a084ccfb41a95a5c6e1280141846e45af9509 Mon Sep 17 00:00:00 2001 From: Maria Eduarda Cunha <42151808+mariaeduardacunha@users.noreply.github.com> Date: Wed, 20 May 2020 23:29:20 -0300 Subject: [PATCH 096/121] Regression: Status presence color (#17707) Co-authored-by: Guilherme Gazzo --- .../client/imports/components/header.css | 10 +++--- .../imports/components/main-content.css | 8 ++--- .../client/imports/components/memberlist.css | 8 ++--- .../client/imports/components/popover.css | 8 ++--- .../components/sidebar/sidebar-header.css | 18 +++++------ .../components/sidebar/sidebar-item.css | 14 ++++---- .../client/imports/general/theme_old.css | 32 +++++++++---------- .../client/imports/general/variables.css | 17 +++++----- .../client/models/CachedCollection.js | 2 +- app/ui/client/views/app/editStatus.css | 16 +++++----- server/startup/migrations/index.js | 1 + server/startup/migrations/v191.js | 9 ++++++ 12 files changed, 77 insertions(+), 66 deletions(-) create mode 100644 server/startup/migrations/v191.js 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/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/general/theme_old.css b/app/theme/client/imports/general/theme_old.css index 657ebb4f3c41..2f88a849dfef 100644 --- a/app/theme/client/imports/general/theme_old.css +++ b/app/theme/client/imports/general/theme_old.css @@ -411,19 +411,19 @@ 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); + border-color: var(--rc-status-online-darken-10); + background-color: var(--rc-status-online); } .account-box .status-offline .thumb::after, @@ -432,11 +432,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 +444,38 @@ 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); + border-color: var(--rc-status-away-darken-10); + 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); + border-color: var(--rc-status-busy-darken-10); + 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); + border-color: var(--rc-status-offline-darken-10); + 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/ui-cached-collection/client/models/CachedCollection.js b/app/ui-cached-collection/client/models/CachedCollection.js index bcd1ffc5b6bb..26dece4ccc93 100644 --- a/app/ui-cached-collection/client/models/CachedCollection.js +++ b/app/ui-cached-collection/client/models/CachedCollection.js @@ -129,7 +129,7 @@ export class CachedCollection extends EventEmitter { userRelated = true, listenChangesForLoggedUsersOnly = false, useSync = true, - version = 10, + version = 11, maxCacheTime = 60 * 60 * 24 * 30, onSyncData = (/* action, record */) => {}, }) { diff --git a/app/ui/client/views/app/editStatus.css b/app/ui/client/views/app/editStatus.css index ab2170f62c88..a7b92dc5d7a8 100644 --- a/app/ui/client/views/app/editStatus.css +++ b/app/ui/client/views/app/editStatus.css @@ -1,19 +1,19 @@ .edit-status-type.rc-popover { &__item { &--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); } } } @@ -21,25 +21,25 @@ .edit-status-type-icon { &--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/server/startup/migrations/index.js b/server/startup/migrations/index.js index c32aef2646df..d4353ab9d79c 100644 --- a/server/startup/migrations/index.js +++ b/server/startup/migrations/index.js @@ -187,4 +187,5 @@ import './v187'; import './v188'; import './v189'; import './v190'; +import './v191'; import './xrun'; diff --git a/server/startup/migrations/v191.js b/server/startup/migrations/v191.js new file mode 100644 index 000000000000..10f5af6a62ce --- /dev/null +++ b/server/startup/migrations/v191.js @@ -0,0 +1,9 @@ +import { Migrations } from '../../../app/migrations/server'; +import { Settings } from '../../../app/models/server'; + +Migrations.add({ + version: 191, + up() { + Settings.remove({ _id: /theme-color-status/ }, { multi: true }); + }, +}); From 9cde69f555156297826f9ff751884b3a8cd31c49 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Thu, 21 May 2020 00:45:09 -0300 Subject: [PATCH 097/121] [NEW][ENTERPRISE] Omnichannel Last-Chatted Agent Preferred option (#17666) --- app/livechat/server/lib/Livechat.js | 5 +- app/livechat/server/lib/RoutingManager.js | 2 +- app/models/server/models/LivechatInquiry.js | 16 +++- app/models/server/models/LivechatRooms.js | 14 +++ app/models/server/models/LivechatVisitors.js | 14 +++ app/models/server/models/Rooms.js | 3 - app/models/server/models/Users.js | 4 +- .../server/hooks/beforeRoutingChat.js | 4 +- .../hooks/checkAgentBeforeTakeInquiry.js | 3 + .../handleLastChattedAgentPreferredEvents.js | 96 +++++++++++++++++++ ee/app/livechat-enterprise/server/index.js | 1 + .../livechat-enterprise/server/lib/Helper.js | 3 +- ee/app/livechat-enterprise/server/settings.js | 7 ++ ee/i18n/en.i18n.json | 2 + ee/i18n/pt-BR.i18n.json | 2 + 15 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 ee/app/livechat-enterprise/server/hooks/handleLastChattedAgentPreferredEvents.js 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/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..49823c11f896 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 = {}; 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/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/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js b/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js index dd5cefce1bfa..4a9701648b91 100644 --- a/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js +++ b/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js @@ -3,7 +3,7 @@ import { settings } from '../../../../../app/settings'; import { LivechatInquiry } from '../../../../../app/models/server'; import { dispatchInquiryPosition, checkWaitingQueue } from '../lib/Helper'; -callbacks.add('livechat.beforeRouteChat', async (inquiry) => { +callbacks.add('livechat.beforeRouteChat', async (inquiry, agent) => { if (!settings.get('Livechat_waiting_queue')) { return inquiry; } @@ -18,7 +18,7 @@ callbacks.add('livechat.beforeRouteChat', async (inquiry) => { return inquiry; } - LivechatInquiry.queueInquiry(_id); + LivechatInquiry.queueInquiry(_id, agent); const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ _id }); if (inq) { diff --git a/ee/app/livechat-enterprise/server/hooks/checkAgentBeforeTakeInquiry.js b/ee/app/livechat-enterprise/server/hooks/checkAgentBeforeTakeInquiry.js index 4cbdd304f043..513f07d8a3e9 100644 --- a/ee/app/livechat-enterprise/server/hooks/checkAgentBeforeTakeInquiry.js +++ b/ee/app/livechat-enterprise/server/hooks/checkAgentBeforeTakeInquiry.js @@ -34,9 +34,12 @@ callbacks.add('livechat.checkAgentBeforeTakeInquiry', async (agent, inquiry) => const { queueInfo: { chats = 0 } = {} } = user; if (maxNumberSimultaneousChat <= chats) { + callbacks.run('livechat.onMaxNumberSimultaneousChatsReached', inquiry, agent); + if (!RoutingManager.getConfig().autoAssignAgent) { throw new Meteor.Error('error-max-number-simultaneous-chats-reached', 'Not allowed'); } + return null; } return agent; diff --git a/ee/app/livechat-enterprise/server/hooks/handleLastChattedAgentPreferredEvents.js b/ee/app/livechat-enterprise/server/hooks/handleLastChattedAgentPreferredEvents.js new file mode 100644 index 000000000000..9bdc2bc8de66 --- /dev/null +++ b/ee/app/livechat-enterprise/server/hooks/handleLastChattedAgentPreferredEvents.js @@ -0,0 +1,96 @@ +import { callbacks } from '../../../../../app/callbacks/server'; +import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; +import { settings } from '../../../../../app/settings/server'; +import { LivechatRooms, LivechatInquiry, LivechatVisitors, Users } from '../../../../../app/models/server'; +import { checkWaitingQueue } from '../lib/Helper'; + +const normalizeDefaultAgent = (agent) => { + if (!agent) { + return; + } + + const { _id: agentId, username } = agent; + return { agentId, username }; +}; + +const checkDefaultAgentOnNewRoom = (defaultAgent, defaultGuest) => { + if (defaultAgent || !defaultGuest) { + return defaultAgent; + } + + if (!RoutingManager.getConfig().autoAssignAgent) { + return defaultAgent; + } + + const { _id: guestId } = defaultGuest; + const guest = LivechatVisitors.findOneById(guestId, { fields: { lastAgent: 1, token: 1 } }); + const { lastAgent: { username: usernameByVisitor } = {}, token } = guest; + + const lastGuestAgent = usernameByVisitor && normalizeDefaultAgent(Users.findOneOnlineAgentByUsername(usernameByVisitor, { fields: { _id: 1, username: 1 } })); + if (lastGuestAgent) { + return lastGuestAgent; + } + + const room = LivechatRooms.findOneLastServedAndClosedByVisitorToken(token, { fields: { servedBy: 1 } }); + if (!room || !room.servedBy) { + return defaultAgent; + } + + const { servedBy: { username: usernameByRoom } } = room; + const lastRoomAgent = normalizeDefaultAgent(Users.findOneOnlineAgentByUsername(usernameByRoom, { fields: { _id: 1, username: 1 } })); + return lastRoomAgent || defaultAgent; +}; + +const onMaxNumberSimultaneousChatsReached = (inquiry, agent) => { + if (!inquiry || !inquiry.defaultAgent) { + return inquiry; + } + + if (!RoutingManager.getConfig().autoAssignAgent) { + return inquiry; + } + + const { _id, defaultAgent, department } = inquiry; + + LivechatInquiry.removeDefaultAgentById(_id); + + const { _id: defaultAgentId } = defaultAgent; + const { agentId } = agent; + + if (defaultAgentId === agentId) { + checkWaitingQueue(department); + } + + return LivechatInquiry.findOneById(_id); +}; + +const afterTakeInquiry = (inquiry, agent) => { + if (!inquiry || !agent) { + return inquiry; + } + + if (!RoutingManager.getConfig().autoAssignAgent) { + return inquiry; + } + + const { v: { token } = {} } = inquiry; + if (!token) { + return inquiry; + } + + LivechatVisitors.updateLastAgentByToken(token, { ...agent, ts: new Date() }); + + return inquiry; +}; +settings.get('Livechat_last_chatted_agent_routing', function(key, value) { + if (!value) { + callbacks.remove('livechat.checkDefaultAgentOnNewRoom', 'livechat-check-default-agent-new-room'); + callbacks.remove('livechat.onMaxNumberSimultaneousChatsReached', 'livechat-on-max-number-simultaneous-chats-reached'); + callbacks.remove('livechat.afterTakeInquiry', 'livechat-save-default-agent-after-take-inquiry'); + return; + } + + callbacks.add('livechat.checkDefaultAgentOnNewRoom', checkDefaultAgentOnNewRoom, callbacks.priority.MEDIUM, 'livechat-check-default-agent-new-room'); + callbacks.add('livechat.onMaxNumberSimultaneousChatsReached', onMaxNumberSimultaneousChatsReached, callbacks.priority.MEDIUM, 'livechat-on-max-number-simultaneous-chats-reached'); + callbacks.add('livechat.afterTakeInquiry', afterTakeInquiry, callbacks.priority.MEDIUM, 'livechat-save-default-agent-after-take-inquiry'); +}); diff --git a/ee/app/livechat-enterprise/server/index.js b/ee/app/livechat-enterprise/server/index.js index 63f36630a18c..f18d76223f40 100644 --- a/ee/app/livechat-enterprise/server/index.js +++ b/ee/app/livechat-enterprise/server/index.js @@ -27,6 +27,7 @@ import './hooks/beforeNewInquiry'; import './hooks/beforeNewRoom'; import './hooks/beforeRoutingChat'; import './hooks/checkAgentBeforeTakeInquiry'; +import './hooks/handleLastChattedAgentPreferredEvents'; import './hooks/onCheckRoomParamsApi'; import './hooks/onLoadConfigApi'; import './hooks/onSetUserStatusLivechat'; diff --git a/ee/app/livechat-enterprise/server/lib/Helper.js b/ee/app/livechat-enterprise/server/lib/Helper.js index d3c525e85066..dd3318c8e678 100644 --- a/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/ee/app/livechat-enterprise/server/lib/Helper.js @@ -100,7 +100,8 @@ const processWaitingQueue = async (department) => { return; } - const room = await RoutingManager.delegateInquiry(inquiry); + const { defaultAgent } = inquiry; + const room = await RoutingManager.delegateInquiry(inquiry, defaultAgent); const propagateAgentDelegated = Meteor.bindEnvironment((rid, agentId) => { dispatchAgentDelegated(rid, agentId); diff --git a/ee/app/livechat-enterprise/server/settings.js b/ee/app/livechat-enterprise/server/settings.js index 8d91cb92f6c2..118fe5967a54 100644 --- a/ee/app/livechat-enterprise/server/settings.js +++ b/ee/app/livechat-enterprise/server/settings.js @@ -51,5 +51,12 @@ export const createSettings = () => { enableQuery: { _id: 'Livechat_auto_close_abandoned_rooms', value: true }, }); + settings.add('Livechat_last_chatted_agent_routing', false, { + type: 'boolean', + group: 'Omnichannel', + section: 'Routing', + enableQuery: { _id: 'Livechat_Routing_Method', value: { $ne: 'Manual_Selection' } }, + }); + Settings.addOptionValueById('Livechat_Routing_Method', { key: 'Load_Balancing', i18nLabel: 'Load_Balancing' }); }; diff --git a/ee/i18n/en.i18n.json b/ee/i18n/en.i18n.json index 09f587ee2297..0c44d3332954 100644 --- a/ee/i18n/en.i18n.json +++ b/ee/i18n/en.i18n.json @@ -34,6 +34,8 @@ "List_of_departments_for_forward": "List of departments allowed for forwarding (Optional)", "List_of_departments_for_forward_description": "Allow to set a restricted list of departments that can receive chats from this department", "Livechat_abandoned_rooms_closed_custom_message": "Custom message when room is automatically closed by visitor inactivity", + "Livechat_last_chatted_agent_routing": "Last-Chatted Agent Preferred", + "Livechat_last_chatted_agent_routing_Description": "The Last-Chatted Agent setting allocates chats to the agent who previously interacted with the same visitor if the agent is available when the chat starts.", "Livechat_Monitors": "Monitors", "Livechat_monitors": "Livechat monitors", "Max_number_of_chats_per_agent": "Max. number of simultaneous chats", diff --git a/ee/i18n/pt-BR.i18n.json b/ee/i18n/pt-BR.i18n.json index 892daf29eed0..e7ebff498eae 100644 --- a/ee/i18n/pt-BR.i18n.json +++ b/ee/i18n/pt-BR.i18n.json @@ -26,6 +26,8 @@ "List_of_departments_for_forward": "Lista de departamentos permitidos para o encaminhamento(Opcional).", "List_of_departments_for_forward_description": "Permite definir uma lista restrita de departamentos que podem receber conversas desse departamento.", "Livechat_abandoned_rooms_closed_custom_message": "Mensagem customizada para usar quando a sala for automaticamente fechada por abandono do visitante", + "Livechat_last_chatted_agent_routing": "Agente preferido pela última conversa", + "Livechat_last_chatted_agent_routing_Description": "Agente preferido pela última conversa aloca bate-papos para o agente que interagiu anteriormente com o mesmo visitante, caso o agente esteja disponível quando o bate-papo for iniciado.", "Livechat_Monitors": "Monitores", "Livechat_monitors": "Monitores de Livechat", "Max_number_of_chats_per_agent": "Número máximo de atendimentos simultâneos", From bf5af0da0bae06212e91e8b4b1ac9bc509d09bc9 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 21 May 2020 00:48:01 -0300 Subject: [PATCH 098/121] Update Fuselage version (#17708) --- package-lock.json | 56 +++++++++++++++++++++++------------------------ package.json | 8 +++---- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ec9af78bac7..e43bcec27d6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2818,9 +2818,9 @@ } }, "@rocket.chat/css-in-js": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.8.0.tgz", - "integrity": "sha512-G6RlTlzUwJR/ske8PjY1CsE2ktnA7YOkErkEBp5+FpXMGxCpNb8zqLqx3jrxJqyRKwXIBnX/6PO7dWPFMHTPpw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/css-in-js/-/css-in-js-0.9.0.tgz", + "integrity": "sha512-CK7WAPAG+Vi8yX73cPIGNU5j/zRMbbXqKYrz7qrAY66qXlsFtz+K1Cj2ntownzi9R1wz4wxHmM89/6wN23WKAg==", "requires": { "@emotion/hash": "^0.8.0", "@emotion/stylis": "^0.8.5" @@ -2836,32 +2836,32 @@ } }, "@rocket.chat/fuselage": { - "version": "0.6.3-dev.45", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.6.3-dev.45.tgz", - "integrity": "sha512-ZcIYh29PQdSMLEo35D1RgGkRLZ5dIbZcBadJ5aN/QYCBx666i5CSIVOMNlbST1nEN8AKk79FWlBjNhMY8npLtw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage/-/fuselage-0.9.0.tgz", + "integrity": "sha512-BRmKElhuf0NwOz3vJdDIwOoGPYm0FZF978ve9kYidCJQevPVY4Th9ptOfD3YCtZjWPWGVqqpbtwbocabPQjurw==", "requires": { - "@rocket.chat/css-in-js": "^0.8.0", - "@rocket.chat/fuselage-tokens": "^0.8.0", - "@rocket.chat/icons": "^0.8.0", + "@rocket.chat/css-in-js": "^0.9.0", + "@rocket.chat/fuselage-tokens": "^0.9.0", + "@rocket.chat/icons": "^0.9.0", "invariant": "^2.2.4" }, "dependencies": { "@rocket.chat/icons": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@rocket.chat/icons/-/icons-0.8.0.tgz", - "integrity": "sha512-5GGgIeIwK3QdtsalNuhtrzGr2iYDbkYnhjHaABo2j6hENB+xIn1p3AkkrKaYQXLqGvIlzwuTMyTGn9b49HzNPg==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/icons/-/icons-0.9.0.tgz", + "integrity": "sha512-Yfzz19+LoD6OpP57Y7W6E4ctiBP1RfG1LvTC+9/FNPpvWRnlQDySZHpUVn+TGrfuvBQ5YH5XqeHIKWZw+YiWEg==" } } }, "@rocket.chat/fuselage-hooks": { - "version": "0.6.3-dev.35", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.6.3-dev.35.tgz", - "integrity": "sha512-hvnNmiDBYhFGa1rvzuZAuPm1gI79YdJAIy9PZDQ77+deYzTpcR8aTCYsTa4p3hHc3RwflmqrXHDbRAjTiHR/1Q==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-hooks/-/fuselage-hooks-0.9.0.tgz", + "integrity": "sha512-15uw1Z63Q8XLr6Or4YoJdReyYozbp6NFYTIDSe3Dz44gp5oDmXzS+Uwmn2iQPxF3qEbxPEMnt/CcVux/Pv3MNw==" }, "@rocket.chat/fuselage-polyfills": { - "version": "0.6.3-dev.45", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-polyfills/-/fuselage-polyfills-0.6.3-dev.45.tgz", - "integrity": "sha512-h+S+FsfeO6VQVSq98SM1flHdM0Zffeqj790RiZdQzOUHEYbCds+yFYGFIrbS/4UnGnn2rgxpqeuDUtFWT7TRaw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-polyfills/-/fuselage-polyfills-0.9.0.tgz", + "integrity": "sha512-or8zQQAZg9bXWpQD9074PuqtiC1Xoy41Z3syS8ABU0NeDS4llJ2EScSt/ksZz+2y7uh3c3MxrZIZLUAMrbup0g==", "requires": { "@juggle/resize-observer": "^3.1.2", "clipboard-polyfill": "^2.8.6", @@ -2869,22 +2869,22 @@ } }, "@rocket.chat/fuselage-tokens": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.8.0.tgz", - "integrity": "sha512-TVPEckSbzHr+Ix3h4OuEd5OyDVAVnviSQRqwYzAbfRvb4B9riZwqUlvdNKgRofy1og5ovRMZUL9CqduGbsdhsA==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-tokens/-/fuselage-tokens-0.9.0.tgz", + "integrity": "sha512-ZiCIDtNsPj7icJLr9iK/Dn0vN+mrUy/blQlrFn0pKznET2ARPAvjWPsAAk/JJu3rlCnD7u4Fd15oI6BJllh+4A==" }, "@rocket.chat/fuselage-ui-kit": { - "version": "0.6.3-dev.39", - "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-ui-kit/-/fuselage-ui-kit-0.6.3-dev.39.tgz", - "integrity": "sha512-ll9lYOM2mEAsKtHiPdxDwVO2YGkarZMCLl7UK6yT71BFVuDY7hJXm14+aj+JBTKc9B2J0CFIo+MfO1cDuqYc+A==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/fuselage-ui-kit/-/fuselage-ui-kit-0.9.0.tgz", + "integrity": "sha512-rVp+gaR3L9tJEBx5onjajPi9aNw8cdqUBtuRxgTwpHUPGxZVxadgJo9OXvFGfB0bt8d9DcXMW+ouRJp+LRfLtg==", "requires": { - "@rocket.chat/ui-kit": "^0.8.0" + "@rocket.chat/ui-kit": "^0.9.0" }, "dependencies": { "@rocket.chat/ui-kit": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@rocket.chat/ui-kit/-/ui-kit-0.8.0.tgz", - "integrity": "sha512-JNpwGcVQfBHjfG/sLJmMwau0BNvUWx210UAji4MnARAvfxBUslUYWzTRDkvxryKX0223dbTC91HOMt1HZDNNLQ==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/ui-kit/-/ui-kit-0.9.0.tgz", + "integrity": "sha512-BBqLT1vbZjUzG4xzUMo56O0EsnH0sLNaESmBy0YYIAzBYkpxwQcVLY0XWq3w7gcLYEUhFvLVvKCis5zPHkZrqQ==" } } }, diff --git a/package.json b/package.json index 55f6d62ff5cd..99750b95786a 100644 --- a/package.json +++ b/package.json @@ -126,10 +126,10 @@ "@nivo/line": "^0.61.1", "@nivo/pie": "^0.61.1", "@rocket.chat/apps-engine": "1.15.0-beta.3411", - "@rocket.chat/fuselage": "^0.6.3-dev.45", - "@rocket.chat/fuselage-hooks": "^0.6.3-dev.35", - "@rocket.chat/fuselage-polyfills": "^0.6.3-dev.45", - "@rocket.chat/fuselage-ui-kit": "^0.6.3-dev.39", + "@rocket.chat/fuselage": "^0.9.0", + "@rocket.chat/fuselage-hooks": "^0.9.0", + "@rocket.chat/fuselage-polyfills": "^0.9.0", + "@rocket.chat/fuselage-ui-kit": "^0.9.0", "@rocket.chat/icons": "^0.6.3-dev.23", "@rocket.chat/ui-kit": "^0.6.3-dev.23", "@slack/client": "^4.8.0", From 4aed76deba9bad4cc6b33eaff98e0749caf72af3 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Thu, 21 May 2020 01:20:44 -0300 Subject: [PATCH 099/121] [NEW][ENTERPRISE] Support for custom Livechat registration form fields (#17581) Co-authored-by: Guilherme Gazzo --- .../views/app/livechatCustomFieldForm.html | 15 ++++--- .../views/app/livechatCustomFieldForm.js | 29 +++++++++++++ .../client/views/app/tabbar/visitorEdit.html | 23 ++-------- .../client/views/app/tabbar/visitorEdit.js | 34 ++++++--------- .../app/tabbar/visitorEditCustomField.html | 22 ++++++++++ .../app/tabbar/visitorEditCustomField.js | 17 ++++++++ app/livechat/client/views/regular.js | 1 + app/livechat/server/api/lib/livechat.js | 2 +- app/livechat/server/api/v1/config.js | 5 ++- .../server/methods/saveCustomField.js | 6 ++- .../livechatCustomFieldsAdditionalForm.html | 42 ++++++++++++++++++ .../livechatCustomFieldsAdditionalForm.js | 37 ++++++++++++++++ .../views/app/registerCustomTemplates.js | 4 +- .../livechat-enterprise/server/hooks/index.js | 17 ++++++++ .../server/hooks/onLoadConfigApi.js | 39 +++++------------ ee/app/livechat-enterprise/server/index.js | 8 +--- .../livechat-enterprise/server/lib/Helper.js | 43 ++++++++++++++++++- ee/i18n/en.i18n.json | 3 ++ ee/i18n/pt-BR.i18n.json | 3 ++ packages/rocketchat-i18n/i18n/en.i18n.json | 4 +- packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 2 + packages/rocketchat-i18n/i18n/pt.i18n.json | 1 + 22 files changed, 266 insertions(+), 91 deletions(-) create mode 100644 app/livechat/client/views/app/tabbar/visitorEditCustomField.html create mode 100644 app/livechat/client/views/app/tabbar/visitorEditCustomField.js create mode 100644 ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.html create mode 100644 ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.js create mode 100644 ee/app/livechat-enterprise/server/hooks/index.js 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/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/v1/config.js b/app/livechat/server/api/v1/config.js index 50e4229fa40e..85ef7a10a865 100644 --- a/app/livechat/server/api/v1/config.js +++ b/app/livechat/server/api/v1/config.js @@ -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/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/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.html b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.html new file mode 100644 index 000000000000..e25cb8f98f8f --- /dev/null +++ b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.html @@ -0,0 +1,42 @@ + diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.js b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.js new file mode 100644 index 000000000000..2632bd883440 --- /dev/null +++ b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatCustomFieldsAdditionalForm.js @@ -0,0 +1,37 @@ +import { ReactiveVar } from 'meteor/reactive-var'; +import { Template } from 'meteor/templating'; +import toastr from 'toastr'; + +import './livechatCustomFieldsAdditionalForm.html'; +import { t } from '../../../../../../../app/utils/client'; + +Template.livechatCustomFieldsAdditionalForm.helpers({ + customField() { + return Template.instance().customField.get(); + }, +}); + +Template.livechatCustomFieldsAdditionalForm.onCreated(function() { + this.customField = new ReactiveVar({}); + + this.autorun(() => { + // To make this template reactive we expect a ReactiveVar through the data property, + // because the parent form may not be rerender, only the dynamic template data + this.customField.set({ ...this.data.get() }); + }); +}); + +Template.livechatCustomFieldsAdditionalForm.events({ + 'change .additional-field'(e, instance) { + const { target: { name, value } } = e; + instance.customField.set({ ...instance.customField.get(), [name]: value }); + }, + + 'blur [name="options"]'(e) { + const { currentTarget: { value } } = e; + if (value.trim() !== '' && !/^([a-zA-Z0-9-_ ]+)(,\s*[a-zA-Z0-9-_ ]+)*$/i.test(value)) { + toastr.error(t('error-invalid-value')); + e.currentTarget.focus(); + } + }, +}); diff --git a/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js b/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js index de436419e742..6a655f1b1ae4 100644 --- a/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js +++ b/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js @@ -1,12 +1,14 @@ import { addCustomFormTemplate } from '../../../../../../app/livechat/client/views/app/customTemplates/register'; +import './customTemplates/livechatCustomFieldsAdditionalForm'; import './customTemplates/livechatDepartmentCustomFieldsForm'; import './customTemplates/livechatAgentEditCustomFieldsForm'; import './customTemplates/livechatAgentInfoCustomFieldsForm'; import './customTemplates/visitorEditCustomFieldsForm'; import './customTemplates/visitorInfoCustomForm'; -addCustomFormTemplate('livechatDepartmentForm', 'livechatDepartmentCustomFieldsForm'); addCustomFormTemplate('livechatAgentEditForm', 'livechatAgentEditCustomFieldsForm'); addCustomFormTemplate('livechatAgentInfoForm', 'livechatAgentInfoCustomFieldsForm'); +addCustomFormTemplate('livechatCustomFieldsAdditionalForm', 'livechatCustomFieldsAdditionalForm'); +addCustomFormTemplate('livechatDepartmentForm', 'livechatDepartmentCustomFieldsForm'); addCustomFormTemplate('livechatVisitorEditForm', 'visitorEditCustomFieldsForm'); addCustomFormTemplate('livechatVisitorInfo', 'visitorInfoCustomForm'); diff --git a/ee/app/livechat-enterprise/server/hooks/index.js b/ee/app/livechat-enterprise/server/hooks/index.js new file mode 100644 index 000000000000..4877aa007de0 --- /dev/null +++ b/ee/app/livechat-enterprise/server/hooks/index.js @@ -0,0 +1,17 @@ +import './addDepartmentAncestors'; +import './afterForwardChatToDepartment'; +import './beforeListTags'; +import './setPredictedVisitorAbandonmentTime'; +import './beforeForwardRoomToDepartment'; +import './afterRemoveDepartment'; +import './onLoadForwardDepartmentRestrictions'; +import './afterTakeInquiry'; +import './beforeNewInquiry'; +import './beforeNewRoom'; +import './beforeRoutingChat'; +import './checkAgentBeforeTakeInquiry'; +import './onCheckRoomParamsApi'; +import './onLoadConfigApi'; +import './onSetUserStatusLivechat'; +import './onCloseLivechat'; +import './onSaveVisitorInfo'; diff --git a/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js b/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js index b05a0a2b5a6b..7bf3f416c6ce 100644 --- a/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js +++ b/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js @@ -1,35 +1,16 @@ import { callbacks } from '../../../../../app/callbacks'; -import { settings } from '../../../../../app/settings'; -import { LivechatInquiry } from '../../../../../app/models/server'; -import { normalizeQueueInfo } from '../lib/Helper'; +import { getLivechatQueueInfo, getLivechatCustomFields } from '../lib/Helper'; -callbacks.add('livechat.onLoadConfigApi', async (room) => { - if (!room) { - return null; - } +callbacks.add('livechat.onLoadConfigApi', async (options = {}) => { + const { room } = options; - if (!settings.get('Livechat_waiting_queue')) { - return null; - } + const queueInfo = await getLivechatQueueInfo(room); + const customFields = getLivechatCustomFields(); - const { _id: rid } = room; - const inquiry = LivechatInquiry.findOneByRoomId(rid); - if (!inquiry) { - return null; - } + const config = { + ...queueInfo && { queueInfo }, + ...customFields && { customFields }, + }; - const { _id, status } = inquiry; - if (status !== 'queued') { - return null; - } - - const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ _id }); - - let queueInfo; - if (inq) { - const { position, department } = inq; - queueInfo = await normalizeQueueInfo({ position, department }); - } - - return { queueInfo }; + return Object.assign({ config }, options); }, callbacks.priority.MEDIUM, 'livechat-on-load-config-api'); diff --git a/ee/app/livechat-enterprise/server/index.js b/ee/app/livechat-enterprise/server/index.js index f18d76223f40..890418ef567c 100644 --- a/ee/app/livechat-enterprise/server/index.js +++ b/ee/app/livechat-enterprise/server/index.js @@ -1,13 +1,6 @@ import { Meteor } from 'meteor/meteor'; import '../lib/messageTypes'; -import './hooks/addDepartmentAncestors'; -import './hooks/afterForwardChatToDepartment'; -import './hooks/beforeListTags'; -import './hooks/setPredictedVisitorAbandonmentTime'; -import './hooks/beforeForwardRoomToDepartment'; -import './hooks/afterRemoveDepartment'; -import './hooks/onLoadForwardDepartmentRestrictions'; import './methods/addMonitor'; import './methods/getUnitsFromUserRoles'; import './methods/removeMonitor'; @@ -38,6 +31,7 @@ import { onLicense } from '../../license/server'; onLicense('livechat-enterprise', () => { require('./api'); + require('./hooks'); const { createPermissions } = require('./permissions'); const { createSettings } = require('./settings'); diff --git a/ee/app/livechat-enterprise/server/lib/Helper.js b/ee/app/livechat-enterprise/server/lib/Helper.js index dd3318c8e678..af42c8fce807 100644 --- a/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/ee/app/livechat-enterprise/server/lib/Helper.js @@ -3,7 +3,14 @@ import { Match, check } from 'meteor/check'; import moment from 'moment'; import { hasRole } from '../../../../../app/authorization'; -import { LivechatDepartment, Users, LivechatInquiry, LivechatRooms, Messages } from '../../../../../app/models/server'; +import { + LivechatDepartment, + Users, + LivechatInquiry, + LivechatRooms, + Messages, + LivechatCustomField, +} from '../../../../../app/models/server'; import { Rooms as RoomRaw } from '../../../../../app/models/server/raw'; import { settings } from '../../../../../app/settings'; import { Livechat } from '../../../../../app/livechat/server/lib/Livechat'; @@ -217,3 +224,37 @@ export const updatePriorityInquiries = (priority) => { updateInquiryQueuePriority(room._id, priority); }); }; + +export const getLivechatCustomFields = () => { + const customFields = LivechatCustomField.find({ visibility: 'visible', scope: 'visitor', public: true }).fetch(); + return customFields.map(({ _id, label, regexp, required = false, type, defaultValue = null, options }) => ({ _id, label, regexp, required, type, defaultValue, ...options && options !== '' && { options: options.split(',') } })); +}; + +export const getLivechatQueueInfo = async (room) => { + if (!room) { + return null; + } + + if (!settings.get('Livechat_waiting_queue')) { + return null; + } + + const { _id: rid } = room; + const inquiry = LivechatInquiry.findOneByRoomId(rid, { fields: { _id: 1, status: 1 } }); + if (!inquiry) { + return null; + } + + const { _id, status } = inquiry; + if (status !== 'queued') { + return null; + } + + const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ _id }); + + if (!inq) { + return null; + } + + return normalizeQueueInfo(inq); +}; diff --git a/ee/i18n/en.i18n.json b/ee/i18n/en.i18n.json index 0c44d3332954..a94242a2d03c 100644 --- a/ee/i18n/en.i18n.json +++ b/ee/i18n/en.i18n.json @@ -11,6 +11,7 @@ "Canned_Response_Removed": "Canned Response Removed", "Canned_Responses_Enable": "Enable Canned Responses", "Closed_automatically": "Closed automatically by the system", + "Default_value": "Default value", "Edit_Tag": "Edit Tag", "Edit_Unit": "Edit Unit", "Edit_Priority": "Edit Priority", @@ -34,6 +35,8 @@ "List_of_departments_for_forward": "List of departments allowed for forwarding (Optional)", "List_of_departments_for_forward_description": "Allow to set a restricted list of departments that can receive chats from this department", "Livechat_abandoned_rooms_closed_custom_message": "Custom message when room is automatically closed by visitor inactivity", + "Livechat_custom_fields_options_placeholder": "Comma-separated list used to select a pre-configured value. Spaces between elements are not accepted.", + "Livechat_custom_fields_public_description": "Public custom fields will be displayed in external applications, such as Livechat, etc.", "Livechat_last_chatted_agent_routing": "Last-Chatted Agent Preferred", "Livechat_last_chatted_agent_routing_Description": "The Last-Chatted Agent setting allocates chats to the agent who previously interacted with the same visitor if the agent is available when the chat starts.", "Livechat_Monitors": "Monitors", diff --git a/ee/i18n/pt-BR.i18n.json b/ee/i18n/pt-BR.i18n.json index e7ebff498eae..2d772e4be909 100644 --- a/ee/i18n/pt-BR.i18n.json +++ b/ee/i18n/pt-BR.i18n.json @@ -5,6 +5,7 @@ "Add_monitor": "Adicionar Monitor", "Available_departments": "Departamentos disponíveis", "Closed_automatically": "Fechado automaticamente pelo sistema", + "Default_value": "Default value", "Edit_Tag": "Editar Tag", "Edit_Unit": "Editar Unidade", "Edit_Priority": "Editar Prioridade", @@ -26,6 +27,8 @@ "List_of_departments_for_forward": "Lista de departamentos permitidos para o encaminhamento(Opcional).", "List_of_departments_for_forward_description": "Permite definir uma lista restrita de departamentos que podem receber conversas desse departamento.", "Livechat_abandoned_rooms_closed_custom_message": "Mensagem customizada para usar quando a sala for automaticamente fechada por abandono do visitante", + "Livechat_custom_fields_options_placeholder": "Lista separada por vírgula usada para selecionar um valor pré-configurado. Espaços entre elementos não são aceitos.", + "Livechat_custom_fields_public_description": "Os custom fields públicos serão exibidos nos aplicativos externos, como o Livechat etc.", "Livechat_last_chatted_agent_routing": "Agente preferido pela última conversa", "Livechat_last_chatted_agent_routing_Description": "Agente preferido pela última conversa aloca bate-papos para o agente que interagiu anteriormente com o mesmo visitante, caso o agente esteja disponível quando o bate-papo for iniciado.", "Livechat_Monitors": "Monitores", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 66795f3b3dd2..d6210569d3bf 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1406,6 +1406,7 @@ "error-invalid-urls": "Invalid URLs", "error-invalid-user": "Invalid user", "error-invalid-username": "Invalid username", + "error-invalid-value": "Invalid valid", "error-invalid-webhook-response": "The webhook URL responded with a status other than 200", "error-message-deleting-blocked": "Message deleting is blocked", "error-message-editing-blocked": "Message editing is blocked", @@ -2802,7 +2803,7 @@ "Refresh_oauth_services": "Refresh OAuth Services", "Refresh_your_page_after_install_to_enable_screen_sharing": "Refresh your page after install to enable screen sharing", "Regenerate_codes": "Regenerate codes", - "Regexp_validation": "Validation by RegExp", + "Regexp_validation": "Validation by regular expression", "Register": "Register a new account", "Register_Server": "Register Server", "Register_Server_Info": "Use the preconfigured gateways and proxies provided by Rocket.Chat Technologies Corp.", @@ -3634,6 +3635,7 @@ "UTF8_Names_Slugify": "UTF8 Names Slugify", "UTF8_Names_Validation": "UTF8 Names Validation", "UTF8_Names_Validation_Description": "RegExp that will be used to validate usernames and channel names", + "Validation": "Validation", "Value_messages": "__value__ messages", "Value_users": "__value__ users", "Validate_email_address": "Validate Email Address", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index f4bdd6bf0d33..5c293c319b42 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -2541,6 +2541,7 @@ "Refresh_oauth_services": "Atualize os Serviços OAuth", "Refresh_your_page_after_install_to_enable_screen_sharing": "Atualize sua página após a instalação para permitir o compartilhamento de tela", "Regenerate_codes": "Regenerar códigos", + "Regexp_validation": "Validação por expressão regular", "Register": "Registrar-se", "Register_Server": "Registrar Servidor", "Register_Server_Info": "Use os gateways e proxies pré-configurados fornecidos pela Rocket.Chat Technologies Corp.", @@ -3272,6 +3273,7 @@ "UTF8_Names_Validation": "Validação de Nomes UTF8", "UTF8_Names_Validation_Description": "Não permitir caracteres especiais e espaços. Você pode usar - _ e. mas não no final do nome", "Validate_email_address": "Validar endereço de e-mail", + "Validation": "Validação", "Verification_email_body": "Você criou uma conta com sucesso em [Site_Name]. Por favor, clique no botão abaixo para confirmar seu endereço de e-mail e finalizar o registro.", "Verification": "Verificação", "Verification_Description": "Você pode usar os seguintes placeholders:
  • [Verification_Url] para o URL de verificação.
  • [nome], [fname], [lname] para o nome completo, primeiro nome ou sobrenome do usuário, respectivamente.
  • [email] para o email do usuário.
  • [Site_Name] e [Site_URL] para o Nome da Aplicação e o URL, respectivamente.
", diff --git a/packages/rocketchat-i18n/i18n/pt.i18n.json b/packages/rocketchat-i18n/i18n/pt.i18n.json index 6cce50aa8e08..a227f8e0c9ac 100644 --- a/packages/rocketchat-i18n/i18n/pt.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt.i18n.json @@ -1256,6 +1256,7 @@ "error-invalid-triggerWords": "Gerador de palavras inválido", "error-invalid-urls": "URLs inválidas", "error-invalid-user": "Utilizador inválido", + "error-invalid-value": "Valor inválido", "error-invalid-username": "Nome de utilizador Inválido", "error-invalid-webhook-response": "O URL do webhook respondeu com um status diferente de 200", "error-message-deleting-blocked": "Exclusão de mensagens está bloqueada", From c35811ddf5d34d5ac1e2c0b0b76131049ae75cbb Mon Sep 17 00:00:00 2001 From: Ritvik jain <34532173+ritvikjain99@users.noreply.github.com> Date: Thu, 21 May 2020 09:52:37 +0530 Subject: [PATCH 100/121] [FIX] Can't click on room's actions menu of sidebar list when in search mode (#16548) --- app/ui-message/client/popup/messagePopup.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/ui-message/client/popup/messagePopup.js b/app/ui-message/client/popup/messagePopup.js index 7ac6a7d785f2..f948dd6d4ffb 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); + } }, }); From 9826434e2c7bc4a716cd8db2302c4e30901dd518 Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Thu, 21 May 2020 01:24:00 -0300 Subject: [PATCH 101/121] Upgrade Livechat Widget version to 1.5.0 (#17710) --- package-lock.json | 47 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index e43bcec27d6f..f73e245f0d76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2810,6 +2810,11 @@ "uuid": "^3.2.1" }, "dependencies": { + "adm-zip": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", + "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==" + }, "typescript": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", @@ -2894,9 +2899,9 @@ "integrity": "sha512-MGljJJkGVe9g3UnKq2tSnfFq1gI6nIFociIXmOx9h/ZISeRc+xL6HpJ9pnbLXubFTPppNYn8JiVDlVB8Mm0flw==" }, "@rocket.chat/livechat": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.4.0.tgz", - "integrity": "sha512-jOUy49njPMW0ZmL9Gk6lRv4X0WTSS9N78iSzR/p3pSV4ivEQk1LRXCi6QLknZmxEXIpe6MTJgUY/ggsORfxrDQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.5.0.tgz", + "integrity": "sha512-Qq+kMRdY6RpZlzl5q3uM7hpAQF5tFFw+v7ON1IqBlFhkX5JA1mzpXnRWzb0si1LtuJ4NjdK2CS4xqvlaVn4A8Q==", "dev": true, "requires": { "@kossnocorp/desvg": "^0.2.0", @@ -2936,12 +2941,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, "query-string": { "version": "6.8.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.3.tgz", @@ -12975,9 +12974,9 @@ } }, "date-fns": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.11.0.tgz", - "integrity": "sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.14.0.tgz", + "integrity": "sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw==", "dev": true }, "date-now": { @@ -20965,9 +20964,9 @@ "dev": true }, "make-plural": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.1.0.tgz", - "integrity": "sha512-0ekbPHqxcdRcmjZ43TkRuejK5rXgMF1OjG4FVnVHgCvOcjrexaSX7a0dfAvqhOm1qWPgjYnXtmz3cHpHW5ZewA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.1.tgz", + "integrity": "sha512-AmkruwJ9EjvyTv6AM8MBMK3TAeOJvhgTv5YQXzF0EP2qawhpvMjDpHvsdOIIT0Vn+BB0+IogmYZ1z+Ulm/m0Fg==", "dev": true }, "map-age-cleaner": { @@ -21025,9 +21024,9 @@ }, "dependencies": { "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", + "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", "dev": true } } @@ -21388,9 +21387,9 @@ "dev": true }, "messageformat-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.2.tgz", - "integrity": "sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", + "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==", "dev": true }, "meteor-blaze-tools": { @@ -22772,9 +22771,9 @@ }, "dependencies": { "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "dev": true } } diff --git a/package.json b/package.json index 99750b95786a..96e3934081e9 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@babel/preset-react": "^7.0.0", "@octokit/rest": "^16.1.0", "@rocket.chat/eslint-config": "^0.3.0", - "@rocket.chat/livechat": "^1.4.0", + "@rocket.chat/livechat": "^1.5.0", "@settlin/spacebars-loader": "^1.0.7", "@storybook/addon-actions": "^5.3.18", "@storybook/addon-knobs": "^5.3.18", From d46c864f5ef792eafff27d634c4a32bb7486c85e Mon Sep 17 00:00:00 2001 From: Renato Becker Date: Thu, 21 May 2020 01:32:13 -0300 Subject: [PATCH 102/121] [NEW][ENTERPRISE] Support Omnichannel conversations auditing (#17692) Co-authored-by: Rodrigo Nascimento --- app/livechat/imports/server/rest/visitors.js | 16 +++++++- app/livechat/server/api/lib/visitors.js | 24 +++++++++++ app/models/server/models/LivechatRooms.js | 10 +++++ app/models/server/raw/LivechatVisitors.js | 41 +++++++++++++++++++ app/ui/client/components/popupList.html | 9 ++++ .../client/templates/audit/audit.html | 7 +++- .../auditing/client/templates/audit/audit.js | 36 ++++++++++++---- ee/app/auditing/client/utils.js | 13 +----- ee/app/auditing/server/methods.js | 34 +++++++++++---- packages/rocketchat-i18n/i18n/en.i18n.json | 2 + packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 2 + 11 files changed, 163 insertions(+), 31 deletions(-) diff --git a/app/livechat/imports/server/rest/visitors.js b/app/livechat/imports/server/rest/visitors.js index c828e9552e58..cc38c3bc4008 100644 --- a/app/livechat/imports/server/rest/visitors.js +++ b/app/livechat/imports/server/rest/visitors.js @@ -2,7 +2,7 @@ import { check } from 'meteor/check'; import { API } from '../../../../api/server'; -import { findVisitorInfo, findVisitedPages, findChatHistory } from '../../../server/api/lib/visitors'; +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/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/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 49823c11f896..6f9e1957c491 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -252,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/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/ui/client/components/popupList.html b/app/ui/client/components/popupList.html index 49318e3aea9e..135554197510 100644 --- a/app/ui/client/components/popupList.html +++ b/app/ui/client/components/popupList.html @@ -43,3 +43,12 @@ {{{modifier item.name}}} + + diff --git a/ee/app/auditing/client/templates/audit/audit.html b/ee/app/auditing/client/templates/audit/audit.html index 1493064c8eed..b5247322ce60 100644 --- a/ee/app/auditing/client/templates/audit/audit.html +++ b/ee/app/auditing/client/templates/audit/audit.html @@ -18,12 +18,15 @@ {{> icon block="rc-select__arrow" icon="arrow-down" }} - {{> auditAutocomplete key='room' hide=typeD onChange=onChange prepare=prepareRoom field='name' term='name' icon='hashtag' label="room_name" placeholder='Channel_Name_Placeholder'}} - {{> auditAutocompleteDirectMessage key='users' hide=nTypeD onChange=onChange collection='UserAndRoom' modifier=modifierUser endpoint='users.autocomplete' field='username' icon='at' label="From" placeholder='Username_Placeholder'}} + {{> auditAutocomplete key='room' hide=nTypeOthers onChange=onChange prepare=prepareRoom field='name' term='name' icon='hashtag' label="room_name" placeholder='Channel_Name_Placeholder'}} + {{> auditAutocompleteDirectMessage key='users' hide=nTypeDM onChange=onChange collection='UserAndRoom' modifier=modifierUser endpoint='users.autocomplete' field='username' icon='at' label="From" placeholder='Username_Placeholder'}} + {{> auditAutocomplete key='visitor' hide=nTypeOmni onChange=onChange collection='CachedVisitorsList' modifier=modifierUser endpoint='livechat/visitors.autocomplete' field='custom_name' term='custom_name' icon='omnichannel' label="Visitor" templateItem="popupList_item_custom" placeholder='Visitor_Name_Placeholder'}} + {{> auditAutocomplete key='agent' hide=nTypeOmni onChange=onChange collection='CachedAgentsList' conditions=agentConditions modifier=modifierUser endpoint='users.autocomplete' field='username' icon='omnichannel' label="Agent" placeholder='Agent_Name_Placeholder'}}