{{#unless isAnonymousOrMustJoinWithCode}}
- {{#unless tmid}}
- {{> messageBoxTyping rid=rid}}
- {{/unless}}
+
+ {{> userActionIndicator rid=rid tmid=tmid}}
{{#if isWritable}}
{{#if popupConfig}}
diff --git a/app/ui-message/client/messageBox/messageBox.js b/app/ui-message/client/messageBox/messageBox.js
index 7bf08b4ac306..7cb519da8e89 100644
--- a/app/ui-message/client/messageBox/messageBox.js
+++ b/app/ui-message/client/messageBox/messageBox.js
@@ -21,9 +21,6 @@ import {
import {
messageBox,
popover,
- call,
- keyCodes,
- isRTL,
} from '../../../ui-utils';
import {
t,
@@ -32,11 +29,14 @@ import {
} from '../../../utils/client';
import './messageBoxActions';
import './messageBoxReplyPreview';
-import './messageBoxTyping';
+import './userActionIndicator.ts';
import './messageBoxAudioMessage';
import './messageBoxNotSubscribed';
import './messageBox.html';
import './messageBoxReadOnly';
+import { keyCodes } from '../../../../client/lib/utils/keyCodes';
+import { isRTL } from '../../../../client/lib/utils/isRTL';
+import { call } from '../../../../client/lib/utils/call';
Template.messageBox.onCreated(function() {
this.state = new ReactiveDict();
diff --git a/app/ui-message/client/messageBox/messageBoxAudioMessage.js b/app/ui-message/client/messageBox/messageBoxAudioMessage.js
index 66b3ceb34f6e..ac296a9cc19f 100644
--- a/app/ui-message/client/messageBox/messageBoxAudioMessage.js
+++ b/app/ui-message/client/messageBox/messageBoxAudioMessage.js
@@ -2,31 +2,47 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { settings } from '../../../settings';
-import { AudioRecorder, fileUpload } from '../../../ui';
+import { AudioRecorder, fileUpload, USER_ACTIVITIES, UserAction } from '../../../ui';
import { t } from '../../../utils';
import './messageBoxAudioMessage.html';
-const startRecording = () => new Promise((resolve, reject) =>
- AudioRecorder.start((result) => (result ? resolve() : reject())));
+const startRecording = async (rid, tmid) => {
+ try {
+ await AudioRecorder.start();
+ UserAction.performContinuously(rid, USER_ACTIVITIES.USER_RECORDING, { tmid });
+ } catch (error) {
+ throw error;
+ }
+};
-const stopRecording = () => new Promise((resolve) => AudioRecorder.stop(resolve));
+const stopRecording = async (rid, tmid) => {
+ const result = await new Promise((resolve) => AudioRecorder.stop(resolve));
+ UserAction.stop(rid, USER_ACTIVITIES.USER_RECORDING, { tmid });
+ return result;
+};
const recordingInterval = new ReactiveVar(null);
const recordingRoomId = new ReactiveVar(null);
-const cancelRecording = (instance) => new Promise(async () => {
+const clearIntervalVariables = () => {
if (recordingInterval.get()) {
clearInterval(recordingInterval.get());
recordingInterval.set(null);
recordingRoomId.set(null);
}
+};
+
+const cancelRecording = async (instance, rid, tmid) => {
+ clearIntervalVariables();
instance.time.set('00:00');
- await stopRecording();
+ const blob = await stopRecording(rid, tmid);
instance.state.set(null);
-});
+
+ return blob;
+};
Template.messageBoxAudioMessage.onCreated(async function() {
this.state = new ReactiveVar(null);
@@ -63,7 +79,8 @@ Template.messageBoxAudioMessage.onCreated(async function() {
Template.messageBoxAudioMessage.onDestroyed(async function() {
if (this.state.get() === 'recording') {
- await cancelRecording(this);
+ const { rid, tmid } = this.data;
+ await cancelRecording(this, rid, tmid);
}
});
@@ -104,8 +121,7 @@ Template.messageBoxAudioMessage.events({
instance.state.set('recording');
try {
- await startRecording();
-
+ await startRecording(this.rid, this.tmid);
const startTime = new Date();
recordingInterval.set(setInterval(() => {
const now = new Date();
@@ -125,7 +141,7 @@ Template.messageBoxAudioMessage.events({
async 'click .js-audio-message-cancel'(event, instance) {
event.preventDefault();
- await cancelRecording(instance);
+ await cancelRecording(instance, this.rid, this.tmid);
},
async 'click .js-audio-message-done'(event, instance) {
@@ -133,19 +149,9 @@ Template.messageBoxAudioMessage.events({
instance.state.set('loading');
- if (recordingInterval.get()) {
- clearInterval(recordingInterval.get());
- recordingInterval.set(null);
- recordingRoomId.set(null);
- }
-
- instance.time.set('00:00');
-
- const blob = await stopRecording();
-
- instance.state.set(null);
-
const { rid, tmid } = this;
+ const blob = await cancelRecording(instance, rid, tmid);
+
await fileUpload([{ file: blob, type: 'video', name: `${ t('Audio record') }.mp3` }], { input: blob }, { rid, tmid });
},
});
diff --git a/app/ui-message/client/messageBox/messageBoxNotSubscribed.js b/app/ui-message/client/messageBox/messageBoxNotSubscribed.js
index 0b0f75833bab..0579dfd00f9d 100644
--- a/app/ui-message/client/messageBox/messageBoxNotSubscribed.js
+++ b/app/ui-message/client/messageBox/messageBoxNotSubscribed.js
@@ -2,11 +2,12 @@ import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
-import { settings } from '../../../settings';
-import { call, RoomManager, RoomHistoryManager } from '../../../ui-utils';
-import { roomTypes } from '../../../utils';
-import { hasAllPermission } from '../../../authorization';
+import { settings } from '../../../settings/client';
+import { RoomManager, RoomHistoryManager } from '../../../ui-utils/client';
+import { roomTypes } from '../../../utils/client';
+import { hasAllPermission } from '../../../authorization/client';
import './messageBoxNotSubscribed.html';
+import { call } from '../../../../client/lib/utils/call';
Template.messageBoxNotSubscribed.helpers({
diff --git a/app/ui-message/client/messageBox/messageBoxTyping.html b/app/ui-message/client/messageBox/messageBoxTyping.html
deleted file mode 100644
index be97891a85f3..000000000000
--- a/app/ui-message/client/messageBox/messageBoxTyping.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
- {{#with data}}
-
- {{users}}
- {{#if multi}}
- {{#if selfTyping}}
- {{_ "are_also_typing"}}
- {{else}}
- {{_ "are_typing"}}
- {{/if}}
- {{else}}
- {{#if selfTyping}}
- {{_ "is_also_typing" context="male"}}
- {{else}}
- {{_ "is_typing" context="male"}}
- {{/if}}
- {{/if}}
-
- {{/with}}
-
diff --git a/app/ui-message/client/messageBox/messageBoxTyping.js b/app/ui-message/client/messageBox/messageBoxTyping.js
deleted file mode 100644
index 8fa993112a03..000000000000
--- a/app/ui-message/client/messageBox/messageBoxTyping.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Template } from 'meteor/templating';
-
-import { MsgTyping } from '../../../ui';
-import { t } from '../../../utils';
-import { getConfig } from '../../../ui-utils/client/config';
-import './messageBoxTyping.html';
-
-const maxUsernames = parseInt(getConfig('max-usernames-typing')) || 4;
-
-Template.messageBoxTyping.helpers({
- data() {
- const users = MsgTyping.get(this.rid);
- if (users.length === 0) {
- return;
- }
- if (users.length === 1) {
- return {
- multi: false,
- selfTyping: MsgTyping.selfTyping,
- users: users[0],
- };
- }
- let last = users.pop();
- if (users.length >= maxUsernames) {
- last = t('others');
- }
- let usernames = users.slice(0, maxUsernames - 1).join(', ');
- usernames = [usernames, last];
- return {
- multi: true,
- selfTyping: MsgTyping.selfTyping,
- users: usernames.join(` ${ t('and') } `),
- };
- },
-});
diff --git a/app/ui-message/client/messageBox/userActionIndicator.html b/app/ui-message/client/messageBox/userActionIndicator.html
new file mode 100644
index 000000000000..55c833ae12df
--- /dev/null
+++ b/app/ui-message/client/messageBox/userActionIndicator.html
@@ -0,0 +1,14 @@
+
+ {{#if data}}
+
+ {{#each data}}
+ {{ users }}
+ {{#if multi}}
+ {{_ "are" }} {{_ action}}{{#unless end}},{{/unless}}
+ {{else}}
+ {{_ "is" }} {{_ action}}{{#unless end}},{{/unless}}
+ {{/if}}
+ {{/each}}
+
+ {{/if}}
+
diff --git a/app/ui-message/client/messageBox/userActionIndicator.ts b/app/ui-message/client/messageBox/userActionIndicator.ts
new file mode 100644
index 000000000000..aff05649748d
--- /dev/null
+++ b/app/ui-message/client/messageBox/userActionIndicator.ts
@@ -0,0 +1,59 @@
+import { Template } from 'meteor/templating';
+
+import { UserAction } from '../../../ui';
+import { t } from '../../../utils/client';
+import { getConfig } from '../../../../client/lib/utils/getConfig';
+
+import './userActionIndicator.html';
+
+const maxUsernames = parseInt(getConfig('max-usernames-typing') || '2');
+
+Template.userActionIndicator.helpers({
+ data() {
+ const roomAction = UserAction.get(this.tmid || this.rid) || {};
+ if (!Object.keys(roomAction).length) {
+ return [];
+ }
+
+ const activities = Object.entries(roomAction);
+ const userActions = activities.map(([key, _users]) => {
+ const users = Object.keys(_users);
+ if (users.length === 0) {
+ return {
+ end: false,
+ };
+ }
+
+ const action = key.split('-')[1];
+ if (users.length === 1) {
+ return {
+ action,
+ multi: false,
+ users: users[0],
+ end: false,
+ };
+ }
+
+ let last = users.pop();
+ if (users.length >= maxUsernames) {
+ last = t('others');
+ }
+
+ const usernames = [users.slice(0, maxUsernames - 1).join(', '), last];
+ return {
+ action,
+ multi: true,
+ users: usernames.join(` ${ t('and') } `),
+ end: false,
+ };
+ }).filter((i) => i.action);
+
+ if (!Object.keys(userActions).length) {
+ return [];
+ }
+
+ // insert end=true for the last item.
+ userActions[userActions.length - 1].end = true;
+ return userActions;
+ },
+});
diff --git a/app/ui-message/client/messageThread.js b/app/ui-message/client/messageThread.js
index d18ac495600a..7f45f140c6db 100644
--- a/app/ui-message/client/messageThread.js
+++ b/app/ui-message/client/messageThread.js
@@ -2,10 +2,10 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import _ from 'underscore';
-import { call } from '../../ui-utils/client';
+import { callWithErrorHandling } from '../../../client/lib/utils/callWithErrorHandling';
import { Messages } from '../../models/client';
-import './messageThread.html';
import { normalizeThreadTitle } from '../../threads/client/lib/normalizeThreadTitle';
+import './messageThread.html';
const findParentMessage = (() => {
const waiting = [];
@@ -15,7 +15,7 @@ const findParentMessage = (() => {
const getMessages = _.debounce(async function() {
const _tmp = [...waiting];
waiting.length = 0;
- resolve(call('getMessages', _tmp));
+ resolve(callWithErrorHandling('getMessages', _tmp));
pending = new Promise((r) => { resolve = r; });
}, 500);
diff --git a/app/ui-message/client/popup/messagePopupConfig.js b/app/ui-message/client/popup/messagePopupConfig.js
index 7a0deb077f79..6a37e6c94c5e 100644
--- a/app/ui-message/client/popup/messagePopupConfig.js
+++ b/app/ui-message/client/popup/messagePopupConfig.js
@@ -12,7 +12,7 @@ import { Messages, Subscriptions } from '../../../models/client';
import { settings } from '../../../settings/client';
import { hasAllPermission, hasAtLeastOnePermission } from '../../../authorization/client';
import { EmojiPicker, emoji } from '../../../emoji';
-import { call } from '../../../ui-utils/client';
+import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { t, getUserPreference, slashCommands } from '../../../utils/client';
import { customMessagePopups } from './customMessagePopups';
import './messagePopupConfig.html';
@@ -61,7 +61,7 @@ const reloadUsersFromRoomMessages = (rid, template) => {
const fetchUsersFromServer = _.throttle(async (filterText, records, rid, cb) => {
const usernames = records.map(({ username }) => username);
- const { users } = await call('spotlight', filterText, usernames, { users: true, mentions: true }, rid);
+ const { users } = await callWithErrorHandling('spotlight', filterText, usernames, { users: true, mentions: true }, rid);
if (!users || users.length <= 0) {
return;
@@ -95,7 +95,7 @@ const fetchRoomsFromServer = _.throttle(async (filterText, records, rid, cb) =>
return;
}
- const { rooms } = await call('spotlight', filterText, null, { rooms: true, mentions: true }, rid);
+ const { rooms } = await callWithErrorHandling('spotlight', filterText, null, { rooms: true, mentions: true }, rid);
if (!rooms || rooms.length <= 0) {
return;
diff --git a/app/ui-message/client/popup/messagePopupEmoji.html b/app/ui-message/client/popup/messagePopupEmoji.html
index c554520afd64..c064ff72cbf8 100644
--- a/app/ui-message/client/popup/messagePopupEmoji.html
+++ b/app/ui-message/client/popup/messagePopupEmoji.html
@@ -1,4 +1,4 @@
- {{> renderEmoji _id}}
+ {{{renderEmoji _id}}}
{{_id}}
diff --git a/app/ui-message/client/pushMessage.js b/app/ui-message/client/pushMessage.js
index 3d8b6c39665a..e147f4579871 100644
--- a/app/ui-message/client/pushMessage.js
+++ b/app/ui-message/client/pushMessage.js
@@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
-import { timeAgo } from '../../lib/client/lib/formatDate';
+import { timeAgo } from '../../../client/lib/utils/timeAgo';
import './pushMessage.html';
Template.pushMessage.helpers({
diff --git a/app/ui-sidenav/client/roomList.js b/app/ui-sidenav/client/roomList.js
index 2823c2500906..f79d7093c964 100644
--- a/app/ui-sidenav/client/roomList.js
+++ b/app/ui-sidenav/client/roomList.js
@@ -170,6 +170,7 @@ const mergeSubRoom = (subscription) => {
priorityId: 1,
livechatData: 1,
departmentId: 1,
+ source: 1,
},
};
@@ -208,6 +209,7 @@ const mergeSubRoom = (subscription) => {
livechatData,
departmentId,
ts,
+ source,
} = room;
subscription.lm = subscription.lr ? new Date(Math.max(subscription.lr, lastRoomUpdate)) : lastRoomUpdate;
@@ -243,6 +245,7 @@ const mergeSubRoom = (subscription) => {
livechatData,
departmentId,
ts,
+ source,
});
};
@@ -283,7 +286,7 @@ const mergeRoomSub = (room) => {
livechatData,
departmentId,
ts,
-
+ source,
} = room;
Subscriptions.update({
@@ -319,6 +322,7 @@ const mergeRoomSub = (room) => {
departmentId,
jitsiTimeout,
ts,
+ source,
...getLowerCaseNames(room, sub.name, sub.fname),
},
});
diff --git a/app/ui-utils/client/config.js b/app/ui-utils/client/config.js
deleted file mode 100644
index ce484f86a7dd..000000000000
--- a/app/ui-utils/client/config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-const url = new URL(window.location);
-const keys = new Set();
-
-export const getConfig = (key) => {
- keys.add(key);
- return url.searchParams.get(key) || Meteor._localStorage.getItem(`rc-config-${ key }`);
-};
diff --git a/app/ui-utils/client/index.js b/app/ui-utils/client/index.js
index bfa02f0c127a..c5ff967f612e 100644
--- a/app/ui-utils/client/index.js
+++ b/app/ui-utils/client/index.js
@@ -2,7 +2,6 @@ export { modal } from './lib/modal';
export { SideNav } from './lib/SideNav';
export { AccountBox } from './lib/AccountBox';
export { menu } from './lib/menu';
-export { call } from './lib/callMethod';
export { MessageAction } from './lib/MessageAction';
export { messageBox } from './lib/messageBox';
export { offlineAction } from './lib/offlineAction';
@@ -11,18 +10,11 @@ export { readMessage } from './lib/readMessages';
export { RoomManager } from './lib/RoomManager';
export { upsertMessage, RoomHistoryManager, normalizeThreadMessage } from './lib/RoomHistoryManager';
export { mainReady } from './lib/mainReady';
-export { Layout } from './lib/Layout';
export { IframeLogin, iframeLogin } from './lib/IframeLogin';
-export { fireGlobalEvent } from './lib/fireGlobalEvent';
-export { getAvatarAsPng } from './lib/avatar';
export { popout } from './lib/popout';
export { messageProperties } from '../lib/MessageProperties';
export { MessageTypes } from '../lib/MessageTypes';
export { Message } from '../lib/Message';
export { openRoom } from './lib/openRoom';
-export { warnUserDeletionMayRemoveRooms } from './lib/warnUserDeletionMayRemoveRooms';
-export * from './lib/rtl';
-export * from './lib/keyCodes';
-export * from './lib/prependReplies';
export * from './lib/collapseArrow';
export * from './lib/messageArgs';
diff --git a/app/ui-utils/client/lib/Layout.js b/app/ui-utils/client/lib/Layout.js
deleted file mode 100644
index 2158d80f2a9b..000000000000
--- a/app/ui-utils/client/lib/Layout.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Tracker } from 'meteor/tracker';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-import { ReactiveVar } from 'meteor/reactive-var';
-
-export const Layout = new class RocketChatLayout {
- constructor() {
- this.embedded = new ReactiveVar();
- Tracker.autorun(() => {
- this.layout = FlowRouter.getQueryParam('layout');
- this.embedded.set(this.layout === 'embedded');
- });
- }
-
- isEmbedded() {
- return this.embedded.get();
- }
-}();
diff --git a/app/ui-utils/client/lib/MessageAction.js b/app/ui-utils/client/lib/MessageAction.js
index f48800e43015..fbcc4b2a4f68 100644
--- a/app/ui-utils/client/lib/MessageAction.js
+++ b/app/ui-utils/client/lib/MessageAction.js
@@ -10,21 +10,14 @@ import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { messageArgs } from './messageArgs';
-import { roomTypes, canDeleteMessage } from '../../../utils/client';
+import { roomTypes } from '../../../utils/client';
import { Messages, Rooms, Subscriptions } from '../../../models/client';
import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/client';
import { modal } from './modal';
import { imperativeModal } from '../../../../client/lib/imperativeModal';
import ReactionList from '../../../../client/components/modals/ReactionList';
-
-const call = (method, ...args) => new Promise((resolve, reject) => {
- Meteor.call(method, ...args, function(err, data) {
- if (err) {
- return reject(err);
- }
- resolve(data);
- });
-});
+import { call } from '../../../../client/lib/utils/call';
+import { canDeleteMessage } from '../../../../client/lib/utils/canDeleteMessage';
export const addMessageToList = (messagesList, message) => {
// checks if the message is not already on the list
diff --git a/app/ui-utils/client/lib/RoomHistoryManager.js b/app/ui-utils/client/lib/RoomHistoryManager.js
index f5bdd066edde..1847c8ebd169 100644
--- a/app/ui-utils/client/lib/RoomHistoryManager.js
+++ b/app/ui-utils/client/lib/RoomHistoryManager.js
@@ -10,10 +10,10 @@ import { escapeHTML } from '@rocket.chat/string-helpers';
import { promises } from '../../../promises/client';
import { RoomManager } from './RoomManager';
import { readMessage } from './readMessages';
-import { renderMessageBody } from '../../../../client/lib/renderMessageBody';
-import { getConfig } from '../config';
-import { call } from './callMethod';
+import { renderMessageBody } from '../../../../client/lib/utils/renderMessageBody';
+import { getConfig } from '../../../../client/lib/utils/getConfig';
import { ChatMessage, ChatSubscription, ChatRoom } from '../../../models';
+import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { filterMarkdown } from '../../../markdown/lib/markdown';
import { getUserPreference } from '../../../utils/client';
@@ -190,7 +190,7 @@ export const RoomHistoryManager = new class extends Emitter {
}
const showMessageInMainThread = getUserPreference(Meteor.userId(), 'showMessageInMainThread', false);
- const result = await call('loadHistory', rid, ts, limit, ls, showMessageInMainThread);
+ const result = await callWithErrorHandling('loadHistory', rid, ts, limit, ls, showMessageInMainThread);
this.unqueue();
@@ -269,7 +269,7 @@ export const RoomHistoryManager = new class extends Emitter {
const { ts } = lastMessage;
if (ts) {
- const result = await call('loadNextMessages', rid, ts, limit);
+ const result = await callWithErrorHandling('loadNextMessages', rid, ts, limit);
upsertMessageBulk({
msgs: Array.from(result.messages).filter((msg) => msg.t !== 'command'),
subscription,
diff --git a/app/ui-utils/client/lib/RoomManager.js b/app/ui-utils/client/lib/RoomManager.js
index e0e41475ffed..888828515f61 100644
--- a/app/ui-utils/client/lib/RoomManager.js
+++ b/app/ui-utils/client/lib/RoomManager.js
@@ -6,7 +6,7 @@ import { Blaze } from 'meteor/blaze';
import { FlowRouter } from 'meteor/kadira:flow-router';
import _ from 'underscore';
-import { fireGlobalEvent } from './fireGlobalEvent';
+import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent';
import { upsertMessage, RoomHistoryManager } from './RoomHistoryManager';
import { mainReady } from './mainReady';
import { menu } from './menu';
@@ -15,9 +15,9 @@ import { callbacks } from '../../../callbacks';
import { Notifications } from '../../../notifications';
import { CachedChatRoom, ChatMessage, ChatSubscription, CachedChatSubscription, ChatRoom } from '../../../models';
import { CachedCollectionManager } from '../../../ui-cached-collection';
-import { getConfig } from '../config';
+import { getConfig } from '../../../../client/lib/utils/getConfig';
import { ROOM_DATA_STREAM } from '../../../utils/stream/constants';
-import { call } from '..';
+import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { RoomManager as NewRoomManager } from '../../../../client/lib/RoomManager';
const maxRoomsOpen = parseInt(getConfig('maxRoomsOpen')) || 5;
@@ -286,7 +286,7 @@ const loadMissedMessages = async function(rid) {
try {
- const result = await call('loadMissedMessages', rid, lastMessage.ts);
+ const result = await callWithErrorHandling('loadMissedMessages', rid, lastMessage.ts);
if (result) {
const subscription = ChatSubscription.findOne({ rid });
return Promise.all(Array.from(result).map((msg) => upsertMessage({ msg, subscription })));
diff --git a/app/ui-utils/client/lib/avatar.js b/app/ui-utils/client/lib/avatar.js
deleted file mode 100644
index c7a038126d82..000000000000
--- a/app/ui-utils/client/lib/avatar.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Blaze } from 'meteor/blaze';
-
-import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL';
-
-Blaze.registerHelper('avatarUrlFromUsername', getUserAvatarURL);
-
-export const getAvatarAsPng = function(username, cb) {
- const image = new Image();
- image.src = getUserAvatarURL(username);
- image.onload = function() {
- const canvas = document.createElement('canvas');
- canvas.width = image.width;
- canvas.height = image.height;
- const context = canvas.getContext('2d');
- context.drawImage(image, 0, 0);
- try {
- return cb(canvas.toDataURL('image/png'));
- } catch (e) {
- return cb('');
- }
- };
- image.onerror = function() {
- return cb('');
- };
- return image.onerror;
-};
diff --git a/app/ui-utils/client/lib/callMethod.js b/app/ui-utils/client/lib/callMethod.js
deleted file mode 100644
index c1b05108ad80..000000000000
--- a/app/ui-utils/client/lib/callMethod.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { handleError } from '../../../utils/client/lib/handleError';
-
-/**
- * Wraps a Meteor method into a Promise.
- * This is particularly useful for creating information dialogs after execution of a Meteor method
- * @param {The Meteor method to be calls} method
- * @param {the method's parameters} params
- */
-export const call = (method, ...params) => new Promise((resolve, reject) => {
- Meteor.call(method, ...params, (err, result) => {
- if (err) {
- handleError(err);
- return reject(err);
- }
- return resolve(result);
- });
-});
-
-export const callMethod = (method, ...params) => new Promise((resolve, reject) => {
- Meteor.call(method, ...params, (err, result) => {
- if (err) {
- return reject(err);
- }
- return resolve(result);
- });
-});
diff --git a/app/ui-utils/client/lib/fireGlobalEvent.js b/app/ui-utils/client/lib/fireGlobalEvent.js
deleted file mode 100644
index c179a29496b4..000000000000
--- a/app/ui-utils/client/lib/fireGlobalEvent.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Tracker } from 'meteor/tracker';
-
-import { settings } from '../../../settings';
-
-export const fireGlobalEvent = function _fireGlobalEvent(eventName, params) {
- window.dispatchEvent(new CustomEvent(eventName, { detail: params }));
-
- Tracker.autorun((computation) => {
- const enabled = settings.get('Iframe_Integration_send_enable');
- if (enabled === undefined) {
- return;
- }
- computation.stop();
- if (enabled) {
- parent.postMessage({
- eventName,
- data: params,
- }, settings.get('Iframe_Integration_send_target_origin'));
- }
- });
-};
diff --git a/app/ui-utils/client/lib/getUidDirectMessage.js b/app/ui-utils/client/lib/getUidDirectMessage.js
deleted file mode 100644
index ecfe3b48fc99..000000000000
--- a/app/ui-utils/client/lib/getUidDirectMessage.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { Rooms } from '../../../models/client';
-
-export const getUidDirectMessage = (rid, userId = Meteor.userId()) => {
- const room = Rooms.findOne({ _id: rid }, { fields: { uids: 1 } });
-
- if (!room || !room.uids || room.uids.length > 2) {
- return false;
- }
-
- const other = room && room.uids.filter((uid) => uid !== userId);
-
- return other && other[0];
-};
diff --git a/app/ui-utils/client/lib/messageContext.js b/app/ui-utils/client/lib/messageContext.js
index 89712cb48cb8..82cf52c2d1d7 100644
--- a/app/ui-utils/client/lib/messageContext.js
+++ b/app/ui-utils/client/lib/messageContext.js
@@ -6,12 +6,13 @@ import { Tracker } from 'meteor/tracker';
import { Subscriptions, Rooms, Users } from '../../../models/client';
import { hasPermission } from '../../../authorization/client';
import { settings } from '../../../settings/client';
-import { getUserPreference, roomTypes, handleError } from '../../../utils/client';
+import { getUserPreference, roomTypes } from '../../../utils/client';
import { AutoTranslate } from '../../../autotranslate/client';
-import { Layout } from './Layout';
-import { fireGlobalEvent } from './fireGlobalEvent';
+import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent';
import { actionLinks } from '../../../action-links/client';
-import { goToRoomById } from '../../../../client/lib/goToRoomById';
+import { goToRoomById } from '../../../../client/lib/utils/goToRoomById';
+import { isLayoutEmbedded } from '../../../../client/lib/utils/isLayoutEmbedded';
+import { handleError } from '../../../../client/lib/utils/handleError';
const fields = { name: 1, username: 1, 'settings.preferences.enableMessageParserEarlyAdoption': 1, 'settings.preferences.showMessageInMainThread': 1, 'settings.preferences.autoImageLoad': 1, 'settings.preferences.saveMobileBandwidth': 1, 'settings.preferences.collapseMediaByDefault': 1, 'settings.preferences.hideRoles': 1 };
@@ -34,7 +35,7 @@ export function messageContext({ rid } = Template.instance()) {
e.stopPropagation();
};
- const runAction = Layout.isEmbedded() ? (msg, e) => {
+ const runAction = isLayoutEmbedded() ? (msg, e) => {
const { actionlink } = e.currentTarget.dataset;
return fireGlobalEvent('click-action-link', {
actionlink,
diff --git a/app/ui-utils/client/lib/modal.js b/app/ui-utils/client/lib/modal.js
index 1971ba908b0e..d7d12baadb43 100644
--- a/app/ui-utils/client/lib/modal.js
+++ b/app/ui-utils/client/lib/modal.js
@@ -2,8 +2,9 @@ import { Meteor } from 'meteor/meteor';
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
-import { t, getUserPreference, handleError } from '../../../utils';
+import { t, getUserPreference } from '../../../utils';
import './modal.html';
+import { handleError } from '../../../../client/lib/utils/handleError';
let modalStack = [];
diff --git a/app/ui-utils/client/lib/openRoom.js b/app/ui-utils/client/lib/openRoom.js
index c9e3ff46b170..9e0a61810e54 100644
--- a/app/ui-utils/client/lib/openRoom.js
+++ b/app/ui-utils/client/lib/openRoom.js
@@ -9,10 +9,12 @@ import { Messages, ChatSubscription } from '../../../models';
import { settings } from '../../../settings';
import { callbacks } from '../../../callbacks';
import { roomTypes } from '../../../utils';
-import { call, callMethod } from './callMethod';
-import { RoomManager, fireGlobalEvent, RoomHistoryManager } from '..';
+import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
+import { call } from '../../../../client/lib/utils/call';
+import { RoomManager, RoomHistoryManager } from '..';
import { RoomManager as NewRoomManager } from '../../../../client/lib/RoomManager';
import { Rooms } from '../../../models/client';
+import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent';
window.currentTracker = undefined;
@@ -34,7 +36,7 @@ export const openRoom = async function(type, name, render = true) {
}
try {
- const room = roomTypes.findRoom(type, name, user) || await callMethod('getRoomByTypeAndName', type, name);
+ const room = roomTypes.findRoom(type, name, user) || await call('getRoomByTypeAndName', type, name);
Rooms.upsert({ _id: room._id }, _.omit(room, '_id'));
if (room._id !== name && type === 'd') { // Redirect old url using username to rid
@@ -66,14 +68,14 @@ export const openRoom = async function(type, name, render = true) {
// update user's room subscription
const sub = ChatSubscription.findOne({ rid: room._id });
if (sub && sub.open === false) {
- call('openRoom', room._id);
+ callWithErrorHandling('openRoom', room._id);
}
if (FlowRouter.getQueryParam('msg')) {
const messageId = FlowRouter.getQueryParam('msg');
const msg = { _id: messageId, rid: room._id };
- const message = Messages.findOne({ _id: msg._id }) || (await call('getMessages', [msg._id]))[0];
+ const message = Messages.findOne({ _id: msg._id }) || (await callWithErrorHandling('getMessages', [msg._id]))[0];
if (message && (message.tmid || message.tcount)) {
return FlowRouter.setParams({ tab: 'thread', context: message.tmid || message._id });
@@ -89,7 +91,7 @@ export const openRoom = async function(type, name, render = true) {
} catch (error) {
c.stop();
if (type === 'd') {
- const result = await call('createDirectMessage', ...name.split(', '));
+ const result = await callWithErrorHandling('createDirectMessage', ...name.split(', '));
if (result) {
return FlowRouter.go('direct', { rid: result.rid }, FlowRouter.current().queryParams);
}
diff --git a/app/ui-utils/client/lib/prependReplies.js b/app/ui-utils/client/lib/prependReplies.js
deleted file mode 100644
index eb9b3a8fd5ea..000000000000
--- a/app/ui-utils/client/lib/prependReplies.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { MessageAction } from './MessageAction';
-import { Rooms, Users } from '../../../models/client';
-
-export const prependReplies = async (msg, replies = [], mention = false) => {
- const { username } = Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } });
-
- const chunks = await Promise.all(replies.map(async ({ _id, rid, u }) => {
- const permalink = await MessageAction.getPermaLink(_id);
- const room = Rooms.findOne(rid, { fields: { t: 1 } });
-
- let chunk = `[ ](${ permalink })`;
- if (room.t === 'd' && u.username !== username && mention) {
- chunk += ` @${ u.username }`;
- }
-
- return chunk;
- }));
-
- chunks.push(msg);
- return chunks.join(' ');
-};
diff --git a/app/ui-utils/client/lib/rtl.js b/app/ui-utils/client/lib/rtl.js
deleted file mode 100644
index 64a55f8dbc1f..000000000000
--- a/app/ui-utils/client/lib/rtl.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// http://stackoverflow.com/a/14824756
-
-const ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
-const rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
-const rtlRegExp = new RegExp(`^[^${ ltrChars }]*[${ rtlChars }]`);
-
-export const isRTL = (text) => rtlRegExp.test(text);
diff --git a/app/ui-utils/client/lib/warnUserDeletionMayRemoveRooms.js b/app/ui-utils/client/lib/warnUserDeletionMayRemoveRooms.js
deleted file mode 100644
index 31c36278c996..000000000000
--- a/app/ui-utils/client/lib/warnUserDeletionMayRemoveRooms.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-
-import { t } from '../../../utils/client';
-import { modal } from './modal';
-
-export const warnUserDeletionMayRemoveRooms = async function(userId, callbackFn, { warningKey, confirmButtonKey, closeOnConfirm = false, skipModalIfEmpty = false, shouldChangeOwner, shouldBeRemoved }) {
- let warningText = warningKey ? t(warningKey) : false;
-
- if (shouldBeRemoved.length + shouldChangeOwner.length === 0 && skipModalIfEmpty) {
- callbackFn();
- return;
- }
-
- if (shouldChangeOwner.length > 0) {
- let newText;
-
- if (shouldChangeOwner.length === 1) {
- newText = TAPi18n.__('A_new_owner_will_be_assigned_automatically_to_the__roomName__room', { roomName: shouldChangeOwner.pop() });
- } else if (shouldChangeOwner.length <= 5) {
- newText = TAPi18n.__('A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__', { count: shouldChangeOwner.length, rooms: shouldChangeOwner.join(', ') });
- } else {
- newText = TAPi18n.__('A_new_owner_will_be_assigned_automatically_to__count__rooms', { count: shouldChangeOwner.length });
- }
-
- if (warningText) {
- warningText = `${ warningText }
${ newText }
`;
- } else {
- warningText = newText;
- }
- }
-
- if (shouldBeRemoved.length > 0) {
- let newText;
-
- if (shouldBeRemoved.length === 1) {
- newText = TAPi18n.__('The_empty_room__roomName__will_be_removed_automatically', { roomName: shouldBeRemoved.pop() });
- } else if (shouldBeRemoved.length <= 5) {
- newText = TAPi18n.__('__count__empty_rooms_will_be_removed_automatically__rooms__', { count: shouldBeRemoved.length, rooms: shouldBeRemoved.join(', ') });
- } else {
- newText = TAPi18n.__('__count__empty_rooms_will_be_removed_automatically', { count: shouldBeRemoved.length });
- }
-
- if (warningText) {
- warningText = `${ warningText }
${ newText }
`;
- } else {
- warningText = newText;
- }
- }
-
- modal.open({
- title: t('Are_you_sure'),
- text: warningText,
- type: 'warning',
- showCancelButton: true,
- confirmButtonColor: '#DD6B55',
- confirmButtonText: t(confirmButtonKey || 'Yes_delete_it'),
- cancelButtonText: t('Cancel'),
- closeOnConfirm,
- html: true,
- }, () => {
- callbackFn();
- });
-};
diff --git a/app/ui-vrecord/client/vrecord.js b/app/ui-vrecord/client/vrecord.js
index c476ffadff00..463c38bddc8c 100644
--- a/app/ui-vrecord/client/vrecord.js
+++ b/app/ui-vrecord/client/vrecord.js
@@ -4,7 +4,7 @@ import { ReactiveVar } from 'meteor/reactive-var';
import _ from 'underscore';
import { VRecDialog } from './VRecDialog';
-import { VideoRecorder, fileUpload } from '../../ui';
+import { VideoRecorder, fileUpload, UserAction, USER_ACTIVITIES } from '../../ui';
Template.vrecDialog.helpers({
recordIcon() {
@@ -33,26 +33,35 @@ Template.vrecDialog.helpers({
const recordingInterval = new ReactiveVar(null);
+const stopVideoRecording = (rid, tmid) => {
+ if (recordingInterval.get()) {
+ clearInterval(recordingInterval.get());
+ recordingInterval.set(null);
+ }
+ UserAction.stop(rid, USER_ACTIVITIES.USER_RECORDING, { tmid });
+};
+
+
Template.vrecDialog.events({
'click .vrec-dialog .cancel'(e, t) {
+ const rid = t.rid.get();
+ const tmid = t.tmid.get();
+
VideoRecorder.stop();
VRecDialog.close();
t.time.set('');
- if (recordingInterval.get()) {
- clearInterval(recordingInterval.get());
- recordingInterval.set(null);
- }
+ stopVideoRecording(rid, tmid);
},
'click .vrec-dialog .record'(e, t) {
+ const rid = t.rid.get();
+ const tmid = t.tmid.get();
if (VideoRecorder.recording.get()) {
VideoRecorder.stopRecording();
- if (recordingInterval.get()) {
- clearInterval(recordingInterval.get());
- recordingInterval.set(null);
- }
+ stopVideoRecording(rid, tmid);
} else {
VideoRecorder.record();
+ UserAction.performContinuously(rid, USER_ACTIVITIES.USER_RECORDING, { tmid });
t.time.set('00:00');
const startTime = new Date();
recordingInterval.set(setInterval(() => {
@@ -73,10 +82,7 @@ Template.vrecDialog.events({
};
VideoRecorder.stop(cb);
instance.time.set('');
- if (recordingInterval.get()) {
- clearInterval(recordingInterval.get());
- recordingInterval.set(null);
- }
+ stopVideoRecording(rid, tmid);
},
});
@@ -124,5 +130,6 @@ Template.vrecDialog.onCreated(function() {
});
Template.vrecDialog.onDestroyed(function() {
+ VRecDialog.close(this.rid.get());
$(window).off('resize', this.remove);
});
diff --git a/app/ui/client/components/header/header.js b/app/ui/client/components/header/header.js
index 205d52a227fc..98f427ac2f39 100644
--- a/app/ui/client/components/header/header.js
+++ b/app/ui/client/components/header/header.js
@@ -1,6 +1,7 @@
import { Template } from 'meteor/templating';
-import { fireGlobalEvent } from '../../../../ui-utils';
+import { fireGlobalEvent } from '../../../../../client/lib/utils/fireGlobalEvent';
+
import './header.html';
Template.header.helpers({
diff --git a/app/ui/client/components/icon.js b/app/ui/client/components/icon.js
index 45ef63a494b8..e49bec52392e 100644
--- a/app/ui/client/components/icon.js
+++ b/app/ui/client/components/icon.js
@@ -1,7 +1,7 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
-import { baseURI } from '../../../utils/client/lib/baseuri';
+import { baseURI } from '../../../../client/lib/baseURI';
import './icon.html';
diff --git a/app/ui/client/index.js b/app/ui/client/index.js
index 93d84c46e7b3..486910fcde0f 100644
--- a/app/ui/client/index.js
+++ b/app/ui/client/index.js
@@ -47,7 +47,7 @@ import './lib/Tooltip';
export { ChatMessages } from './lib/chatMessages';
export { fileUpload } from './lib/fileUpload';
-export { MsgTyping } from './lib/msgTyping';
+export { UserAction, USER_ACTIVITIES } from './lib/UserAction';
export { KonchatNotification } from './lib/notification';
export { Login, Button } from './lib/rocket';
export { sendOfflineFileMessage } from './lib/sendOfflineFileMessage';
diff --git a/app/ui/client/lib/Tooltip.js b/app/ui/client/lib/Tooltip.js
index 862104b96899..eba4f5ddd894 100644
--- a/app/ui/client/lib/Tooltip.js
+++ b/app/ui/client/lib/Tooltip.js
@@ -1,6 +1,7 @@
import { Tracker } from 'meteor/tracker';
import { createEphemeralPortal } from '../../../../client/lib/portals/createEphemeralPortal';
+import { createAnchor } from '../../../../client/lib/utils/createAnchor';
const Dep = new Tracker.Dependency();
@@ -8,14 +9,6 @@ let state;
let dom;
let unregister;
-const createAnchor = () => {
- const div = document.createElement('div');
- div.id = 'react-tooltip';
- document.body.appendChild(div);
- return div;
-};
-
-
const props = () => {
Dep.depend();
return state;
@@ -29,7 +22,7 @@ export const closeTooltip = () => {
};
export const openToolTip = (title, anchor) => {
- dom = dom || createAnchor();
+ dom = dom || createAnchor('react-tooltip');
state = {
title,
anchor,
@@ -38,7 +31,7 @@ export const openToolTip = (title, anchor) => {
unregister = unregister || createEphemeralPortal(() => import('./TooltipComponent'), props, dom);
};
-document.body.addEventListener('mouseover', (() => {
+window.matchMedia('(hover: none)').matches || document.body.addEventListener('mouseover', (() => {
let timeout;
return (e) => {
timeout = timeout && clearTimeout(timeout);
diff --git a/app/ui/client/lib/UserAction.ts b/app/ui/client/lib/UserAction.ts
new file mode 100644
index 000000000000..847de8e19079
--- /dev/null
+++ b/app/ui/client/lib/UserAction.ts
@@ -0,0 +1,165 @@
+import { Meteor } from 'meteor/meteor';
+import { Tracker } from 'meteor/tracker';
+import { ReactiveDict } from 'meteor/reactive-dict';
+import { Session } from 'meteor/session';
+import { debounce } from 'lodash';
+
+import { settings } from '../../../settings/client';
+import { Notifications } from '../../../notifications/client';
+import { IExtras, IRoomActivity, IActionsObject, IUser } from '../../../../definition/IUserAction';
+
+const TIMEOUT = 15000;
+const RENEW = TIMEOUT / 3;
+
+export const USER_ACTIVITY = 'user-activity';
+
+export const USER_ACTIVITIES = {
+ USER_RECORDING: 'user-recording',
+ USER_TYPING: 'user-typing',
+ USER_UPLOADING: 'user-uploading',
+};
+
+const activityTimeouts = new Map();
+const activityRenews = new Map();
+const continuingIntervals = new Map();
+const roomActivities = new Map
>();
+const rooms = new Map();
+
+const performingUsers = new ReactiveDict();
+
+const shownName = function(user: IUser | null | undefined): string|undefined {
+ if (!user) {
+ return;
+ }
+ if (settings.get('UI_Use_Real_Name')) {
+ return user.name;
+ }
+ return user.username;
+};
+
+const emitActivities = debounce((rid: string, extras: IExtras): void => {
+ const activities = roomActivities.get(extras?.tmid || rid) || new Set();
+ Notifications.notifyRoom(rid, USER_ACTIVITY, shownName(Meteor.user()), [...activities], extras);
+}, 500);
+
+function handleStreamAction(rid: string, username: string, activityTypes: string[], extras?: IExtras): void {
+ rid = extras?.tmid || rid;
+ const roomActivities = performingUsers.get(rid) || {};
+
+ for (const [, activity] of Object.entries(USER_ACTIVITIES)) {
+ roomActivities[activity] = roomActivities[activity] || new Map();
+ const users = roomActivities[activity];
+ const timeout = users[username];
+
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+
+ if (activityTypes.includes(activity)) {
+ activityTypes.splice(activityTypes.indexOf(activity), 1);
+ users[username] = setTimeout(() => handleStreamAction(rid, username, activityTypes, extras), TIMEOUT);
+ } else {
+ delete users[username];
+ }
+ }
+
+ performingUsers.set(rid, roomActivities);
+}
+export const UserAction = new class {
+ constructor() {
+ Tracker.autorun(() => Session.get('openedRoom') && this.addStream(Session.get('openedRoom')));
+ }
+
+ addStream(rid: string): void {
+ if (rooms.get(rid)) {
+ return;
+ }
+ const handler = function(username: string, activityType: string[], extras?: object): void {
+ const user = Meteor.users.findOne(Meteor.userId() || undefined, { fields: { name: 1, username: 1 } });
+ if (username === shownName(user)) {
+ return;
+ }
+ handleStreamAction(rid, username, activityType, extras);
+ };
+ rooms.set(rid, handler);
+ Notifications.onRoom(rid, USER_ACTIVITY, handler);
+ }
+
+ performContinuously(rid: string, activityType: string, extras: IExtras = {}): void {
+ const trid = extras?.tmid || rid;
+ const key = `${ activityType }-${ trid }`;
+
+ if (continuingIntervals.get(key)) {
+ return;
+ }
+ this.start(rid, activityType, extras);
+
+ continuingIntervals.set(key, setInterval(() => {
+ this.start(rid, activityType, extras);
+ }, RENEW));
+ }
+
+ start(rid: string, activityType: string, extras: IExtras = {}): void {
+ const trid = extras?.tmid || rid;
+ const key = `${ activityType }-${ trid }`;
+
+ if (activityRenews.get(key)) {
+ return;
+ }
+
+ activityRenews.set(key, setTimeout(() => {
+ clearTimeout(activityRenews.get(key));
+ activityRenews.delete(key);
+ }, RENEW));
+
+ const activities = roomActivities.get(trid) || new Set();
+ activities.add(activityType);
+ roomActivities.set(trid, activities);
+
+ emitActivities(rid, extras);
+
+ if (activityTimeouts.get(key)) {
+ clearTimeout(activityTimeouts.get(key));
+ activityTimeouts.delete(key);
+ }
+
+ activityTimeouts.set(key, setTimeout(() => this.stop(trid, activityType, extras), TIMEOUT));
+ activityTimeouts.get(key);
+ }
+
+ stop(rid: string, activityType: string, extras: IExtras): void {
+ const trid = extras?.tmid || rid;
+ const key = `${ activityType }-${ trid }`;
+
+ if (activityTimeouts.get(key)) {
+ clearTimeout(activityTimeouts.get(key));
+ activityTimeouts.delete(key);
+ }
+ if (activityRenews.get(key)) {
+ clearTimeout(activityRenews.get(key));
+ activityRenews.delete(key);
+ }
+ if (continuingIntervals.get(key)) {
+ clearInterval(continuingIntervals.get(key));
+ continuingIntervals.delete(key);
+ }
+
+ const activities = roomActivities.get(trid) || new Set();
+ activities.delete(activityType);
+ roomActivities.set(trid, activities);
+ emitActivities(rid, extras);
+ }
+
+ cancel(rid: string): void {
+ if (!rooms.get(rid)) {
+ return;
+ }
+
+ Notifications.unRoom(rid, USER_ACTIVITY, rooms.get(rid));
+ rooms.delete(rid);
+ }
+
+ get(roomId: string): IRoomActivity | undefined {
+ return performingUsers.get(roomId);
+ }
+}();
diff --git a/app/ui/client/lib/chatMessages.js b/app/ui/client/lib/chatMessages.js
index 35042378fbf0..7d8290fa89a8 100644
--- a/app/ui/client/lib/chatMessages.js
+++ b/app/ui/client/lib/chatMessages.js
@@ -9,17 +9,14 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { escapeHTML } from '@rocket.chat/string-helpers';
import { KonchatNotification } from './notification';
-import { MsgTyping } from './msgTyping';
+import { UserAction, USER_ACTIVITIES } from '../index';
import { fileUpload } from './fileUpload';
-import { t, slashCommands, handleError } from '../../../utils/client';
+import { t, slashCommands } from '../../../utils/client';
import {
messageProperties,
MessageTypes,
readMessage,
modal,
- call,
- keyCodes,
- prependReplies,
} from '../../../ui-utils/client';
import { settings } from '../../../settings/client';
import { callbacks } from '../../../callbacks/client';
@@ -30,6 +27,10 @@ import { emoji } from '../../../emoji/client';
import { generateTriggerId } from '../../../ui-message/client/ActionManager';
import { imperativeModal } from '../../../../client/lib/imperativeModal';
import GenericModal from '../../../../client/components/GenericModal';
+import { keyCodes } from '../../../../client/lib/utils/keyCodes';
+import { prependReplies } from '../../../../client/lib/utils/prependReplies';
+import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
+import { handleError } from '../../../../client/lib/utils/handleError';
const messageBoxState = {
@@ -102,7 +103,7 @@ export class ChatMessages {
return;
}
- const message = Messages.findOne(mid) || await call('getSingleMessage', mid);
+ const message = Messages.findOne(mid) || await callWithErrorHandling('getSingleMessage', mid);
if (!message) {
return;
}
@@ -254,10 +255,10 @@ export class ChatMessages {
async send(event, { rid, tmid, value, tshow }, done = () => {}) {
const threadsEnabled = settings.get('Threads_enabled');
- MsgTyping.stop(rid);
+ UserAction.stop(rid, USER_ACTIVITIES.USER_TYPING, { tmid });
if (!ChatSubscription.findOne({ rid })) {
- await call('joinRoom', rid);
+ await callWithErrorHandling('joinRoom', rid);
}
messageBoxState.save({ rid, tmid }, this.input);
@@ -345,7 +346,7 @@ export class ChatMessages {
return;
}
- await call('sendMessage', message);
+ await callWithErrorHandling('sendMessage', message);
}
async processSetReaction({ rid, tmid, msg }) {
@@ -359,7 +360,7 @@ export class ChatMessages {
}
const lastMessage = this.collection.findOne({ rid, tmid }, { fields: { ts: 1 }, sort: { ts: -1 } });
- await call('setReaction', reaction, lastMessage._id);
+ await callWithErrorHandling('setReaction', reaction, lastMessage._id);
return true;
}
@@ -406,7 +407,7 @@ export class ChatMessages {
}
this.clearEditing();
- await call('updateMessage', message);
+ await callWithErrorHandling('updateMessage', message);
return true;
}
@@ -521,7 +522,7 @@ export class ChatMessages {
}
- await call('deleteMessage', { _id });
+ await callWithErrorHandling('deleteMessage', { _id });
}
keydown(event) {
@@ -579,9 +580,9 @@ export class ChatMessages {
if (!Object.values(keyCodes).includes(keyCode)) {
if (input.value.trim()) {
- MsgTyping.start(rid);
+ UserAction.start(rid, USER_ACTIVITIES.USER_TYPING, { tmid });
} else {
- MsgTyping.stop(rid);
+ UserAction.stop(rid, USER_ACTIVITIES.USER_TYPING, { tmid });
}
}
@@ -589,6 +590,6 @@ export class ChatMessages {
}
onDestroyed(rid) {
- MsgTyping.cancel(rid);
+ UserAction.cancel(rid);
}
}
diff --git a/app/ui/client/lib/fileUpload.js b/app/ui/client/lib/fileUpload.js
index af54aedea9ef..551c2d647d5d 100644
--- a/app/ui/client/lib/fileUpload.js
+++ b/app/ui/client/lib/fileUpload.js
@@ -3,10 +3,11 @@ import { Random } from 'meteor/random';
import { Session } from 'meteor/session';
import { settings } from '../../../settings/client';
+import { UserAction, USER_ACTIVITIES } from '../index';
import { fileUploadIsValidContentType, APIClient } from '../../../utils';
-import { prependReplies } from '../../../ui-utils';
import { imperativeModal } from '../../../../client/lib/imperativeModal';
import FileUploadModal from '../../../../client/components/modals/FileUploadModal';
+import { prependReplies } from '../../../../client/lib/utils/prependReplies';
export const uploadFileWithMessage = async (rid, tmid, { description, fileName, msg, file }) => {
const data = new FormData();
@@ -47,6 +48,9 @@ export const uploadFileWithMessage = async (rid, tmid, { description, fileName,
Session.set('uploading', uploads);
},
});
+ if (Session.get('uploading').length) {
+ UserAction.performContinuously(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid });
+ }
Tracker.autorun((computation) => {
const isCanceling = Session.get(`uploading-cancel-${ upload.id }`);
@@ -65,13 +69,21 @@ export const uploadFileWithMessage = async (rid, tmid, { description, fileName,
try {
await promise;
const uploads = Session.get('uploading') || [];
- return Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
+ const remainingUploads = Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
+
+ if (!Session.get('uploading').length) {
+ UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid });
+ }
+ return remainingUploads;
} catch (error) {
const uploads = Session.get('uploading') || [];
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.error = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message;
u.percentage = 0;
});
+ if (!uploads.length) {
+ UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid });
+ }
Session.set('uploading', uploads);
}
};
diff --git a/app/ui/client/lib/iframeCommands.js b/app/ui/client/lib/iframeCommands.js
index 22b69694993c..77b1d1982a92 100644
--- a/app/ui/client/lib/iframeCommands.js
+++ b/app/ui/client/lib/iframeCommands.js
@@ -7,8 +7,8 @@ import { escapeRegExp } from '@rocket.chat/string-helpers';
import { AccountBox } from '../../../ui-utils';
import { settings } from '../../../settings';
import { callbacks } from '../../../callbacks';
-import { baseURI } from '../../../utils/client/lib/baseuri.js';
import { add, remove } from '../../../../client/views/room/lib/Toolbox/IframeButtons';
+import { baseURI } from '../../../../client/lib/baseURI';
const commands = {
go(data) {
diff --git a/app/ui/client/lib/msgTyping.js b/app/ui/client/lib/msgTyping.js
deleted file mode 100644
index 660cf75e7e74..000000000000
--- a/app/ui/client/lib/msgTyping.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { Tracker } from 'meteor/tracker';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { ReactiveDict } from 'meteor/reactive-dict';
-import { Session } from 'meteor/session';
-import _ from 'underscore';
-
-import { settings } from '../../../settings';
-import { Notifications } from '../../../notifications';
-
-const shownName = function(user) {
- if (!user) {
- return;
- }
- if (settings.get('UI_Use_Real_Name')) {
- return user.name;
- }
- return user.username;
-};
-
-const timeouts = {};
-const timeout = 15000;
-const renew = timeout / 3;
-const renews = {};
-const rooms = {};
-const selfTyping = new ReactiveVar(false);
-const usersTyping = new ReactiveDict();
-
-const stopTyping = (rid) => Notifications.notifyRoom(rid, 'typing', shownName(Meteor.user()), false);
-const typing = (rid) => Notifications.notifyRoom(rid, 'typing', shownName(Meteor.user()), true);
-
-export const MsgTyping = new class {
- constructor() {
- Tracker.autorun(() => Session.get('openedRoom') && this.addStream(Session.get('openedRoom')));
- }
-
- get selfTyping() { return selfTyping.get(); }
-
- cancel(rid) {
- if (rooms[rid]) {
- Notifications.unRoom(rid, 'typing', rooms[rid]);
- Object.values(usersTyping.get(rid) || {}).forEach(clearTimeout);
- usersTyping.set(rid);
- delete rooms[rid];
- }
- }
-
- addStream(rid) {
- if (rooms[rid]) {
- return;
- }
- rooms[rid] = function(username, typing) {
- const user = Meteor.users.findOne(Meteor.userId(), { fields: { name: 1, username: 1 } });
- if (username === shownName(user)) {
- return;
- }
- const users = usersTyping.get(rid) || {};
- if (typing === true) {
- clearTimeout(users[username]);
- users[username] = setTimeout(function() {
- const u = usersTyping.get(rid);
- delete u[username];
- usersTyping.set(rid, u);
- }, timeout);
- } else {
- delete users[username];
- }
-
- usersTyping.set(rid, users);
- };
- return Notifications.onRoom(rid, 'typing', rooms[rid]);
- }
-
- stop(rid) {
- selfTyping.set(false);
- if (timeouts[rid]) {
- clearTimeout(timeouts[rid]);
- delete timeouts[rid];
- delete renews[rid];
- }
- return stopTyping(rid);
- }
-
-
- start(rid) {
- selfTyping.set(true);
-
- if (renews[rid]) {
- return;
- }
-
- renews[rid] = setTimeout(() => delete renews[rid], renew);
-
- typing(rid);
-
- if (timeouts[rid]) {
- clearTimeout(timeouts[rid]);
- }
-
- timeouts[rid] = setTimeout(() => this.stop(rid), timeout);
-
- return timeouts[rid];
- }
-
-
- get(rid) {
- return _.keys(usersTyping.get(rid)) || [];
- }
-}();
diff --git a/app/ui/client/lib/notification.js b/app/ui/client/lib/notification.js
index 51f370835983..9aaff3b7f4b7 100644
--- a/app/ui/client/lib/notification.js
+++ b/app/ui/client/lib/notification.js
@@ -12,9 +12,9 @@ import { e2e } from '../../../e2e/client';
import { Users, ChatSubscription } from '../../../models';
import { getUserPreference } from '../../../utils';
import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL';
-import { getAvatarAsPng } from '../../../ui-utils';
import { promises } from '../../../promises/client';
import { CustomSounds } from '../../../custom-sounds/client/lib/CustomSounds';
+import { getAvatarAsPng } from '../../../../client/lib/utils/getAvatarAsPng';
export const KonchatNotification = {
notificationStatus: new ReactiveVar(),
@@ -101,24 +101,35 @@ export const KonchatNotification = {
},
newMessage(rid) {
- if (!Session.equals(`user_${ Meteor.user().username }_status`, 'busy')) {
- const userId = Meteor.userId();
- const newMessageNotification = getUserPreference(userId, 'newMessageNotification');
- const audioVolume = getUserPreference(userId, 'notificationsSoundVolume');
+ if (Session.equals(`user_${ Meteor.user().username }_status`, 'busy')) {
+ return;
+ }
- const sub = ChatSubscription.findOne({ rid }, { fields: { audioNotificationValue: 1 } });
+ const userId = Meteor.userId();
+ const newMessageNotification = getUserPreference(userId, 'newMessageNotification');
+ const audioVolume = getUserPreference(userId, 'notificationsSoundVolume');
- if (sub && sub.audioNotificationValue !== 'none') {
- if (sub && sub.audioNotificationValue && sub.audioNotificationValue !== '0') {
- CustomSounds.play(sub.audioNotificationValue, {
- volume: Number((audioVolume / 100).toPrecision(2)),
- });
- } else if (newMessageNotification !== 'none') {
- CustomSounds.play(newMessageNotification, {
- volume: Number((audioVolume / 100).toPrecision(2)),
- });
- }
+ const sub = ChatSubscription.findOne({ rid }, { fields: { audioNotificationValue: 1 } });
+
+ if (!sub || sub.audioNotificationValue === 'none') {
+ return;
+ }
+
+ try {
+ if (sub.audioNotificationValue && sub.audioNotificationValue !== '0') {
+ CustomSounds.play(sub.audioNotificationValue, {
+ volume: Number((audioVolume / 100).toPrecision(2)),
+ });
+ return;
+ }
+
+ if (newMessageNotification !== 'none') {
+ CustomSounds.play(newMessageNotification, {
+ volume: Number((audioVolume / 100).toPrecision(2)),
+ });
}
+ } catch (e) {
+ // do nothing
}
},
diff --git a/app/ui/client/views/app/burger.js b/app/ui/client/views/app/burger.js
index a7e34daa2b80..53d2e5d52692 100644
--- a/app/ui/client/views/app/burger.js
+++ b/app/ui/client/views/app/burger.js
@@ -3,7 +3,8 @@ import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { ChatSubscription } from '../../../../models/client';
-import { Layout, menu } from '../../../../ui-utils/client';
+import { menu } from '../../../../ui-utils/client';
+import { isLayoutEmbedded } from '../../../../../client/lib/utils/isLayoutEmbedded';
import { getUserPreference } from '../../../../utils';
Template.burger.helpers({
@@ -50,7 +51,7 @@ Template.burger.helpers({
},
embeddedVersion() {
- return Layout.isEmbedded();
+ return isLayoutEmbedded();
},
});
diff --git a/app/ui/client/views/app/lib/getCommonRoomEvents.js b/app/ui/client/views/app/lib/getCommonRoomEvents.js
index c273067e36ee..869464451fa0 100644
--- a/app/ui/client/views/app/lib/getCommonRoomEvents.js
+++ b/app/ui/client/views/app/lib/getCommonRoomEvents.js
@@ -4,15 +4,13 @@ import { Random } from 'meteor/random';
import { FlowRouter } from 'meteor/kadira:flow-router';
import {
- fireGlobalEvent,
popover,
- Layout,
MessageAction,
} from '../../../../../ui-utils/client';
import {
addMessageToList,
} from '../../../../../ui-utils/client/lib/MessageAction';
-import { call } from '../../../../../ui-utils/client/lib/callMethod';
+import { callWithErrorHandling } from '../../../../../../client/lib/utils/callWithErrorHandling';
import { promises } from '../../../../../promises/client';
import { isURL } from '../../../../../utils/lib/isURL';
import { openUserCard } from '../../../lib/UserCard';
@@ -21,7 +19,9 @@ import { ChatMessage, Rooms, Messages } from '../../../../../models';
import { t } from '../../../../../utils/client';
import { chatMessages } from '../room';
import { EmojiEvents } from '../../../../../reactions/client/init';
-import { goToRoomById } from '../../../../../../client/lib/goToRoomById';
+import { goToRoomById } from '../../../../../../client/lib/utils/goToRoomById';
+import { fireGlobalEvent } from '../../../../../../client/lib/utils/fireGlobalEvent';
+import { isLayoutEmbedded } from '../../../../../../client/lib/utils/isLayoutEmbedded';
const mountPopover = (e, i, outerContext) => {
let context = $(e.target).parents('.message').data('context');
@@ -163,13 +163,13 @@ export const getCommonRoomEvents = () => ({
e.preventDefault();
e.stopPropagation();
const { msg } = messageArgs(this);
- call('followMessage', { mid: msg._id });
+ callWithErrorHandling('followMessage', { mid: msg._id });
},
'click .js-unfollow-thread'(e) {
e.preventDefault();
e.stopPropagation();
const { msg } = messageArgs(this);
- call('unfollowMessage', { mid: msg._id });
+ callWithErrorHandling('unfollowMessage', { mid: msg._id });
},
'click .js-open-thread'(event) {
event.preventDefault();
@@ -259,7 +259,7 @@ export const getCommonRoomEvents = () => ({
return;
}
- await call('sendMessage', msgObject);
+ await callWithErrorHandling('sendMessage', msgObject);
},
'click .message-actions__menu'(e, template) {
const messageContext = messageArgs(this);
@@ -311,7 +311,7 @@ export const getCommonRoomEvents = () => ({
const { currentTarget: { dataset: { channel, group, username } } } = e;
if (channel) {
- if (Layout.isEmbedded()) {
+ if (isLayoutEmbedded()) {
fireGlobalEvent('click-mention-link', { path: FlowRouter.path('channel', { name: channel }), channel });
}
goToRoomById(channel);
diff --git a/app/ui/client/views/app/room.js b/app/ui/client/views/app/room.js
index 9d3bc3ca3b57..e0880116c225 100644
--- a/app/ui/client/views/app/room.js
+++ b/app/ui/client/views/app/room.js
@@ -10,15 +10,13 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
-import { t, roomTypes, getUserPreference, handleError } from '../../../../utils/client';
+import { t, roomTypes, getUserPreference } from '../../../../utils/client';
import { WebRTC } from '../../../../webrtc/client';
import { ChatMessage, RoomRoles, Users, Subscriptions, Rooms } from '../../../../models';
import {
- fireGlobalEvent,
RoomHistoryManager,
RoomManager,
readMessage,
- Layout,
} from '../../../../ui-utils/client';
import { messageContext } from '../../../../ui-utils/client/lib/messageContext';
import { messageArgs } from '../../../../ui-utils/client/lib/messageArgs';
@@ -30,6 +28,9 @@ import { fileUpload } from '../../lib/fileUpload';
import './room.html';
import { getCommonRoomEvents } from './lib/getCommonRoomEvents';
import { RoomManager as NewRoomManager } from '../../../../../client/lib/RoomManager';
+import { fireGlobalEvent } from '../../../../../client/lib/utils/fireGlobalEvent';
+import { isLayoutEmbedded } from '../../../../../client/lib/utils/isLayoutEmbedded';
+import { handleError } from '../../../../../client/lib/utils/handleError';
export const chatMessages = {};
@@ -39,7 +40,7 @@ const userCanDrop = (_id) => !roomTypes.readOnly(_id, Users.findOne({ _id: Meteo
export const openProfileTab = (e, tabBar, username) => {
e.stopPropagation();
- if (Layout.isEmbedded()) {
+ if (isLayoutEmbedded()) {
fireGlobalEvent('click-user-card-message', { username });
e.preventDefault();
return;
@@ -269,7 +270,7 @@ Template.roomOld.helpers({
messageboxData() {
const { sendToBottomIfNecessary, subscription } = Template.instance();
const { _id: rid } = this;
- const isEmbedded = Layout.isEmbedded();
+ const isEmbedded = isLayoutEmbedded();
const showFormattingTips = settings.get('Message_ShowFormattingTips');
return {
diff --git a/app/utils/client/index.js b/app/utils/client/index.js
index f9f13ae83b4a..b53382341371 100644
--- a/app/utils/client/index.js
+++ b/app/utils/client/index.js
@@ -2,7 +2,6 @@ export { t, isRtl } from '../lib/tapi18n';
export { getDefaultSubscriptionPref } from '../lib/getDefaultSubscriptionPref';
export { Info } from '../rocketchat.info';
export { isEmail } from '../lib/isEmail';
-export { handleError } from './lib/handleError';
export { getUserPreference } from '../lib/getUserPreference';
export { fileUploadMediaWhiteList, fileUploadIsValidContentType } from '../lib/fileUploadRestrictions';
export { roomTypes } from './lib/roomTypes';
@@ -11,17 +10,14 @@ export { RoomTypesCommon } from '../lib/RoomTypesCommon';
export { getUserAvatarURL } from '../lib/getUserAvatarURL';
export { slashCommands } from '../lib/slashCommand';
export { getUserNotificationPreference } from '../lib/getUserNotificationPreference';
-export { applyCustomTranslations } from './lib/CustomTranslations';
export { getAvatarColor } from '../lib/getAvatarColor';
export { getURL } from '../lib/getURL';
export { placeholders } from '../lib/placeholders';
export { templateVarHandler } from '../lib/templateVarHandler';
-export { APIClient, mountArrayQueryParameters } from './lib/RestApiClient';
-export { canDeleteMessage } from './lib/canDeleteMessage';
+export { APIClient } from './lib/RestApiClient';
+export { secondsToHHMMSS } from '../lib/timeConverter';
export { SWCache } from './lib/swCache';
export { cleanMessagesAtStartup, triggerOfflineMsgs } from './lib/offlineMessages';
-export { secondsToHHMMSS } from '../lib/timeConverter';
-export { waitUntilFind } from './lib/waitUntilFind';
export { isMobile } from './lib/isMobile';
export { hex_sha1 } from './lib/sha1';
export { share, isShareAvailable, getShareData } from './lib/share';
diff --git a/app/utils/client/lib/CustomTranslations.js b/app/utils/client/lib/CustomTranslations.js
deleted file mode 100644
index 7754e9a609e5..000000000000
--- a/app/utils/client/lib/CustomTranslations.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { TAPi18n, TAPi18next } from 'meteor/rocketchat:tap-i18n';
-
-import { settings } from '../../../settings';
-
-export function applyCustomTranslations() {
- let CustomTranslations = settings.get('Custom_Translations');
- if (typeof CustomTranslations === 'string' && CustomTranslations.trim() !== '') {
- try {
- CustomTranslations = JSON.parse(CustomTranslations);
-
- for (const lang in CustomTranslations) {
- if (CustomTranslations.hasOwnProperty(lang)) {
- const translations = CustomTranslations[lang];
- TAPi18next.addResourceBundle(lang, 'project', translations);
- }
- }
- TAPi18n._language_changed_tracker.changed();
- } catch (e) {
- console.error('Invalid setting Custom_Translations', e);
- }
- }
-}
diff --git a/app/utils/client/lib/RestApiClient.js b/app/utils/client/lib/RestApiClient.js
index 863e32fbef1d..5dae274c803c 100644
--- a/app/utils/client/lib/RestApiClient.js
+++ b/app/utils/client/lib/RestApiClient.js
@@ -1,13 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
-import { baseURI } from './baseuri';
import { process2faReturn } from '../../../2fa/client/callWithTwoFactorRequired';
-
-export const mountArrayQueryParameters = (label, array) => array.reduce((acc, item) => {
- acc += `${ label }[]=${ item }&`;
- return acc;
-}, '');
+import { baseURI } from '../../../../client/lib/baseURI';
export const APIClient = {
delete(endpoint, params) {
@@ -50,10 +45,11 @@ export const APIClient = {
query += query === '' ? '?' : '&';
if (Array.isArray(params[key])) {
- const joinedArray = params[key].join(`&${ key }[]=`);
+ const encodedParams = params[key].map((value) => encodeURIComponent(value));
+ const joinedArray = encodedParams.join(`&${ key }[]=`);
query += `${ key }[]=${ joinedArray }`;
} else {
- query += `${ key }=${ params[key] }`;
+ query += `${ key }=${ encodeURIComponent(params[key]) }`;
}
});
}
diff --git a/app/utils/client/lib/baseuri.js b/app/utils/client/lib/baseuri.js
deleted file mode 100644
index a0545b56ecd9..000000000000
--- a/app/utils/client/lib/baseuri.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-export const baseURI = (() => {
- if (document.baseURI) { return document.baseURI; }
-
- // Should be exactly one tag:
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
- const base = document.getElementsByTagName('base');
-
- // Return location from BASE tag.
- if (base.length > 0) { return base[0].href; }
-
- // Else use implementation of documentURI:
- // http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-baseURI
- return document.URL;
-})();
-
-Meteor.absoluteUrl.defaultOptions = { ...Meteor.absoluteUrl.defaultOptions, rootUrl: baseURI };
diff --git a/app/utils/client/lib/canDeleteMessage.js b/app/utils/client/lib/canDeleteMessage.js
deleted file mode 100644
index 3f35d5974529..000000000000
--- a/app/utils/client/lib/canDeleteMessage.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import moment from 'moment';
-
-import { hasPermission } from '../../../authorization/client';
-import { settings } from '../../../settings/client';
-
-export const canDeleteMessage = ({ rid, ts, uid }) => {
- const userId = Meteor.userId();
-
- const forceDelete = hasPermission('force-delete-message', rid);
- if (forceDelete) {
- return true;
- }
-
- const isDeleteAllowed = settings.get('Message_AllowDeleting');
- if (!isDeleteAllowed) {
- return false;
- }
-
- const allowed = hasPermission('delete-message', rid);
-
- const deleteOwn = allowed || (uid === userId && hasPermission('delete-own-message'));
- if (!allowed && !deleteOwn) {
- return false;
- }
-
- const blockDeleteInMinutes = settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
- if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
- let msgTs;
- if (ts != null) {
- msgTs = moment(ts);
- }
- let currentTsDiff;
- if (msgTs != null) {
- currentTsDiff = moment().diff(msgTs, 'minutes');
- }
- return currentTsDiff < blockDeleteInMinutes;
- }
-
- return true;
-};
diff --git a/app/utils/client/lib/handleError.js b/app/utils/client/lib/handleError.js
deleted file mode 100644
index 120c3b7d41f3..000000000000
--- a/app/utils/client/lib/handleError.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import _ from 'underscore';
-import toastr from 'toastr';
-import { escapeHTML } from '@rocket.chat/string-helpers';
-
-export const handleError = function(error, useToastr = true) {
- if (error.xhr) {
- error = error.xhr.responseJSON || {};
- }
-
- if (_.isObject(error.details)) {
- for (const key in error.details) {
- if (error.details.hasOwnProperty(key)) {
- error.details[key] = TAPi18n.__(error.details[key]);
- }
- }
- }
-
- if (useToastr) {
- if (error.toastrShowed) {
- return;
- }
- const details = Object.entries(error.details || {})
- .reduce((obj, [key, value]) => ({ ...obj, [key]: escapeHTML(value) }), {});
- const message = TAPi18n.__(error.error || error.message, details);
- const title = details.errorTitle && TAPi18n.__(details.errorTitle);
-
- return toastr.error(message, title);
- }
-
- return escapeHTML(TAPi18n.__(error.error || error.message, error.details));
-};
diff --git a/app/utils/client/lib/offlineMessages.js b/app/utils/client/lib/offlineMessages.js
index 675f8718fa74..b625585db6c8 100644
--- a/app/utils/client/lib/offlineMessages.js
+++ b/app/utils/client/lib/offlineMessages.js
@@ -4,7 +4,7 @@ import { sortBy } from 'underscore';
import localforage from 'localforage';
import { call } from '../../../ui-utils/client';
-import { getConfig } from '../../../ui-utils/client/config';
+import { getConfig } from '../../../../client/lib/utils/getConfig';
import { ChatMessage, CachedChatMessage } from '../../../models/client';
import { SWCache, APIClient } from '..';
diff --git a/app/utils/client/lib/waitUntilFind.ts b/app/utils/client/lib/waitUntilFind.ts
deleted file mode 100644
index bb07282ef165..000000000000
--- a/app/utils/client/lib/waitUntilFind.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Tracker } from 'meteor/tracker';
-
-export const waitUntilFind = (fn: () => T | undefined): Promise => new Promise((resolve) => {
- Tracker.autorun((c) => {
- const result = fn();
-
- if (result === undefined) {
- return;
- }
-
- c.stop();
- resolve(result);
- });
-});
diff --git a/app/utils/lib/getDefaultSubscriptionPref.js b/app/utils/lib/getDefaultSubscriptionPref.js
index e0761ac55956..294a7d50a734 100644
--- a/app/utils/lib/getDefaultSubscriptionPref.js
+++ b/app/utils/lib/getDefaultSubscriptionPref.js
@@ -5,7 +5,6 @@ export const getDefaultSubscriptionPref = (userPref) => {
desktopNotifications,
mobileNotifications,
emailNotificationMode,
- audioNotifications,
highlights,
} = (userPref.settings && userPref.settings.preferences) || {};
@@ -28,10 +27,5 @@ export const getDefaultSubscriptionPref = (userPref) => {
subscription.emailPrefOrigin = 'user';
}
- if (audioNotifications && audioNotifications !== 'default') {
- subscription.audioNotifications = audioNotifications;
- subscription.audioPrefOrigin = 'user';
- }
-
return subscription;
};
diff --git a/app/utils/lib/getUserNotificationPreference.js b/app/utils/lib/getUserNotificationPreference.js
index ce80cd5befbb..4b7bcdec4379 100644
--- a/app/utils/lib/getUserNotificationPreference.js
+++ b/app/utils/lib/getUserNotificationPreference.js
@@ -8,9 +8,8 @@ export const getUserNotificationPreference = (user, pref) => {
let preferenceKey;
switch (pref) {
- case 'audio': preferenceKey = 'audioNotifications'; break;
case 'desktop': preferenceKey = 'desktopNotifications'; break;
- case 'mobile': preferenceKey = 'mobileNotifications'; break;
+ case 'mobile': preferenceKey = 'pushNotifications'; break;
case 'email': preferenceKey = 'emailNotificationMode'; break;
}
diff --git a/app/utils/lib/templateVarHandler.js b/app/utils/lib/templateVarHandler.js
index 463a48ed34e3..dba5cdef43bf 100644
--- a/app/utils/lib/templateVarHandler.js
+++ b/app/utils/lib/templateVarHandler.js
@@ -3,8 +3,8 @@ import { Meteor } from 'meteor/meteor';
let logger;
if (Meteor.isServer) {
- const { Logger } = require('../../logger/server/server');
- logger = new Logger('TemplateVarHandler', {});
+ const { Logger } = require('../../../server/lib/logger/Logger');
+ logger = new Logger('TemplateVarHandler');
}
export const templateVarHandler = function(variable, object) {
diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info
index 586ff857ba81..692f5a791ffe 100644
--- a/app/utils/rocketchat.info
+++ b/app/utils/rocketchat.info
@@ -1,3 +1,3 @@
{
- "version": "3.18.1"
+ "version": "4.0.3"
}
diff --git a/app/utils/server/lib/cron/Cronjobs.ts b/app/utils/server/lib/cron/Cronjobs.ts
index c9cabed5a861..8ab96121e54f 100644
--- a/app/utils/server/lib/cron/Cronjobs.ts
+++ b/app/utils/server/lib/cron/Cronjobs.ts
@@ -1,15 +1,18 @@
import { SyncedCron } from 'meteor/littledata:synced-cron';
+type ScheduleType = 'cron' | 'text';
+
export interface ICronJobs {
- add(name: string, schedule: string, callback: Function): void;
+ add(name: string, schedule: string, callback: Function, scheduleType?: ScheduleType): void;
remove(name: string): void;
+ nextScheduledAtDate(name: string): Date | number | undefined;
}
class SyncedCronJobs implements ICronJobs {
- add(name: string, schedule: string, callback: Function): void {
+ add(name: string, schedule: string, callback: Function, scheduleType: ScheduleType = 'cron'): void {
SyncedCron.add({
name,
- schedule: (parser: any) => parser.cron(schedule),
+ schedule: (parser: any) => parser[scheduleType](schedule),
job() {
const [day, hour] = this.name.split('/');
callback(day, hour);
@@ -20,6 +23,10 @@ class SyncedCronJobs implements ICronJobs {
remove(name: string): void {
SyncedCron.remove(name);
}
+
+ nextScheduledAtDate(name: string): Date | number | undefined {
+ return SyncedCron.nextScheduledAtDate(name);
+ }
}
export const cronJobs: ICronJobs = new SyncedCronJobs();
diff --git a/app/videobridge/server/methods/bbb.js b/app/videobridge/server/methods/bbb.js
index 3695cc389cd3..4721dc9842ce 100644
--- a/app/videobridge/server/methods/bbb.js
+++ b/app/videobridge/server/methods/bbb.js
@@ -3,9 +3,10 @@ import { HTTP } from 'meteor/http';
import xml2js from 'xml2js';
import BigBlueButtonApi from '../../../bigbluebutton/server';
-import { settings } from '../../../settings';
-import { Rooms, Users } from '../../../models';
-import { saveStreamingOptions } from '../../../channel-settings';
+import { SystemLogger } from '../../../../server/lib/logger/system';
+import { settings } from '../../../settings/server';
+import { Rooms, Users } from '../../../models/server';
+import { saveStreamingOptions } from '../../../channel-settings/server';
import { API } from '../../../api/server';
const parser = new xml2js.Parser({
@@ -66,7 +67,7 @@ Meteor.methods({
if (hookResult.statusCode !== 200) {
// TODO improve error logging
- console.log({ hookResult });
+ SystemLogger.error(hookResult);
return;
}
@@ -130,7 +131,7 @@ API.v1.addRoute('videoconference.bbb.update/:id', { authRequired: false }, {
const meetingID = event.data.attributes.meeting['external-meeting-id'];
const rid = meetingID.replace(settings.get('uniqueID'), '');
- console.log(eventType, rid);
+ SystemLogger.debug(eventType, rid);
if (eventType === 'meeting-ended') {
saveStreamingOptions(rid, {});
@@ -147,14 +148,14 @@ API.v1.addRoute('videoconference.bbb.update/:id', { authRequired: false }, {
// if (getMeetingInfoResult.statusCode !== 200) {
// // TODO improve error logging
- // console.log({ getMeetingInfoResult });
+ // SystemLogger.error({ getMeetingInfoResult });
// }
// const doc = parseString(getMeetingInfoResult.content);
// if (doc.response.returncode[0]) {
// const participantCount = parseInt(doc.response.participantCount[0]);
- // console.log(participantCount);
+ // SystemLogger.debug(participantCount);
// }
// }
},
diff --git a/app/videobridge/server/methods/jitsiSetTimeout.js b/app/videobridge/server/methods/jitsiSetTimeout.js
index 3f630067d222..bfb109efd322 100644
--- a/app/videobridge/server/methods/jitsiSetTimeout.js
+++ b/app/videobridge/server/methods/jitsiSetTimeout.js
@@ -6,7 +6,7 @@ import { callbacks } from '../../../callbacks/server';
import { metrics } from '../../../metrics/server';
import * as CONSTANTS from '../../constants';
import { canSendMessage } from '../../../authorization/server';
-import { SystemLogger } from '../../../logger/server';
+import { SystemLogger } from '../../../../server/lib/logger/system';
Meteor.methods({
'jitsi:updateTimeout': (rid, joiningNow = true) => {
diff --git a/app/webdav/client/webdavFilePicker.css b/app/webdav/client/webdavFilePicker.css
index 54c0d02a277a..7a6a6ffc28f0 100644
--- a/app/webdav/client/webdavFilePicker.css
+++ b/app/webdav/client/webdavFilePicker.css
@@ -259,7 +259,6 @@
}
.webdav-grid-header {
-
padding: 0.5rem 0;
color: #444444;
diff --git a/app/webdav/client/webdavFilePicker.js b/app/webdav/client/webdavFilePicker.js
index 9a0cb4b68494..600dbb8d3355 100644
--- a/app/webdav/client/webdavFilePicker.js
+++ b/app/webdav/client/webdavFilePicker.js
@@ -8,9 +8,10 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { ReactiveDict } from 'meteor/reactive-dict';
import { timeAgo } from '../../ui/client/views/app/helpers';
-import { modal, call } from '../../ui-utils';
-import { t } from '../../utils';
-import { fileUploadHandler } from '../../file-upload';
+import { modal } from '../../ui-utils/client';
+import { t } from '../../utils/client';
+import { fileUploadHandler } from '../../file-upload/client';
+import { call } from '../../../client/lib/utils/call';
function sortTable(data, sortBy, sortDirection) {
if (sortDirection === 'desc') {
diff --git a/app/webdav/server/methods/uploadFileToWebdav.ts b/app/webdav/server/methods/uploadFileToWebdav.ts
index b818579dcf1d..345550285e7f 100644
--- a/app/webdav/server/methods/uploadFileToWebdav.ts
+++ b/app/webdav/server/methods/uploadFileToWebdav.ts
@@ -6,7 +6,7 @@ import { getWebdavCredentials } from './getWebdavCredentials';
import { WebdavAccounts } from '../../../models/server';
import { WebdavClientAdapter } from '../lib/webdavClientAdapter';
-const logger = new Logger('WebDAV_Upload', {});
+const logger = new Logger('WebDAV_Upload');
Meteor.methods({
async uploadFileToWebdav(accountId, fileData, name) {
diff --git a/app/webrtc/client/WebRTCClass.js b/app/webrtc/client/WebRTCClass.js
index 67610a6c0b65..1d8485b245d4 100644
--- a/app/webrtc/client/WebRTCClass.js
+++ b/app/webrtc/client/WebRTCClass.js
@@ -11,7 +11,7 @@ import { settings } from '../../settings';
import { modal } from '../../ui-utils';
import { ChatSubscription } from '../../models';
import { WEB_RTC_EVENTS } from '..';
-import { goToRoomById } from '../../../client/lib/goToRoomById';
+import { goToRoomById } from '../../../client/lib/utils/goToRoomById';
class WebRTCTransportClass extends Emitter {
constructor(webrtcInstance) {
diff --git a/app/webrtc/client/screenShare.js b/app/webrtc/client/screenShare.js
index 827d1fb3eba6..7099474be786 100644
--- a/app/webrtc/client/screenShare.js
+++ b/app/webrtc/client/screenShare.js
@@ -1,4 +1,4 @@
-import { fireGlobalEvent } from '../../ui-utils';
+import { fireGlobalEvent } from '../../../client/lib/utils/fireGlobalEvent';
export const ChromeScreenShare = {
callbacks: {},
diff --git a/client/.eslintrc.js b/client/.eslintrc.js
index d5bb431b9890..4d6bf74449b7 100644
--- a/client/.eslintrc.js
+++ b/client/.eslintrc.js
@@ -16,7 +16,10 @@ module.exports = {
},
],
'jsx-quotes': ['error', 'prefer-single'],
- 'new-cap': ['error', { capIsNewExceptions: ['HTML.Comment', 'HTML.DIV', 'SHA256'] }],
+ 'new-cap': [
+ 'error',
+ { capIsNewExceptions: ['HTML.Comment', 'HTML.Raw', 'HTML.DIV', 'SHA256'] },
+ ],
'prefer-arrow-callback': ['error', { allowNamedFunctions: true }],
'prettier/prettier': 2,
'react/display-name': 'error',
@@ -83,7 +86,10 @@ module.exports = {
},
],
'jsx-quotes': ['error', 'prefer-single'],
- 'new-cap': ['error', { capIsNewExceptions: ['HTML.Comment', 'HTML.DIV', 'SHA256'] }],
+ 'new-cap': [
+ 'error',
+ { capIsNewExceptions: ['HTML.Comment', 'HTML.Raw', 'HTML.DIV', 'SHA256'] },
+ ],
'no-extra-parens': 'off',
'no-spaced-func': 'off',
'no-unused-vars': 'off',
diff --git a/client/UIKit/hooks/useUIKitHandleAction.tsx b/client/UIKit/hooks/useUIKitHandleAction.tsx
index 276d8655d301..4a0c71590170 100644
--- a/client/UIKit/hooks/useUIKitHandleAction.tsx
+++ b/client/UIKit/hooks/useUIKitHandleAction.tsx
@@ -13,8 +13,11 @@ import { UiKitPayload, UIKitActionEvent } from '../../../definition/UIKit';
const useUIKitHandleAction = (
state: S,
): ((event: UIKitActionEvent) => Promise) =>
- useMutableCallback(async ({ blockId, value, appId, actionId }) =>
- ActionManager.triggerBlockAction({
+ useMutableCallback(async ({ blockId, value, appId, actionId }) => {
+ if (!appId) {
+ throw new Error('useUIKitHandleAction - invalid appId');
+ }
+ return ActionManager.triggerBlockAction({
container: {
type: UIKitIncomingInteractionContainerType.VIEW,
id: state.viewId || state.appId,
@@ -23,7 +26,7 @@ const useUIKitHandleAction = (
appId,
value,
blockId,
- }),
- );
+ });
+ });
export { useUIKitHandleAction };
diff --git a/client/components/burger/BurgerBadge.stories.tsx b/client/components/BurgerMenu/BurgerBadge.stories.tsx
similarity index 100%
rename from client/components/burger/BurgerBadge.stories.tsx
rename to client/components/BurgerMenu/BurgerBadge.stories.tsx
diff --git a/client/components/BurgerMenu/BurgerBadge.tsx b/client/components/BurgerMenu/BurgerBadge.tsx
new file mode 100644
index 000000000000..efc029edc181
--- /dev/null
+++ b/client/components/BurgerMenu/BurgerBadge.tsx
@@ -0,0 +1,18 @@
+import { css } from '@rocket.chat/css-in-js';
+import { Box, Badge } from '@rocket.chat/fuselage';
+import React, { ReactElement } from 'react';
+
+const BurgerBadge = ({ children }: { children?: unknown }): ReactElement => (
+
+
+
+);
+
+export default BurgerBadge;
diff --git a/client/components/burger/BurgerIcon.stories.js b/client/components/BurgerMenu/BurgerIcon.stories.tsx
similarity index 58%
rename from client/components/burger/BurgerIcon.stories.js
rename to client/components/BurgerMenu/BurgerIcon.stories.tsx
index e58c586e4b1f..e009311d8bec 100644
--- a/client/components/burger/BurgerIcon.stories.js
+++ b/client/components/BurgerMenu/BurgerIcon.stories.tsx
@@ -1,3 +1,4 @@
+import { Story } from '@storybook/react';
import React from 'react';
import { centeredDecorator } from '../../../.storybook/decorators';
@@ -10,8 +11,8 @@ export default {
decorators: [centeredDecorator],
};
-export const Normal = () => ;
+export const Normal: Story = () => ;
-export const Open = () => ;
+export const Open: Story = () => ;
-export const Transitioning = () => ;
+export const Transitioning: Story = () => ;
diff --git a/client/components/burger/BurgerIcon.js b/client/components/BurgerMenu/BurgerIcon.tsx
similarity index 74%
rename from client/components/burger/BurgerIcon.js
rename to client/components/BurgerMenu/BurgerIcon.tsx
index 990e4db9dfa3..aedcdae226b0 100644
--- a/client/components/burger/BurgerIcon.js
+++ b/client/components/BurgerMenu/BurgerIcon.tsx
@@ -1,10 +1,10 @@
import { usePrefersReducedMotion } from '@rocket.chat/fuselage-hooks';
-import React from 'react';
+import React, { ReactElement, ReactNode } from 'react';
import Line from './Line';
import Wrapper from './Wrapper';
-function BurgerIcon({ children, open }) {
+const BurgerIcon = ({ children, open }: { children?: ReactNode; open?: boolean }): ReactElement => {
const isReducedMotionPreferred = usePrefersReducedMotion();
return (
@@ -15,6 +15,6 @@ function BurgerIcon({ children, open }) {
{children}
);
-}
+};
export default BurgerIcon;
diff --git a/client/components/BurgerMenu/BurgerMenu.tsx b/client/components/BurgerMenu/BurgerMenu.tsx
new file mode 100644
index 000000000000..785f943282c6
--- /dev/null
+++ b/client/components/BurgerMenu/BurgerMenu.tsx
@@ -0,0 +1,26 @@
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+import React, { memo, ReactElement } from 'react';
+
+import { useLayout } from '../../contexts/LayoutContext';
+import { useSession } from '../../contexts/SessionContext';
+import { useEmbeddedLayout } from '../../hooks/useEmbeddedLayout';
+import BurgerMenuButton from './BurgerMenuButton';
+
+const BurgerMenu = (): ReactElement => {
+ const { sidebar } = useLayout();
+ const isLayoutEmbedded = useEmbeddedLayout();
+ const unreadMessagesBadge = useSession('unread');
+
+ const isSidebarOpen = sidebar.isOpen();
+ const toggleSidebar = useMutableCallback(() => sidebar.toggle());
+
+ return (
+
+ );
+};
+
+export default memo(BurgerMenu);
diff --git a/client/components/burger/BurgerMenuButton.stories.js b/client/components/BurgerMenu/BurgerMenuButton.stories.tsx
similarity index 51%
rename from client/components/burger/BurgerMenuButton.stories.js
rename to client/components/BurgerMenu/BurgerMenuButton.stories.tsx
index 0ba6a971893c..3109b3f7c07c 100644
--- a/client/components/burger/BurgerMenuButton.stories.js
+++ b/client/components/BurgerMenu/BurgerMenuButton.stories.tsx
@@ -1,4 +1,5 @@
import { action } from '@storybook/addon-actions';
+import { Story } from '@storybook/react';
import React from 'react';
import { centeredDecorator } from '../../../.storybook/decorators';
@@ -10,8 +11,8 @@ export default {
decorators: [centeredDecorator],
};
-export const Basic = () => ;
+export const Basic: Story = () => ;
-export const Open = () => ;
+export const Open: Story = () => ;
-export const WithBadge = () => ;
+export const WithBadge: Story = () => ;
diff --git a/client/components/burger/BurgerMenuButton.js b/client/components/BurgerMenu/BurgerMenuButton.tsx
similarity index 54%
rename from client/components/burger/BurgerMenuButton.js
rename to client/components/BurgerMenu/BurgerMenuButton.tsx
index d049d43eef8b..a92c368489be 100644
--- a/client/components/burger/BurgerMenuButton.js
+++ b/client/components/BurgerMenu/BurgerMenuButton.tsx
@@ -1,31 +1,36 @@
import { css } from '@rocket.chat/css-in-js';
import { Box } from '@rocket.chat/fuselage';
-import React from 'react';
+import React, { ReactElement } from 'react';
import { useTranslation } from '../../contexts/TranslationContext';
-import { useEmbeddedLayout } from '../../hooks/useEmbeddedLayout';
import BurgerBadge from './BurgerBadge';
import BurgerIcon from './BurgerIcon';
-function BurgerMenuButton({ open, badge, ...props }) {
- const isLayoutEmbedded = useEmbeddedLayout();
+type BurgerMenuButtonProps = {
+ open?: boolean;
+ badge?: number | unknown;
+ onClick: () => void;
+};
+
+const BurgerMenuButton = ({ open, badge, onClick }: BurgerMenuButtonProps): ReactElement => {
const t = useTranslation();
return (
- {!isLayoutEmbedded && badge && {badge}}
+ {badge && {badge}}
);
-}
+};
export default BurgerMenuButton;
diff --git a/client/components/BurgerMenu/Line.tsx b/client/components/BurgerMenu/Line.tsx
new file mode 100644
index 000000000000..41339fa7deb7
--- /dev/null
+++ b/client/components/BurgerMenu/Line.tsx
@@ -0,0 +1,50 @@
+import { css } from '@rocket.chat/css-in-js';
+import { Box } from '@rocket.chat/fuselage';
+import React, { ReactElement } from 'react';
+
+const Line = ({ animated, moved }: { animated: boolean; moved?: boolean }): ReactElement => {
+ const animatedStyle = animated
+ ? css`
+ will-change: transform;
+ transition: transform 0.2s ease-out;
+ `
+ : '';
+
+ const movedStyle = moved
+ ? css`
+ &:nth-child(1),
+ &:nth-child(3) {
+ transform-origin: 50%, 50%, 0;
+ }
+
+ &:nth-child(1) {
+ transform: translate(-25%, 3px) rotate(-45deg) scale(0.5, 1);
+ }
+
+ [dir='rtl'] &:nth-child(1) {
+ transform: translate(25%, 3px) rotate(45deg) scale(0.5, 1);
+ }
+
+ &:nth-child(3) {
+ transform: translate(-25%, -3px) rotate(45deg) scale(0.5, 1);
+ }
+
+ [dir='rtl'] &:nth-child(3) {
+ transform: translate(25%, -3px) rotate(-45deg) scale(0.5, 1);
+ }
+ `
+ : '';
+
+ return (
+
+ );
+};
+
+export default Line;
diff --git a/client/components/burger/Wrapper.js b/client/components/BurgerMenu/Wrapper.tsx
similarity index 64%
rename from client/components/burger/Wrapper.js
rename to client/components/BurgerMenu/Wrapper.tsx
index b3aacde0d950..80ceeacf0de6 100644
--- a/client/components/burger/Wrapper.js
+++ b/client/components/BurgerMenu/Wrapper.tsx
@@ -1,18 +1,19 @@
import { Box } from '@rocket.chat/fuselage';
-import React from 'react';
+import React, { ReactElement, ReactNode } from 'react';
-const Wrapper = ({ children }) => (
+const Wrapper = ({ children }: { children: ReactNode }): ReactElement => (
);
diff --git a/client/components/BurgerMenu/index.ts b/client/components/BurgerMenu/index.ts
new file mode 100644
index 000000000000..be4fd9776533
--- /dev/null
+++ b/client/components/BurgerMenu/index.ts
@@ -0,0 +1 @@
+export { default } from './BurgerMenu';
diff --git a/client/components/Card/CardIcon.tsx b/client/components/Card/CardIcon.tsx
index 66e387834c89..6a240af1a8fe 100644
--- a/client/components/Card/CardIcon.tsx
+++ b/client/components/Card/CardIcon.tsx
@@ -1,7 +1,12 @@
import { Box, Icon } from '@rocket.chat/fuselage';
-import React, { FC } from 'react';
+import React, { ComponentProps, ReactElement, ReactNode } from 'react';
-const CardIcon: FC<{ name: string }> = ({ name, children, ...props }) => (
+type CardIconProps = { children: ReactNode } | ComponentProps;
+
+const hasChildrenProp = (props: CardIconProps): props is { children: ReactNode } =>
+ 'children' in props;
+
+const CardIcon = (props: CardIconProps): ReactElement => (
= ({ name, children, ...props }) => (
alignItems='flex-end'
justifyContent='center'
>
- {children || }
+ {hasChildrenProp(props) ? props.children : }
);
diff --git a/client/components/CreateDiscussion/CreateDiscussion.tsx b/client/components/CreateDiscussion/CreateDiscussion.tsx
index c936e0d3ba70..6f2601cb7b42 100644
--- a/client/components/CreateDiscussion/CreateDiscussion.tsx
+++ b/client/components/CreateDiscussion/CreateDiscussion.tsx
@@ -17,9 +17,9 @@ import { IMessage } from '../../../definition/IMessage';
import { IRoom } from '../../../definition/IRoom';
import { IUser } from '../../../definition/IUser';
import { useTranslation } from '../../contexts/TranslationContext';
-import { useEndpointActionExperimental } from '../../hooks/useEndpointAction';
+import { useEndpointActionExperimental } from '../../hooks/useEndpointActionExperimental';
import { useForm } from '../../hooks/useForm';
-import { goToRoomById } from '../../lib/goToRoomById';
+import { goToRoomById } from '../../lib/utils/goToRoomById';
import RoomAutoComplete from '../RoomAutoComplete';
import UserAutoCompleteMultiple from '../UserAutoCompleteMultiple';
import DefaultParentRoomField from './DefaultParentRoomField';
@@ -76,7 +76,7 @@ const CreateDiscussion = ({
...(parentMessageId && { pmid: parentMessageId }),
});
- goToRoomById(result?.discussion?.rid);
+ goToRoomById(result.discussion._id);
onClose();
} catch (error) {
console.warn(error);
diff --git a/client/components/Emoji.js b/client/components/Emoji.js
index 5298b9c3c173..b5e94bfe6abe 100644
--- a/client/components/Emoji.js
+++ b/client/components/Emoji.js
@@ -1,6 +1,6 @@
import React from 'react';
-import { renderEmoji } from '../../app/emoji/client/index';
+import { renderEmoji } from '../lib/utils/renderEmoji';
function Emoji({ emojiHandle, className = undefined }) {
const markup = { __html: `${renderEmoji(emojiHandle)}` };
diff --git a/client/components/MarkdownText.tsx b/client/components/MarkdownText.tsx
index d89000c6db82..4baefbab19d1 100644
--- a/client/components/MarkdownText.tsx
+++ b/client/components/MarkdownText.tsx
@@ -3,7 +3,7 @@ import dompurify from 'dompurify';
import marked from 'marked';
import React, { ComponentProps, FC, useMemo } from 'react';
-import { renderMessageEmoji } from '../lib/renderMessageEmoji';
+import { renderMessageEmoji } from '../lib/utils/renderMessageEmoji';
type MarkdownTextParams = {
content: string;
diff --git a/client/components/Message/Attachments/DefaultAttachment.tsx b/client/components/Message/Attachments/DefaultAttachment.tsx
index 712234bc6b92..6eb203908d65 100644
--- a/client/components/Message/Attachments/DefaultAttachment.tsx
+++ b/client/components/Message/Attachments/DefaultAttachment.tsx
@@ -78,13 +78,21 @@ const DefaultAttachment: FC = (attachment) => {
return field;
}
- const { value, ...rest } = field;
+ const { value, title, ...rest } = field;
return {
...rest,
+ title: (
+ `${line} `)}
+ />
+ ),
value: (
`${line} `)}
/>
),
diff --git a/client/components/Message/Attachments/FieldsAttachment/Field.tsx b/client/components/Message/Attachments/FieldsAttachment/Field.tsx
index e13ac0afcc5f..6ed0bcea845b 100644
--- a/client/components/Message/Attachments/FieldsAttachment/Field.tsx
+++ b/client/components/Message/Attachments/FieldsAttachment/Field.tsx
@@ -3,7 +3,7 @@ import React, { ComponentProps, FC, ReactNode } from 'react';
type FieldProps = {
short?: boolean;
- title: string;
+ title: ReactNode;
value: ReactNode;
} & Omit, 'title' | 'value'>;
diff --git a/client/components/Message/Attachments/FieldsAttachment/index.tsx b/client/components/Message/Attachments/FieldsAttachment/index.tsx
index fa12b9ae84a2..6fd9c44dd64a 100644
--- a/client/components/Message/Attachments/FieldsAttachment/index.tsx
+++ b/client/components/Message/Attachments/FieldsAttachment/index.tsx
@@ -4,13 +4,13 @@ import React, { FC, ReactNode } from 'react';
import Field from './Field';
import ShortField from './ShortField';
-type FieldProp = {
+type FieldsAttachmentProp = {
short?: boolean;
- title: string;
+ title: ReactNode;
value: ReactNode;
};
-const FieldsAttachment: FC<{ fields: FieldProp[] }> = ({ fields }): any => (
+const FieldsAttachment: FC<{ fields: FieldsAttachmentProp[] }> = ({ fields }): any => (
{fields.map((field, index) =>
field.short ? : ,
diff --git a/client/components/Message/Attachments/QuoteAttachment.tsx b/client/components/Message/Attachments/QuoteAttachment.tsx
index acbabe4f9b65..f8413028553c 100644
--- a/client/components/Message/Attachments/QuoteAttachment.tsx
+++ b/client/components/Message/Attachments/QuoteAttachment.tsx
@@ -57,7 +57,7 @@ export const QuoteAttachment: FC = ({
{attachments && (
-
+
)}
diff --git a/client/components/Message/Body/Body.tsx b/client/components/Message/Body/Body.tsx
index 62fba1254107..c94930761ebf 100644
--- a/client/components/Message/Body/Body.tsx
+++ b/client/components/Message/Body/Body.tsx
@@ -47,7 +47,7 @@ const Body: FC = ({ tokens, mentions }) => {
}
if (block.type === 'CODE') {
- return
;
+ return
;
}
if (block.type === 'HEADING') {
diff --git a/client/components/Message/Body/Bold.tsx b/client/components/Message/Body/Bold.tsx
index 846993864be0..14104d4482c0 100644
--- a/client/components/Message/Body/Bold.tsx
+++ b/client/components/Message/Body/Bold.tsx
@@ -2,12 +2,15 @@ import { Bold as ASTBold } from '@rocket.chat/message-parser';
import React, { FC } from 'react';
import Italic from './Italic';
+import Link from './Link';
import Strike from './Strike';
const Bold: FC<{ value: ASTBold['value'] }> = ({ value = [] }) => (
{value.map((block, index) => {
switch (block.type) {
+ case 'LINK':
+ return ;
case 'PLAIN_TEXT':
return block.value;
case 'STRIKE':
diff --git a/client/components/Message/Body/Code.tsx b/client/components/Message/Body/Code.tsx
index b17439bc9d1e..00ca52da211c 100644
--- a/client/components/Message/Body/Code.tsx
+++ b/client/components/Message/Body/Code.tsx
@@ -1,19 +1,54 @@
import { Code as ASTCode } from '@rocket.chat/message-parser';
-import React, { FC } from 'react';
+import React, { FC, useEffect, useState } from 'react';
+import hljs, { register } from '../../../../app/markdown/lib/hljs';
import CodeLine from './CodeLine';
-const Code: FC<{ value: ASTCode['value'] }> = ({ value = [] }) => (
-
- {value.map((block, index) => {
- switch (block.type) {
- case 'CODE_LINE':
- return ;
- default:
- return null;
- }
- })}
-
-);
+type hljsResult = {
+ language: string;
+ code: string;
+ value: string;
+};
+
+const isHljsResult = (result: any): result is hljsResult => result && result.value;
+
+const Code: FC = ({ value = [], language }) => {
+ const [code, setCode] = useState<(JSX.Element | null)[] | { language: string; code: string }>(
+ () =>
+ value.map((block, index) => {
+ switch (block.type) {
+ case 'CODE_LINE':
+ return ;
+ default:
+ return null;
+ }
+ }),
+ );
+ useEffect(() => {
+ !language || language === 'none'
+ ? setCode(hljs.highlightAuto(value.map((line) => line.value.value).join('\n')))
+ : register(language).then(() => {
+ setCode(hljs.highlight(language, value.map((line) => line.value.value).join('\n')));
+ });
+ }, [language, value]);
+
+ return (
+
+
+ \`\`\`
+
+
+ {isHljsResult(code) ? (
+
+ ) : (
+ code
+ )}
+
+
+ \`\`\`
+
+
+ );
+};
export default Code;
diff --git a/client/components/Message/Body/Image.tsx b/client/components/Message/Body/Image.tsx
new file mode 100644
index 000000000000..d98db0d46b93
--- /dev/null
+++ b/client/components/Message/Body/Image.tsx
@@ -0,0 +1,21 @@
+import { Image as ASTImage } from '@rocket.chat/message-parser';
+import React, { FC } from 'react';
+
+type ImageProps = {
+ value: ASTImage['value'];
+};
+
+const style = {
+ maxWidth: '100%',
+};
+
+const Image: FC = ({ value }) => {
+ const { src, label } = value;
+ return (
+
+
+
+ );
+};
+
+export default Image;
diff --git a/client/components/Message/Body/Inline.tsx b/client/components/Message/Body/Inline.tsx
index 3e7e7ec2a349..ff3780737ddd 100644
--- a/client/components/Message/Body/Inline.tsx
+++ b/client/components/Message/Body/Inline.tsx
@@ -3,6 +3,7 @@ import React, { FC } from 'react';
import Emoji from '../../Emoji';
import Bold from './Bold';
+import Image from './Image';
import InlineCode from './InlineCode';
import Italic from './Italic';
import Link from './Link';
@@ -18,6 +19,8 @@ const Inline: FC<{ value: ASTParagraph['value']; mentions?: UserMention[] }> = (
<>
{value.map((block, idx) => {
switch (block.type) {
+ case 'IMAGE':
+ return ;
case 'PLAIN_TEXT':
return block.value;
case 'BOLD':
diff --git a/client/components/Message/Body/Italic.tsx b/client/components/Message/Body/Italic.tsx
index cb0061ae2684..c88f0d7516b7 100644
--- a/client/components/Message/Body/Italic.tsx
+++ b/client/components/Message/Body/Italic.tsx
@@ -2,12 +2,15 @@ import { Italic as ASTItalic } from '@rocket.chat/message-parser';
import React, { FC } from 'react';
import Bold from './Bold';
+import Link from './Link';
import Strike from './Strike';
const Italic: FC<{ value: ASTItalic['value'] }> = ({ value = [] }) => (
{value.map((block, index) => {
switch (block.type) {
+ case 'LINK':
+ return ;
case 'PLAIN_TEXT':
return block.value;
case 'STRIKE':
diff --git a/client/components/Message/Body/Link.tsx b/client/components/Message/Body/Link.tsx
index 5ecc831933bb..4b8ec157cd05 100644
--- a/client/components/Message/Body/Link.tsx
+++ b/client/components/Message/Body/Link.tsx
@@ -1,7 +1,7 @@
import { Link as ASTLink } from '@rocket.chat/message-parser';
import React, { FC } from 'react';
-import { baseURI } from '../../../lib/baseuri';
+import { baseURI } from '../../../lib/baseURI';
import Bold from './Bold';
import Italic from './Italic';
import Strike from './Strike';
diff --git a/client/components/Message/Body/Strike.tsx b/client/components/Message/Body/Strike.tsx
index 04f096586b25..75139731169b 100644
--- a/client/components/Message/Body/Strike.tsx
+++ b/client/components/Message/Body/Strike.tsx
@@ -3,11 +3,14 @@ import React, { FC } from 'react';
import Bold from './Bold';
import Italic from './Italic';
+import Link from './Link';
const Strike: FC<{ value: ASTStrike['value'] }> = ({ value = [] }) => (
{value.map((block, index) => {
switch (block.type) {
+ case 'LINK':
+ return ;
case 'PLAIN_TEXT':
return block.value;
case 'BOLD':
diff --git a/client/components/Page/PageHeader.tsx b/client/components/Page/PageHeader.tsx
index 3afd3332d2ca..f68cfa559d65 100644
--- a/client/components/Page/PageHeader.tsx
+++ b/client/components/Page/PageHeader.tsx
@@ -1,10 +1,9 @@
import { Box } from '@rocket.chat/fuselage';
-import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import React, { useContext, FC } from 'react';
-import { useSession } from '../../contexts/SessionContext';
-import { useSidebar } from '../../contexts/SidebarContext';
-import BurgerMenuButton from '../burger/BurgerMenuButton';
+import { useLayout } from '../../contexts/LayoutContext';
+import BurgerMenu from '../BurgerMenu';
+import TemplateHeader from '../Header';
import PageContext from './PageContext';
type PageHeaderProps = {
@@ -13,13 +12,7 @@ type PageHeaderProps = {
const PageHeader: FC = ({ children = undefined, title, ...props }) => {
const [border] = useContext(PageContext);
- const hasBurgerMenuButton = useMediaQuery('(max-width: 780px)');
- const [isSidebarOpen, setSidebarOpen] = useSidebar();
- const unreadMessagesBadge = useSession('unread');
-
- const handleBurgerMenuButtonClick = (): void => {
- setSidebarOpen((isSidebarOpen) => !isSidebarOpen);
- };
+ const { isMobile } = useLayout();
return (
@@ -34,13 +27,10 @@ const PageHeader: FC = ({ children = undefined, title, ...props
color='neutral-800'
{...props}
>
- {hasBurgerMenuButton && (
-
+ {isMobile && (
+
+
+
)}
{title}
diff --git a/client/components/RoomAutoComplete/hooks/useRoomsList.ts b/client/components/RoomAutoComplete/hooks/useRoomsList.ts
new file mode 100644
index 000000000000..b35a660fb242
--- /dev/null
+++ b/client/components/RoomAutoComplete/hooks/useRoomsList.ts
@@ -0,0 +1,63 @@
+import { useCallback, useState } from 'react';
+
+import { IRoom } from '../../../../definition/IRoom';
+import { useEndpoint } from '../../../contexts/ServerContext';
+import { useScrollableRecordList } from '../../../hooks/lists/useScrollableRecordList';
+import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate';
+import { RecordList } from '../../../lib/lists/RecordList';
+
+type RoomListOptions = {
+ text: string;
+};
+
+export const useRoomsList = (
+ options: RoomListOptions,
+): {
+ itemsList: RecordList;
+ initialItemCount: number;
+ reload: () => void;
+ loadMoreItems: (start: number, end: number) => void;
+} => {
+ const [itemsList, setItemsList] = useState(() => new RecordList());
+ const reload = useCallback(() => setItemsList(new RecordList()), []);
+ const endpoint = 'rooms.autocomplete.channelAndPrivate.withPagination';
+
+ const getRooms = useEndpoint('GET', endpoint);
+
+ useComponentDidUpdate(() => {
+ options && reload();
+ }, [options, reload]);
+
+ const fetchData = useCallback(
+ async (start, end) => {
+ const { items: rooms, total } = await getRooms({
+ selector: JSON.stringify({ name: options.text || '' }),
+ offset: start,
+ count: start + end,
+ sort: JSON.stringify({ name: 1 }),
+ });
+
+ const items = rooms.map((room: any) => {
+ room._updatedAt = new Date(room._updatedAt);
+ room.label = room.name;
+ room.value = room.name;
+ return room;
+ });
+
+ return {
+ items,
+ itemCount: total,
+ };
+ },
+ [getRooms, options.text],
+ );
+
+ const { loadMoreItems, initialItemCount } = useScrollableRecordList(itemsList, fetchData, 25);
+
+ return {
+ reload,
+ itemsList,
+ loadMoreItems,
+ initialItemCount,
+ };
+};
diff --git a/client/components/UserAutoComplete/UserAutoComplete.js b/client/components/UserAutoComplete/UserAutoComplete.js
index a4d662a5dbd6..a7efd9a4d308 100644
--- a/client/components/UserAutoComplete/UserAutoComplete.js
+++ b/client/components/UserAutoComplete/UserAutoComplete.js
@@ -1,4 +1,5 @@
-import { AutoComplete, Option } from '@rocket.chat/fuselage';
+import { AutoComplete, Option, Box, Chip } from '@rocket.chat/fuselage';
+import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { memo, useMemo, useState } from 'react';
import { useEndpointData } from '../../hooks/useEndpointData';
@@ -10,25 +11,37 @@ const query = (term = '', conditions = {}) => ({ selector: JSON.stringify({ term
const UserAutoComplete = (props) => {
const { conditions = {} } = props;
const [filter, setFilter] = useState('');
+ const debouncedFilter = useDebouncedValue(filter, 1000);
const { value: data } = useEndpointData(
'users.autocomplete',
// eslint-disable-next-line react-hooks/exhaustive-deps
- useMemo(() => query(filter, conditions), [filter]),
+ useMemo(() => query(debouncedFilter, conditions), [filter]),
);
+
const options = useMemo(
() => (data && data.items.map((user) => ({ value: user.username, label: user.name }))) || [],
[data],
);
+
return (
(
- <>
- {label}
- >
- )}
+ renderSelected={({ value, label }) => {
+ if (!value) {
+ return '';
+ }
+
+ return (
+ props.onChange()} mie='x4'>
+
+
+ {label}
+
+
+ );
+ }}
renderItem={({ value, ...props }) => (
} />
)}
diff --git a/client/components/UserStatus/UserStatus.js b/client/components/UserStatus/UserStatus.tsx
similarity index 75%
rename from client/components/UserStatus/UserStatus.js
rename to client/components/UserStatus/UserStatus.tsx
index e0c93b1d1166..abb049633478 100644
--- a/client/components/UserStatus/UserStatus.js
+++ b/client/components/UserStatus/UserStatus.tsx
@@ -1,9 +1,13 @@
import { StatusBullet } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
+import React, { memo, ComponentProps, ReactElement } from 'react';
import { useTranslation } from '../../contexts/TranslationContext';
-const UserStatus = ({ small, status, ...props }) => {
+type UserStatusProps = {
+ small?: boolean;
+} & ComponentProps;
+
+const UserStatus = ({ small, status, ...props }: UserStatusProps): ReactElement => {
const size = small ? 'small' : 'large';
const t = useTranslation();
switch (status) {
diff --git a/client/components/VerticalBar/VerticalBar.js b/client/components/VerticalBar/VerticalBar.tsx
similarity index 82%
rename from client/components/VerticalBar/VerticalBar.js
rename to client/components/VerticalBar/VerticalBar.tsx
index 5ce4a84645d5..6e34172c6cb2 100644
--- a/client/components/VerticalBar/VerticalBar.js
+++ b/client/components/VerticalBar/VerticalBar.tsx
@@ -1,9 +1,9 @@
import { Box } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
+import React, { FC, ComponentProps, memo } from 'react';
import { useLayoutContextualBarPosition, useLayoutSizes } from '../../providers/LayoutProvider';
-function VerticalBar({ children, ...props }) {
+const VerticalBar: FC> = ({ children, ...props }) => {
const sizes = useLayoutSizes();
const position = useLayoutContextualBarPosition();
return (
@@ -27,6 +27,6 @@ function VerticalBar({ children, ...props }) {
{children}
);
-}
+};
export default memo(VerticalBar);
diff --git a/client/components/VerticalBar/VerticalBarAction.js b/client/components/VerticalBar/VerticalBarAction.js
deleted file mode 100644
index c686d547ccbd..000000000000
--- a/client/components/VerticalBar/VerticalBarAction.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { ActionButton } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-function VerticalBarAction({ name, ...props }) {
- return ;
-}
-
-export default memo(VerticalBarAction);
diff --git a/client/components/VerticalBar/VerticalBarAction.tsx b/client/components/VerticalBar/VerticalBarAction.tsx
new file mode 100644
index 000000000000..ae411e39b157
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarAction.tsx
@@ -0,0 +1,13 @@
+import { ActionButton } from '@rocket.chat/fuselage';
+import React, { ReactElement, memo, MouseEventHandler } from 'react';
+
+const VerticalBarAction = ({
+ name,
+ ...props
+}: {
+ name: string;
+ title?: string;
+ onClick?: MouseEventHandler;
+}): ReactElement => ;
+
+export default memo(VerticalBarAction);
diff --git a/client/components/VerticalBar/VerticalBarActionBack.js b/client/components/VerticalBar/VerticalBarActionBack.js
deleted file mode 100644
index b04dbd6db3ad..000000000000
--- a/client/components/VerticalBar/VerticalBarActionBack.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React, { memo } from 'react';
-
-import VerticalBarAction from './VerticalBarAction';
-
-function VerticalBarActionBack(props) {
- return ;
-}
-
-export default memo(VerticalBarActionBack);
diff --git a/client/components/VerticalBar/VerticalBarActionBack.tsx b/client/components/VerticalBar/VerticalBarActionBack.tsx
new file mode 100644
index 000000000000..427dbd1d7fc6
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarActionBack.tsx
@@ -0,0 +1,9 @@
+import React, { ReactElement, memo, ComponentProps } from 'react';
+
+import VerticalBarAction from './VerticalBarAction';
+
+const VerticalBarActionBack = (props: ComponentProps): ReactElement => (
+
+);
+
+export default memo(VerticalBarActionBack);
diff --git a/client/components/VerticalBar/VerticalBarActions.js b/client/components/VerticalBar/VerticalBarActions.js
deleted file mode 100644
index c3611b5d391e..000000000000
--- a/client/components/VerticalBar/VerticalBarActions.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { ButtonGroup } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-function VerticalBarActions(props) {
- return ;
-}
-
-export default memo(VerticalBarActions);
diff --git a/client/components/VerticalBar/VerticalBarActions.tsx b/client/components/VerticalBar/VerticalBarActions.tsx
new file mode 100644
index 000000000000..c63c8d5f8d85
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarActions.tsx
@@ -0,0 +1,8 @@
+import { ButtonGroup } from '@rocket.chat/fuselage';
+import React, { memo, ReactElement, ComponentProps } from 'react';
+
+const VerticalBarActions = (props: ComponentProps): ReactElement => (
+
+);
+
+export default memo(VerticalBarActions);
diff --git a/client/components/VerticalBar/VerticalBarButton.js b/client/components/VerticalBar/VerticalBarButton.js
deleted file mode 100644
index e075a89ebcb0..000000000000
--- a/client/components/VerticalBar/VerticalBarButton.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Button } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-function VerticalBarButton(props) {
- return ;
-}
-
-export default memo(VerticalBarButton);
diff --git a/client/components/VerticalBar/VerticalBarButton.tsx b/client/components/VerticalBar/VerticalBarButton.tsx
new file mode 100644
index 000000000000..3a381dd87b56
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarButton.tsx
@@ -0,0 +1,8 @@
+import { Button } from '@rocket.chat/fuselage';
+import React, { ComponentProps, memo, ReactElement } from 'react';
+
+const VerticalBarButton = (props: ComponentProps): ReactElement => (
+
+);
+
+export default memo(VerticalBarButton);
diff --git a/client/components/VerticalBar/VerticalBarClose.js b/client/components/VerticalBar/VerticalBarClose.tsx
similarity index 54%
rename from client/components/VerticalBar/VerticalBarClose.js
rename to client/components/VerticalBar/VerticalBarClose.tsx
index 674c62a3f066..a5ab5202e42b 100644
--- a/client/components/VerticalBar/VerticalBarClose.js
+++ b/client/components/VerticalBar/VerticalBarClose.tsx
@@ -1,11 +1,13 @@
-import React, { memo } from 'react';
+import React, { memo, ComponentProps, ReactElement } from 'react';
import { useTranslation } from '../../contexts/TranslationContext';
import VerticalBarAction from './VerticalBarAction';
-function VerticalBarClose(props) {
+type VerticalBarCloseProps = Partial>;
+
+const VerticalBarClose = (props: VerticalBarCloseProps): ReactElement => {
const t = useTranslation();
return ;
-}
+};
export default memo(VerticalBarClose);
diff --git a/client/components/VerticalBar/VerticalBarContent.js b/client/components/VerticalBar/VerticalBarContent.js
deleted file mode 100644
index d6d76e25353f..000000000000
--- a/client/components/VerticalBar/VerticalBarContent.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React, { forwardRef, memo } from 'react';
-
-import Page from '../Page';
-
-const VerticalBarContent = forwardRef(function VerticalBarContent(props, ref) {
- return ;
-});
-
-export default memo(VerticalBarContent);
diff --git a/client/components/VerticalBar/VerticalBarContent.tsx b/client/components/VerticalBar/VerticalBarContent.tsx
new file mode 100644
index 000000000000..3a1039a2081b
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarContent.tsx
@@ -0,0 +1,11 @@
+import React, { ComponentProps, forwardRef, memo } from 'react';
+
+import Page from '../Page';
+
+const VerticalBarContent = forwardRef>(
+ function VerticalBarContent(props, ref) {
+ return ;
+ },
+);
+
+export default memo(VerticalBarContent);
diff --git a/client/components/VerticalBar/VerticalBarFooter.js b/client/components/VerticalBar/VerticalBarFooter.js
deleted file mode 100644
index edf8141e6435..000000000000
--- a/client/components/VerticalBar/VerticalBarFooter.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Box, Margins } from '@rocket.chat/fuselage';
-import React, { forwardRef, memo } from 'react';
-
-const VerticalBarFooter = forwardRef(function VerticalBarFooter({ children, ...props }, ref) {
- return (
-
- {children}
-
- );
-});
-
-export default memo(VerticalBarFooter);
diff --git a/client/components/VerticalBar/VerticalBarFooter.tsx b/client/components/VerticalBar/VerticalBarFooter.tsx
new file mode 100644
index 000000000000..a1fba1a2699c
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarFooter.tsx
@@ -0,0 +1,14 @@
+import { Box } from '@rocket.chat/fuselage';
+import React, { forwardRef, ComponentProps, memo } from 'react';
+
+const VerticalBarFooter = forwardRef>(
+ function VerticalBarFooter({ children, ...props }, ref) {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+export default memo(VerticalBarFooter);
diff --git a/client/components/VerticalBar/VerticalBarHeader.js b/client/components/VerticalBar/VerticalBarHeader.js
deleted file mode 100644
index f15d279c5fd4..000000000000
--- a/client/components/VerticalBar/VerticalBarHeader.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Box, Margins } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-function VerticalBarHeader({ children, ...props }) {
- return (
-
-
- {children}
-
-
- );
-}
-
-export default memo(VerticalBarHeader);
diff --git a/client/components/VerticalBar/VerticalBarHeader.tsx b/client/components/VerticalBar/VerticalBarHeader.tsx
new file mode 100644
index 000000000000..4f53dbeec16e
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarHeader.tsx
@@ -0,0 +1,34 @@
+import { Box, Margins } from '@rocket.chat/fuselage';
+import React, { FC, memo, ReactNode, ComponentProps } from 'react';
+
+const VerticalBarHeader: FC<{ children: ReactNode; props?: ComponentProps }> = ({
+ children,
+ ...props
+}) => (
+
+
+ {children}
+
+
+);
+
+export default memo(VerticalBarHeader);
diff --git a/client/components/VerticalBar/VerticalBarIcon.js b/client/components/VerticalBar/VerticalBarIcon.js
deleted file mode 100644
index dd0bad136b3f..000000000000
--- a/client/components/VerticalBar/VerticalBarIcon.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Icon } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-function VerticalBarIcon(props) {
- return ;
-}
-
-export default memo(VerticalBarIcon);
diff --git a/client/components/VerticalBar/VerticalBarIcon.tsx b/client/components/VerticalBar/VerticalBarIcon.tsx
new file mode 100644
index 000000000000..41adf6183d52
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarIcon.tsx
@@ -0,0 +1,8 @@
+import { Icon } from '@rocket.chat/fuselage';
+import React, { ReactElement, ComponentProps, memo } from 'react';
+
+const VerticalBarIcon = (props: ComponentProps): ReactElement => (
+
+);
+
+export default memo(VerticalBarIcon);
diff --git a/client/components/VerticalBar/VerticalBarInnerContent.js b/client/components/VerticalBar/VerticalBarInnerContent.js
deleted file mode 100644
index 6665d85c5f14..000000000000
--- a/client/components/VerticalBar/VerticalBarInnerContent.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Box } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-function VerticalBarInnerContent(props) {
- return (
-
- );
-}
-
-export default memo(VerticalBarInnerContent);
diff --git a/client/components/VerticalBar/VerticalBarInnerContent.tsx b/client/components/VerticalBar/VerticalBarInnerContent.tsx
new file mode 100644
index 000000000000..f36ba7a32e01
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarInnerContent.tsx
@@ -0,0 +1,15 @@
+import { Box } from '@rocket.chat/fuselage';
+import React, { ReactElement, memo, ComponentProps } from 'react';
+
+const VerticalBarInnerContent = (props: ComponentProps): ReactElement => (
+
+);
+
+export default memo(VerticalBarInnerContent);
diff --git a/client/components/VerticalBar/VerticalBarScrollableContent.js b/client/components/VerticalBar/VerticalBarScrollableContent.tsx
similarity index 53%
rename from client/components/VerticalBar/VerticalBarScrollableContent.js
rename to client/components/VerticalBar/VerticalBarScrollableContent.tsx
index 28d368aa6897..a007f66a0cf6 100644
--- a/client/components/VerticalBar/VerticalBarScrollableContent.js
+++ b/client/components/VerticalBar/VerticalBarScrollableContent.tsx
@@ -1,12 +1,12 @@
import { Margins } from '@rocket.chat/fuselage';
-import React, { forwardRef, memo } from 'react';
+import React, { forwardRef, memo, ComponentProps } from 'react';
import Page from '../Page';
-const VerticalBarScrollableContent = forwardRef(function VerticalBarScrollableContent(
- { children, ...props },
- ref,
-) {
+const VerticalBarScrollableContent = forwardRef<
+ HTMLElement,
+ ComponentProps
+>(function VerticalBarScrollableContent({ children, ...props }, ref) {
return (
{children}
diff --git a/client/components/VerticalBar/VerticalBarSkeleton.js b/client/components/VerticalBar/VerticalBarSkeleton.js
deleted file mode 100644
index 7915a9d72c3b..000000000000
--- a/client/components/VerticalBar/VerticalBarSkeleton.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Box, Skeleton } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-import VerticalBar from './VerticalBar';
-import VerticalBarHeader from './VerticalBarHeader';
-
-function VerticalBarSkeleton(props) {
- return (
-
-
-
-
-
-
- {Array(5)
- .fill()
- .map((_, index) => (
-
- ))}
-
-
- );
-}
-
-export default memo(VerticalBarSkeleton);
diff --git a/client/components/VerticalBar/VerticalBarSkeleton.tsx b/client/components/VerticalBar/VerticalBarSkeleton.tsx
new file mode 100644
index 000000000000..184c78362881
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarSkeleton.tsx
@@ -0,0 +1,23 @@
+import { Box, Skeleton } from '@rocket.chat/fuselage';
+import React, { ReactElement, ComponentProps, memo } from 'react';
+
+import VerticalBar from './VerticalBar';
+import VerticalBarHeader from './VerticalBarHeader';
+
+const VerticalBarSkeleton = (props: ComponentProps): ReactElement => (
+
+
+
+
+
+
+ {Array(5)
+ .fill(5)
+ .map((_, index) => (
+
+ ))}
+
+
+);
+
+export default memo(VerticalBarSkeleton);
diff --git a/client/components/VerticalBar/VerticalBarText.js b/client/components/VerticalBar/VerticalBarText.js
deleted file mode 100644
index e9c7cf9da900..000000000000
--- a/client/components/VerticalBar/VerticalBarText.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Box } from '@rocket.chat/fuselage';
-import React, { memo } from 'react';
-
-function VerticalBarText(props) {
- return ;
-}
-
-export default memo(VerticalBarText);
diff --git a/client/components/VerticalBar/VerticalBarText.tsx b/client/components/VerticalBar/VerticalBarText.tsx
new file mode 100644
index 000000000000..f7e8a81c7d58
--- /dev/null
+++ b/client/components/VerticalBar/VerticalBarText.tsx
@@ -0,0 +1,8 @@
+import { Box } from '@rocket.chat/fuselage';
+import React, { ReactElement, memo, ComponentProps } from 'react';
+
+const VerticalBarText = (props: ComponentProps): ReactElement => (
+
+);
+
+export default memo(VerticalBarText);
diff --git a/client/components/VerticalBar/index.d.ts b/client/components/VerticalBar/index.d.ts
deleted file mode 100644
index bb71a8db2160..000000000000
--- a/client/components/VerticalBar/index.d.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { ActionButton, Box, Button, ButtonGroup, Icon, Scrollable } from '@rocket.chat/fuselage';
-import {
- ComponentProps,
- FC,
- ForwardRefExoticComponent,
- PropsWithoutRef,
- RefAttributes,
-} from 'react';
-
-type VerticalBarType = FC> & {
- InnerContent: FC>;
- Icon: FC>;
- Footer: ForwardRefExoticComponent<
- PropsWithoutRef> & RefAttributes
- >;
- Text: FC>;
- Action: FC, 'icon'>>;
- Actions: FC>;
- Header: FC>;
- Close: FC, 'icon'>>;
- Content: ForwardRefExoticComponent<
- PropsWithoutRef> & RefAttributes
- >;
-
- ScrollableContent: ForwardRefExoticComponent<
- PropsWithoutRef<
- ComponentProps['onScrollContent'] &
- Omit, 'name'>
- > &
- RefAttributes
- >;
- Skeleton: FC>;
- Button: FC>;
- Back: FC>;
-};
-
-declare const VerticalBar: VerticalBarType;
-
-export = VerticalBar;
diff --git a/client/components/VerticalBar/index.js b/client/components/VerticalBar/index.ts
similarity index 100%
rename from client/components/VerticalBar/index.js
rename to client/components/VerticalBar/index.ts
diff --git a/client/components/burger/BurgerBadge.js b/client/components/burger/BurgerBadge.js
deleted file mode 100644
index 37c0f68f3c49..000000000000
--- a/client/components/burger/BurgerBadge.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Badge } from '@rocket.chat/fuselage';
-import React from 'react';
-
-function BurgerBadge({ children }) {
- return (
-
- );
-}
-
-export default BurgerBadge;
diff --git a/client/components/burger/Line.js b/client/components/burger/Line.js
deleted file mode 100644
index 7fc323e384a3..000000000000
--- a/client/components/burger/Line.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { css } from '@rocket.chat/css-in-js';
-import { Box } from '@rocket.chat/fuselage';
-import React from 'react';
-
-const Line = ({ animated, moved }) => (
-
-);
-
-export default Line;
diff --git a/client/components/modals/FileUploadModal/FilePreview.tsx b/client/components/modals/FileUploadModal/FilePreview.tsx
index f518dc7ebf40..24dba78c748a 100644
--- a/client/components/modals/FileUploadModal/FilePreview.tsx
+++ b/client/components/modals/FileUploadModal/FilePreview.tsx
@@ -1,6 +1,6 @@
import React, { ReactElement } from 'react';
-import { isIE11 } from '../../../../app/ui-utils/client/lib/isIE11';
+import { isIE11 } from '../../../lib/utils/isIE11';
import GenericPreview from './GenericPreview';
import MediaPreview from './MediaPreview';
@@ -25,7 +25,7 @@ const shouldShowMediaPreview = (file: File, fileType: FilePreviewType | undefine
if (!fileType) {
return false;
}
- if (isIE11()) {
+ if (isIE11) {
return false;
}
// Avoid preview if file size bigger than 10mb
diff --git a/client/components/modals/FileUploadModal/GenericPreview.tsx b/client/components/modals/FileUploadModal/GenericPreview.tsx
index 26c5884eb143..4f74d98aa8db 100644
--- a/client/components/modals/FileUploadModal/GenericPreview.tsx
+++ b/client/components/modals/FileUploadModal/GenericPreview.tsx
@@ -1,7 +1,7 @@
import { Box, Icon } from '@rocket.chat/fuselage';
import React, { ReactElement } from 'react';
-import { formatBytes } from '../../../lib/formatBytes';
+import { formatBytes } from '../../../lib/utils/formatBytes';
const GenericPreview = ({ file }: { file: File }): ReactElement => (
diff --git a/client/contexts/EditableSettingsContext.ts b/client/contexts/EditableSettingsContext.ts
index 100c641c86de..dc1d5bcc3771 100644
--- a/client/contexts/EditableSettingsContext.ts
+++ b/client/contexts/EditableSettingsContext.ts
@@ -1,12 +1,13 @@
import { createContext, useContext, useMemo } from 'react';
import { useSubscription, Subscription, Unsubscribe } from 'use-subscription';
-import { ISetting, SectionName, SettingId, GroupId } from '../../definition/ISetting';
+import { ISetting, SectionName, SettingId, GroupId, TabId } from '../../definition/ISetting';
import { SettingsContextQuery } from './SettingsContext';
export interface IEditableSetting extends ISetting {
disabled: boolean;
changed: boolean;
+ invisible: boolean;
}
export type EditableSettingsContextQuery = SettingsContextQuery & {
@@ -18,7 +19,8 @@ export type EditableSettingsContextValue = {
readonly queryEditableSettings: (
query: EditableSettingsContextQuery,
) => Subscription;
- readonly queryGroupSections: (_id: GroupId) => Subscription;
+ readonly queryGroupSections: (_id: GroupId, tab?: TabId) => Subscription;
+ readonly queryGroupTabs: (_id: GroupId) => Subscription;
readonly dispatch: (changes: Partial[]) => void;
};
@@ -35,6 +37,10 @@ export const EditableSettingsContext = createContext [],
subscribe: (): Unsubscribe => (): void => undefined,
}),
+ queryGroupTabs: () => ({
+ getCurrentValue: (): TabId[] => [],
+ subscribe: (): Unsubscribe => (): void => undefined,
+ }),
dispatch: () => undefined,
});
@@ -54,10 +60,17 @@ export const useEditableSettings = (query?: EditableSettingsContextQuery): IEdit
return useSubscription(subscription);
};
-export const useEditableSettingsGroupSections = (_id: SettingId): SectionName[] => {
+export const useEditableSettingsGroupSections = (_id: SettingId, tab?: TabId): SectionName[] => {
const { queryGroupSections } = useContext(EditableSettingsContext);
- const subscription = useMemo(() => queryGroupSections(_id), [queryGroupSections, _id]);
+ const subscription = useMemo(() => queryGroupSections(_id, tab), [queryGroupSections, _id, tab]);
+ return useSubscription(subscription);
+};
+
+export const useEditableSettingsGroupTabs = (_id: SettingId): TabId[] => {
+ const { queryGroupTabs } = useContext(EditableSettingsContext);
+
+ const subscription = useMemo(() => queryGroupTabs(_id), [queryGroupTabs, _id]);
return useSubscription(subscription);
};
diff --git a/client/contexts/ModalContext.ts b/client/contexts/ModalContext.ts
index 5cbd9deaccf2..0f36c6ece8ae 100644
--- a/client/contexts/ModalContext.ts
+++ b/client/contexts/ModalContext.ts
@@ -1,12 +1,16 @@
import { createContext, useContext, ReactNode } from 'react';
-type ModalContextValue = unknown & {
+import { modal } from '../../app/ui-utils/client';
+
+type ModalContextValue = typeof modal & {
setModal: (modal: ReactNode) => void;
};
-export const ModalContext = createContext({
- setModal: () => undefined,
-});
+export const ModalContext = createContext(
+ Object.assign(modal, {
+ setModal: () => undefined,
+ }),
+);
export const useModal = (): ModalContextValue => useContext(ModalContext);
diff --git a/client/contexts/ServerContext/ServerContext.ts b/client/contexts/ServerContext/ServerContext.ts
index 8467dccad400..d791fea0d2d4 100644
--- a/client/contexts/ServerContext/ServerContext.ts
+++ b/client/contexts/ServerContext/ServerContext.ts
@@ -1,13 +1,8 @@
import { createContext, useCallback, useContext, useMemo } from 'react';
-import {
- ServerEndpointMethodOf,
- ServerEndpointPath,
- ServerEndpointFunction,
- ServerEndpointRequestPayload,
- ServerEndpointFormData,
- ServerEndpointResponsePayload,
-} from './endpoints';
+import { IServerInfo } from '../../../definition/IServerInfo';
+import type { Serialized } from '../../../definition/Serialized';
+import type { PathFor, Params, Return, Method } from './endpoints';
import {
ServerMethodFunction,
ServerMethodName,
@@ -17,18 +12,17 @@ import {
} from './methods';
type ServerContextValue = {
- info: {};
+ info?: IServerInfo;
absoluteUrl: (path: string) => string;
callMethod?: (
methodName: MethodName,
...args: ServerMethodParameters
) => Promise>;
- callEndpoint?: , Path extends ServerEndpointPath>(
- httpMethod: Method,
- endpoint: Path,
- params: ServerEndpointRequestPayload,
- formData?: ServerEndpointFormData,
- ) => Promise>;
+ callEndpoint: >(
+ method: M,
+ path: P,
+ params: Params[0],
+ ) => Promise>>;
uploadToEndpoint: (endpoint: string, params: any, formData: any) => Promise;
getStream: (
streamName: string,
@@ -37,13 +31,22 @@ type ServerContextValue = {
};
export const ServerContext = createContext({
- info: {},
+ info: undefined,
absoluteUrl: (path) => path,
+ callEndpoint: () => {
+ throw new Error('not implemented');
+ },
uploadToEndpoint: async () => undefined,
getStream: () => () => (): void => undefined,
});
-export const useServerInformation = (): {} => useContext(ServerContext).info;
+export const useServerInformation = (): IServerInfo => {
+ const { info } = useContext(ServerContext);
+ if (!info) {
+ throw new Error('useServerInformation: no info available');
+ }
+ return info;
+};
export const useAbsoluteUrl = (): ((path: string) => string) =>
useContext(ServerContext).absoluteUrl;
@@ -67,30 +70,13 @@ export const useMethod = (
);
};
-export const useEndpoint = <
- Method extends ServerEndpointMethodOf,
- Path extends ServerEndpointPath,
->(
- httpMethod: Method,
- endpoint: Path,
-): ServerEndpointFunction => {
+export const useEndpoint = >(
+ method: M,
+ path: P,
+): ((params: Params[0]) => Promise>>) => {
const { callEndpoint } = useContext(ServerContext);
- return useCallback(
- (
- params: ServerEndpointRequestPayload,
- formData?: ServerEndpointFormData,
- ) => {
- if (!callEndpoint) {
- throw new Error(
- `cannot use useEndpoint(${httpMethod}, ${endpoint}) hook without a wrapping ServerContext`,
- );
- }
-
- return callEndpoint(httpMethod, endpoint, params, formData);
- },
- [callEndpoint, endpoint, httpMethod],
- );
+ return useCallback((params) => callEndpoint(method, path, params), [callEndpoint, path, method]);
};
export const useUpload = (endpoint: string): ((params: any, formData: any) => Promise) => {
diff --git a/client/contexts/ServerContext/endpoints.ts b/client/contexts/ServerContext/endpoints.ts
index f2a4c07a3774..0a57ef4479d0 100644
--- a/client/contexts/ServerContext/endpoints.ts
+++ b/client/contexts/ServerContext/endpoints.ts
@@ -1,120 +1,87 @@
-import { EngagementDashboardActiveUsersEndpoint } from '../../../ee/app/engagement-dashboard/client/contexts/ServerContext/endpoints/EngagementDashboardActiveUsers';
-import { ExternalComponentsEndpoint as AppsExternalComponentsEndpoint } from './endpoints/apps/externalComponents';
-import { FilesEndpoint as ChannelsFilesEndpoint } from './endpoints/v1/channels/files';
-import { ChannelsMembersEndpoint } from './endpoints/v1/channels/members';
-import { FollowMessageEndpoint as ChatFollowMessageEndpoint } from './endpoints/v1/chat/followMessage';
-import { GetDiscussionsEndpoint as ChatGetDiscussionsEndpoint } from './endpoints/v1/chat/getDiscussions';
-import { GetMessageEndpoint as ChatGetMessageEndpoint } from './endpoints/v1/chat/getMessage';
-import { GetThreadsListEndpoint as ChatGetThreadsListEndpoint } from './endpoints/v1/chat/getThreadsList';
-import { UnfollowMessageEndpoint as ChatUnfollowMessageEndpoint } from './endpoints/v1/chat/unfollowMessage';
-import { ManualRegisterEndpoint as CloudManualRegisterEndpoint } from './endpoints/v1/cloud/manualRegister';
-import { ListEndpoint as CustomUserStatusListEndpoint } from './endpoints/v1/custom-user-status/list';
-import { ResolveSrvEndpoint } from './endpoints/v1/dns/resolve.srv';
-import { ResolveTxtEndpoint } from './endpoints/v1/dns/resolve.txt';
-import { ListEndpoint as EmojiCustomListEndpoint } from './endpoints/v1/emoji-custom/list';
-import { FilesEndpoint as GroupsFilesEndpoint } from './endpoints/v1/groups/files';
-import { GroupsMembersEndpoint } from './endpoints/v1/groups/members';
-import { FilesEndpoint as ImFilesEndpoint } from './endpoints/v1/im/files';
-import { ImMembersEndpoint } from './endpoints/v1/im/members';
-import { AppearanceEndpoint as LivechatAppearanceEndpoint } from './endpoints/v1/livechat/appearance';
-import { LivechatCustomFieldsEndpoint } from './endpoints/v1/livechat/customFields';
-import { LivechatDepartment } from './endpoints/v1/livechat/department';
-import { LivechatDepartmentSingle } from './endpoints/v1/livechat/departmentSingle';
-import { LivechatDepartmentsByUnit } from './endpoints/v1/livechat/departmentsByUnit';
-import { LivechatMonitorsList } from './endpoints/v1/livechat/monitorsList';
-import { LivechatRoomOnHoldEndpoint } from './endpoints/v1/livechat/onHold';
-import { LivechatRoomsEndpoint } from './endpoints/v1/livechat/rooms';
-import { LivechatTagsList } from './endpoints/v1/livechat/tagsList';
-import { LivechatUsersAgentEndpoint } from './endpoints/v1/livechat/usersAgent';
-import { LivechatVisitorInfoEndpoint } from './endpoints/v1/livechat/visitorInfo';
-import { CannedResponseEndpoint } from './endpoints/v1/omnichannel/cannedResponse';
-import { CannedResponsesEndpoint } from './endpoints/v1/omnichannel/cannedResponses';
-import { AutocompleteAvailableForTeamsEndpoint as RoomsAutocompleteTeamsEndpoint } from './endpoints/v1/rooms/autocompleteAvailableForTeams';
-import { AutocompleteChannelAndPrivateEndpoint as RoomsAutocompleteEndpoint } from './endpoints/v1/rooms/autocompleteChannelAndPrivate';
-import { RoomsInfo as RoomsInfoEndpoint } from './endpoints/v1/rooms/roomsInfo';
-import { AddRoomsEndpoint as TeamsAddRoomsEndpoint } from './endpoints/v1/teams/addRooms';
-import { ListRoomsEndpoint } from './endpoints/v1/teams/listRooms';
-import { ListRoomsOfUserEndpoint } from './endpoints/v1/teams/listRoomsOfUser';
-import { AutocompleteEndpoint as UsersAutocompleteEndpoint } from './endpoints/v1/users/autocomplete';
-import { SendEmailCodeEndpoint } from './endpoints/v1/users/twoFactorAuth/sendEmailCode';
+import type { ExtractKeys, ValueOf } from '../../../definition/utils';
+import type { EngagementDashboardEndpoints } from '../../../ee/client/contexts/ServerContext/endpoints/v1/engagementDashboard';
+import type { AppsEndpoints } from './endpoints/apps';
+import type { ChannelsEndpoints } from './endpoints/v1/channels';
+import type { ChatEndpoints } from './endpoints/v1/chat';
+import type { CloudEndpoints } from './endpoints/v1/cloud';
+import type { CustomUserStatusEndpoints } from './endpoints/v1/customUserStatus';
+import type { DmEndpoints } from './endpoints/v1/dm';
+import type { DnsEndpoints } from './endpoints/v1/dns';
+import type { EmojiCustomEndpoints } from './endpoints/v1/emojiCustom';
+import type { GroupsEndpoints } from './endpoints/v1/groups';
+import type { ImEndpoints } from './endpoints/v1/im';
+import type { LDAPEndpoints } from './endpoints/v1/ldap';
+import type { LicensesEndpoints } from './endpoints/v1/licenses';
+import type { MiscEndpoints } from './endpoints/v1/misc';
+import type { OmnichannelEndpoints } from './endpoints/v1/omnichannel';
+import type { RoomsEndpoints } from './endpoints/v1/rooms';
+import type { StatisticsEndpoints } from './endpoints/v1/statistics';
+import type { TeamsEndpoints } from './endpoints/v1/teams';
+import type { UsersEndpoints } from './endpoints/v1/users';
-export type ServerEndpoints = {
- 'chat.getMessage': ChatGetMessageEndpoint;
- 'chat.followMessage': ChatFollowMessageEndpoint;
- 'chat.unfollowMessage': ChatUnfollowMessageEndpoint;
- 'cloud.manualRegister': CloudManualRegisterEndpoint;
- 'chat.getDiscussions': ChatGetDiscussionsEndpoint;
- 'chat.getThreadsList': ChatGetThreadsListEndpoint;
- 'dns.resolve.srv': ResolveSrvEndpoint;
- 'dns.resolve.txt': ResolveTxtEndpoint;
- 'emoji-custom.list': EmojiCustomListEndpoint;
- 'channels.files': ChannelsFilesEndpoint;
- 'im.files': ImFilesEndpoint;
- 'im.members': ImMembersEndpoint;
- 'groups.files': GroupsFilesEndpoint;
- 'groups.members': GroupsMembersEndpoint;
- 'channels.members': ChannelsMembersEndpoint;
- 'users.autocomplete': UsersAutocompleteEndpoint;
- 'livechat/appearance': LivechatAppearanceEndpoint;
- 'custom-user-status.list': CustomUserStatusListEndpoint;
- '/apps/externalComponents': AppsExternalComponentsEndpoint;
- 'rooms.autocomplete.channelAndPrivate': RoomsAutocompleteEndpoint;
- 'rooms.autocomplete.availableForTeams': RoomsAutocompleteTeamsEndpoint;
- 'teams.listRooms': ListRoomsEndpoint;
- 'teams.listRoomsOfUser': ListRoomsOfUserEndpoint;
- 'teams.addRooms': TeamsAddRoomsEndpoint;
- 'livechat/visitors.info': LivechatVisitorInfoEndpoint;
- 'livechat/room.onHold': LivechatRoomOnHoldEndpoint;
- 'livechat/monitors.list': LivechatMonitorsList;
- 'livechat/tags.list': LivechatTagsList;
- 'livechat/department': LivechatDepartment;
- 'livechat/department/${string}': LivechatDepartmentSingle;
- 'livechat/departments.by-unit/': LivechatDepartmentsByUnit;
- 'engagement-dashboard/users/active-users': EngagementDashboardActiveUsersEndpoint;
- 'rooms.info': RoomsInfoEndpoint;
- 'users.2fa.sendEmailCode': SendEmailCodeEndpoint;
- 'livechat/custom-fields': LivechatCustomFieldsEndpoint;
- 'livechat/rooms': LivechatRoomsEndpoint;
- 'livechat/users/agent': LivechatUsersAgentEndpoint;
- 'canned-responses': CannedResponsesEndpoint;
- 'canned-responses/${string}': CannedResponseEndpoint;
-};
+type Endpoints = ChatEndpoints &
+ ChannelsEndpoints &
+ CloudEndpoints &
+ CustomUserStatusEndpoints &
+ DmEndpoints &
+ DnsEndpoints &
+ EmojiCustomEndpoints &
+ GroupsEndpoints &
+ ImEndpoints &
+ LDAPEndpoints &
+ RoomsEndpoints &
+ TeamsEndpoints &
+ UsersEndpoints &
+ EngagementDashboardEndpoints &
+ AppsEndpoints &
+ OmnichannelEndpoints &
+ StatisticsEndpoints &
+ LicensesEndpoints &
+ MiscEndpoints;
-export type ServerEndpointPath = keyof ServerEndpoints;
-export type ServerEndpointMethodOf = keyof ServerEndpoints[Path] &
- ('GET' | 'POST' | 'DELETE');
+type Endpoint = UnionizeEndpoints;
-type ServerEndpoint<
- Method extends ServerEndpointMethodOf,
- Path extends ServerEndpointPath,
-> = ServerEndpoints[Path][Method] extends (...args: any[]) => any
- ? ServerEndpoints[Path][Method]
- : (...args: any[]) => any;
+type UnionizeEndpoints = ValueOf<
+ {
+ [P in keyof EE]: UnionizeMethods;
+ }
+>;
-export type ServerEndpointRequestPayload<
- Method extends ServerEndpointMethodOf,
- Path extends ServerEndpointPath,
-> = Parameters>[0];
+type ExtractOperations = ExtractKeys any>;
-export type ServerEndpointFormData<
- Method extends ServerEndpointMethodOf,
- Path extends ServerEndpointPath,
-> = Parameters>[1];
+type UnionizeMethods = ValueOf<
+ {
+ [M in keyof OO as ExtractOperations]: (
+ method: M,
+ path: OO extends { path: string } ? OO['path'] : P,
+ ...params: Parameters