diff --git a/apps/meteor/app/api/server/lib/messages.ts b/apps/meteor/app/api/server/lib/messages.ts
index d6955ce7b933..71f9f540c6bf 100644
--- a/apps/meteor/app/api/server/lib/messages.ts
+++ b/apps/meteor/app/api/server/lib/messages.ts
@@ -55,7 +55,7 @@ export async function findStarredMessages({
}): Promise<{
messages: IMessage[];
count: number;
- offset: any;
+ offset: number;
total: number;
}> {
const room = await Rooms.findOneById(roomId);
diff --git a/apps/meteor/app/mentions-flextab/client/actionButton.ts b/apps/meteor/app/mentions-flextab/client/actionButton.ts
deleted file mode 100644
index aa39b619f3d4..000000000000
--- a/apps/meteor/app/mentions-flextab/client/actionButton.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { Template } from 'meteor/templating';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-
-import { MessageAction, RoomHistoryManager } from '../../ui-utils/client';
-import { messageArgs } from '../../../client/lib/utils/messageArgs';
-import { Rooms } from '../../models/client';
-
-Meteor.startup(function () {
- MessageAction.addButton({
- id: 'jump-to-message',
- icon: 'jump',
- label: 'Jump_to_message',
- context: ['mentions', 'threads'],
- action(e, props) {
- e.preventDefault();
- e.stopPropagation();
- const { message = messageArgs(this).msg } = props;
- if (window.matchMedia('(max-width: 500px)').matches) {
- (Template.instance() as any).tabBar.close();
- }
- if (message.tmid) {
- return FlowRouter.go(
- FlowRouter.getRouteName(),
- {
- tab: 'thread',
- context: message.tmid,
- rid: message.rid,
- name: Rooms.findOne({ _id: message.rid })?.name ?? '',
- },
- {
- jump: message._id,
- },
- );
- }
- RoomHistoryManager.getSurroundingMessages(message);
- },
- order: 100,
- group: ['message', 'menu'],
- });
-});
diff --git a/apps/meteor/app/mentions-flextab/client/index.js b/apps/meteor/app/mentions-flextab/client/index.js
deleted file mode 100644
index f1443a980322..000000000000
--- a/apps/meteor/app/mentions-flextab/client/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import './views/mentionsFlexTab.html';
-import './views/mentionsFlexTab';
-import './actionButton';
-import './tabBar';
diff --git a/apps/meteor/app/mentions-flextab/client/tabBar.ts b/apps/meteor/app/mentions-flextab/client/tabBar.ts
deleted file mode 100644
index fa88a0f2d96d..000000000000
--- a/apps/meteor/app/mentions-flextab/client/tabBar.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { addAction } from '../../../client/views/room/lib/Toolbox';
-
-addAction('mentions', {
- groups: ['channel', 'group', 'team'],
- id: 'mentions',
- title: 'Mentions',
- icon: 'at',
- template: 'mentionsFlexTab',
- order: 9,
-});
diff --git a/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.html b/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.html
deleted file mode 100644
index 4884793a3156..000000000000
--- a/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
- {{#if Template.subscriptionsReady}}
- {{#unless hasMessages}}
-
- {{/unless}}
- {{/if}}
-
-
- {{# with messageContext}}
- {{#each msg in messages}}{{> message groupable=false msg=msg room=room subscription=subscription settings=settings u=u customClass="mentions" context="mentions"}}{{/each}}
- {{/with}}
-
- {{#if hasMore}}
-
- {{> loading}}
-
- {{/if}}
-
-
diff --git a/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.js b/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.js
deleted file mode 100644
index 6ae77a09fa24..000000000000
--- a/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import _ from 'underscore';
-import { Meteor } from 'meteor/meteor';
-import { Mongo } from 'meteor/mongo';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-
-import { createMessageContext } from '../../../ui-utils/client/lib/messageContext';
-import { upsertMessageBulk } from '../../../ui-utils/client/lib/RoomHistoryManager';
-import { APIClient } from '../../../utils/client';
-import { Messages, Users } from '../../../models/client';
-import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
-
-const LIMIT_DEFAULT = 50;
-
-Template.mentionsFlexTab.helpers({
- hasMessages() {
- return Template.instance().messages.find().count();
- },
- messages() {
- const instance = Template.instance();
- return instance.messages.find({}, { limit: instance.limit.get(), sort: { ts: -1 } });
- },
- hasMore() {
- return Template.instance().hasMore.get();
- },
- messageContext: createMessageContext,
-});
-
-Template.mentionsFlexTab.onCreated(function () {
- this.messages = new Mongo.Collection(null);
-
- this.hasMore = new ReactiveVar(true);
- this.limit = new ReactiveVar(LIMIT_DEFAULT);
-
- this.autorun(() => {
- const query = {
- '_hidden': { $ne: true },
- 'mentions.username': Users.findOne(Meteor.userId(), { fields: { username: 1 } }).username,
- 'rid': this.data.rid,
- '_updatedAt': {
- $gt: new Date(),
- },
- };
-
- this.cursor && this.cursor.stop();
-
- this.limit.set(LIMIT_DEFAULT);
-
- this.cursor = Messages.find(query).observe({
- added: ({ _id, ...message }) => {
- this.messages.upsert({ _id }, message);
- },
- changed: ({ _id, ...message }) => {
- this.messages.upsert({ _id }, message);
- },
- removed: ({ _id }) => {
- this.messages.remove({ _id });
- },
- });
- });
-
- this.autorun(async () => {
- const limit = this.limit.get();
- const { messages, total } = await APIClient.get('/v1/chat.getMentionedMessages', {
- roomId: this.data.rid,
- count: limit,
- });
-
- upsertMessageBulk({ msgs: messages }, this.messages);
-
- this.hasMore.set(total > limit);
- });
-});
-
-Template.mentionsFlexTab.onDestroyed(function () {
- this.cursor.stop();
-});
-
-Template.mentionsFlexTab.events({
- ...getCommonRoomEvents(),
- 'scroll .js-list': _.throttle(function (e, instance) {
- if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) {
- return instance.limit.set(instance.limit.get() + 50);
- }
- }, 200),
-});
diff --git a/apps/meteor/app/message-pin/client/actionButton.ts b/apps/meteor/app/message-pin/client/actionButton.ts
deleted file mode 100644
index a661ba0c0b7a..000000000000
--- a/apps/meteor/app/message-pin/client/actionButton.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-
-import { RoomHistoryManager, MessageAction } from '../../ui-utils/client';
-import { messageArgs } from '../../../client/lib/utils/messageArgs';
-import { settings } from '../../settings/client';
-import { hasAtLeastOnePermission } from '../../authorization/client';
-import { Rooms } from '../../models/client';
-import { dispatchToastMessage } from '../../../client/lib/toast';
-import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
-
-Meteor.startup(function () {
- MessageAction.addButton({
- id: 'pin-message',
- icon: 'pin',
- label: 'Pin',
- context: ['pinned', 'message', 'message-mobile', 'threads', 'direct'],
- action(_, props) {
- const { message = messageArgs(this).msg } = props;
- message.pinned = true;
- Meteor.call('pinMessage', message, function (error: Error) {
- if (error) {
- dispatchToastMessage({ type: 'error', message: error });
- }
- });
- },
- condition({ message, subscription, room }) {
- if (!settings.get('Message_AllowPinning') || message.pinned || !subscription) {
- return false;
- }
- const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
- if (isLivechatRoom) {
- return false;
- }
- return hasAtLeastOnePermission('pin-message', message.rid);
- },
- order: 7,
- group: 'menu',
- });
-
- MessageAction.addButton({
- id: 'unpin-message',
- icon: 'pin',
- label: 'Unpin',
- context: ['pinned', 'message', 'message-mobile', 'threads', 'direct'],
- action(_, props) {
- const { message = messageArgs(this).msg } = props;
- message.pinned = false;
- Meteor.call('unpinMessage', message, function (error: Error) {
- if (error) {
- dispatchToastMessage({ type: 'error', message: error });
- }
- });
- },
- condition({ message, subscription }) {
- if (!subscription || !settings.get('Message_AllowPinning') || !message.pinned) {
- return false;
- }
-
- return hasAtLeastOnePermission('pin-message', message.rid);
- },
- order: 8,
- group: 'menu',
- });
-
- MessageAction.addButton({
- id: 'jump-to-pin-message',
- icon: 'jump',
- label: 'Jump_to_message',
- context: ['pinned', 'message-mobile', 'direct'],
- action(_, props) {
- const { message = messageArgs(this).msg } = props;
- if (window.matchMedia('(max-width: 500px)').matches) {
- (Template.instance() as any).tabBar.close();
- }
- if (message.tmid) {
- return FlowRouter.go(
- FlowRouter.getRouteName(),
- {
- tab: 'thread',
- context: message.tmid,
- rid: message.rid,
- jump: message._id,
- name: Rooms.findOne({ _id: message.rid })?.name ?? '',
- },
- {
- jump: message._id,
- },
- );
- }
- return RoomHistoryManager.getSurroundingMessages(message);
- },
- condition({ subscription }) {
- return !!subscription;
- },
- order: 100,
- group: ['message', 'menu'],
- });
-
- MessageAction.addButton({
- id: 'permalink-pinned',
- icon: 'permalink',
- label: 'Get_link',
- // classes: 'clipboard',
- context: ['pinned'],
- async action(_, props) {
- try {
- const { message = messageArgs(this).msg } = props;
- const permalink = await MessageAction.getPermaLink(message._id);
- navigator.clipboard.writeText(permalink);
- dispatchToastMessage({ type: 'success', message: TAPi18n.__('Copied') });
- } catch (e) {
- dispatchToastMessage({ type: 'error', message: e });
- }
- },
- condition({ subscription }) {
- return !!subscription;
- },
- order: 101,
- group: 'menu',
- });
-});
diff --git a/apps/meteor/app/message-pin/client/index.js b/apps/meteor/app/message-pin/client/index.js
deleted file mode 100644
index c48cf4ab0b98..000000000000
--- a/apps/meteor/app/message-pin/client/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import './actionButton';
-import './messageType';
-import './pinMessage';
-import './tabBar';
-import './views/pinnedMessages.html';
-import './views/pinnedMessages';
-import './views/stylesheets/messagepin.css';
diff --git a/apps/meteor/app/message-pin/client/messageType.js b/apps/meteor/app/message-pin/client/messageType.js
deleted file mode 100644
index 0ce3da33853d..000000000000
--- a/apps/meteor/app/message-pin/client/messageType.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { MessageTypes } from '../../ui-utils';
-
-Meteor.startup(function () {
- MessageTypes.registerType({
- id: 'message_pinned',
- system: true,
- message: 'Pinned_a_message',
- });
-});
diff --git a/apps/meteor/app/message-pin/client/pinMessage.js b/apps/meteor/app/message-pin/client/pinMessage.js
deleted file mode 100644
index b45bed7839c3..000000000000
--- a/apps/meteor/app/message-pin/client/pinMessage.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-
-import { settings } from '../../settings';
-import { ChatMessage, Subscriptions } from '../../models/client';
-import { dispatchToastMessage } from '../../../client/lib/toast';
-
-Meteor.methods({
- pinMessage(message) {
- if (!Meteor.userId()) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-not-authorized') });
- return false;
- }
- if (!settings.get('Message_AllowPinning')) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('pinning-not-allowed') });
- return false;
- }
- if (Subscriptions.findOne({ rid: message.rid }) == null) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-pinning-message') });
- return false;
- }
- if (typeof message._id !== 'string') {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-pinning-message') });
- return false;
- }
- dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_pinned') });
- return ChatMessage.update(
- {
- _id: message._id,
- rid: message.rid,
- },
- {
- $set: {
- pinned: true,
- },
- },
- );
- },
- unpinMessage(message) {
- if (!Meteor.userId()) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-not-authorized') });
- return false;
- }
- if (!settings.get('Message_AllowPinning')) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('unpinning-not-allowed') });
- return false;
- }
- if (Subscriptions.findOne({ rid: message.rid }) == null) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-unpinning-message') });
- return false;
- }
- if (typeof message._id !== 'string') {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-unpinning-message') });
- return false;
- }
- dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_unpinned') });
- return ChatMessage.update(
- {
- _id: message._id,
- rid: message.rid,
- },
- {
- $set: {
- pinned: false,
- },
- },
- );
- },
-});
diff --git a/apps/meteor/app/message-pin/client/views/pinnedMessages.html b/apps/meteor/app/message-pin/client/views/pinnedMessages.html
deleted file mode 100644
index 69aa06225a8b..000000000000
--- a/apps/meteor/app/message-pin/client/views/pinnedMessages.html
+++ /dev/null
@@ -1,22 +0,0 @@
-
- {{#if Template.subscriptionsReady}}
- {{#unless hasMessages}}
-
- {{/unless}}
- {{/if}}
-
-
- {{# with messageContext}}
- {{#each msg in messages}}{{> message context="pinned" msg=msg room=room groupable=true subscription=subscription settings=settings u=u }}{{/each}}
- {{/with}}
-
-
- {{#if hasMore}}
-
- {{> loading}}
-
- {{/if}}
-
-
diff --git a/apps/meteor/app/message-pin/client/views/pinnedMessages.js b/apps/meteor/app/message-pin/client/views/pinnedMessages.js
deleted file mode 100644
index f5843f32d42e..000000000000
--- a/apps/meteor/app/message-pin/client/views/pinnedMessages.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import _ from 'underscore';
-import { Mongo } from 'meteor/mongo';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-
-import { upsertMessageBulk } from '../../../ui-utils/client/lib/RoomHistoryManager';
-import { createMessageContext } from '../../../ui-utils/client/lib/messageContext';
-import { APIClient } from '../../../utils/client';
-import { Messages } from '../../../models/client';
-import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
-
-const LIMIT_DEFAULT = 50;
-
-Template.pinnedMessages.helpers({
- hasMessages() {
- return Template.instance().messages.find().count();
- },
- messages() {
- const instance = Template.instance();
- return instance.messages.find({}, { limit: instance.limit.get(), sort: { ts: -1 } });
- },
- hasMore() {
- return Template.instance().hasMore.get();
- },
- messageContext: createMessageContext,
-});
-
-Template.pinnedMessages.onCreated(function () {
- this.pinnedMessages = new ReactiveVar([]);
- this.hasMore = new ReactiveVar(true);
- this.limit = new ReactiveVar(LIMIT_DEFAULT);
- this.rid = this.data.rid;
- this.messages = new Mongo.Collection(null);
-
- this.autorun(() => {
- const query = {
- t: { $ne: 'rm' },
- _hidden: { $ne: true },
- pinned: true,
- rid: this.data.rid,
- _updatedAt: {
- $gte: new Date(),
- },
- };
-
- this.cursor && this.cursor.stop();
-
- this.limit.set(LIMIT_DEFAULT);
-
- this.cursor = Messages.find(query).observe({
- added: ({ _id, ...message }) => {
- this.messages.upsert({ _id }, message);
- },
- changed: ({ _id, ...message }) => {
- this.messages.upsert({ _id }, message);
- },
- removed: ({ _id }) => {
- this.messages.remove({ _id });
- },
- });
- });
-
- this.autorun(async () => {
- const limit = this.limit.get();
- const { messages, total } = await APIClient.get('/v1/chat.getPinnedMessages', {
- roomId: this.rid,
- count: limit,
- });
-
- upsertMessageBulk({ msgs: messages }, this.messages);
-
- this.hasMore.set(total > limit);
- });
-});
-
-Template.mentionsFlexTab.onDestroyed(function () {
- this.cursor.stop();
-});
-
-Template.pinnedMessages.events({
- ...getCommonRoomEvents(),
- 'scroll .js-list': _.throttle(function (e, instance) {
- if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) {
- return instance.limit.set(instance.limit.get() + 50);
- }
- }, 200),
-});
diff --git a/apps/meteor/app/message-pin/client/views/stylesheets/messagepin.css b/apps/meteor/app/message-pin/client/views/stylesheets/messagepin.css
deleted file mode 100644
index c4dc8a9b1cef..000000000000
--- a/apps/meteor/app/message-pin/client/views/stylesheets/messagepin.css
+++ /dev/null
@@ -1,25 +0,0 @@
-.icon-pin.rotate-45::before {
- transform: rotate(45deg);
-}
-
-.pinned-messages-list {
- & li.empty {
- margin-top: 60px;
-
- text-align: center;
-
- color: #7f7f7f;
- }
-
- & .load-more {
- text-align: center;
- text-transform: lowercase;
-
- font-style: italic;
- line-height: 40px;
-
- & .load-more-loading {
- color: #aaaaaa;
- }
- }
-}
diff --git a/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js b/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js
index 3166433bb527..48da295d07bc 100644
--- a/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js
+++ b/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js
@@ -67,7 +67,7 @@ Template.snippetedMessages.onCreated(function () {
});
});
-Template.mentionsFlexTab.onDestroyed(function () {
+Template.snippetedMessages.onDestroyed(function () {
this.cursor.stop();
});
diff --git a/apps/meteor/app/message-star/client/actionButton.ts b/apps/meteor/app/message-star/client/actionButton.ts
deleted file mode 100644
index 8a37b39b3507..000000000000
--- a/apps/meteor/app/message-star/client/actionButton.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { Template } from 'meteor/templating';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-import { FlowRouter } from 'meteor/kadira:flow-router';
-
-import { settings } from '../../settings/client';
-import { RoomHistoryManager, MessageAction } from '../../ui-utils/client';
-import { messageArgs } from '../../../client/lib/utils/messageArgs';
-import { Rooms } from '../../models/client';
-import { dispatchToastMessage } from '../../../client/lib/toast';
-import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
-
-Meteor.startup(function () {
- MessageAction.addButton({
- id: 'star-message',
- icon: 'star',
- label: 'Star',
- context: ['starred', 'message', 'message-mobile', 'threads', 'federated'],
- action(_, props) {
- const { message = messageArgs(this).msg } = props;
- Meteor.call('starMessage', { ...message, starred: true }, function (error: any) {
- if (error) {
- dispatchToastMessage({ type: 'error', message: error });
- }
- });
- },
- condition({ message, subscription, user, room }) {
- if (subscription == null && settings.get('Message_AllowStarring')) {
- return false;
- }
- const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
- if (isLivechatRoom) {
- return false;
- }
-
- return !Array.isArray(message.starred) || !message.starred.find((star: any) => star._id === user?._id);
- },
- order: 9,
- group: 'menu',
- });
-
- MessageAction.addButton({
- id: 'unstar-message',
- icon: 'star',
- label: 'Unstar_Message',
- context: ['starred', 'message', 'message-mobile', 'threads', 'federated'],
- action(_, props) {
- const { message = messageArgs(this).msg } = props;
-
- Meteor.call('starMessage', { ...message, starred: false }, function (error?: any) {
- if (error) {
- dispatchToastMessage({ type: 'error', message: error });
- }
- });
- },
- condition({ message, subscription, user }) {
- if (subscription == null && settings.get('Message_AllowStarring')) {
- return false;
- }
-
- return Boolean(message.starred?.find((star: any) => star._id === user?._id));
- },
- order: 9,
- group: 'menu',
- });
-
- MessageAction.addButton({
- id: 'jump-to-star-message',
- icon: 'jump',
- label: 'Jump_to_message',
-
- context: ['starred', 'threads', 'message-mobile'],
- action() {
- const { msg: message } = messageArgs(this);
- if (window.matchMedia('(max-width: 500px)').matches) {
- (Template.instance() as any).tabBar.close();
- }
- if (message.tmid) {
- return FlowRouter.go(
- FlowRouter.getRouteName(),
- {
- tab: 'thread',
- context: message.tmid,
- rid: message.rid,
- jump: message._id,
- name: Rooms.findOne({ _id: message.rid })?.name ?? '',
- },
- {
- jump: message._id,
- },
- );
- }
- RoomHistoryManager.getSurroundingMessages(message);
- },
- condition({ message, subscription, user }) {
- if (subscription == null || !settings.get('Message_AllowStarring')) {
- return false;
- }
-
- return Boolean(message.starred?.find((star) => star._id === user?._id));
- },
- order: 100,
- group: ['message', 'menu'],
- });
-
- MessageAction.addButton({
- id: 'permalink-star',
- icon: 'permalink',
- label: 'Get_link',
- // classes: 'clipboard',
- context: ['starred', 'threads'],
- async action(_, props) {
- try {
- const { message = messageArgs(this).msg } = props;
- const permalink = await MessageAction.getPermaLink(message._id);
- navigator.clipboard.writeText(permalink);
- dispatchToastMessage({ type: 'success', message: TAPi18n.__('Copied') });
- } catch (e) {
- dispatchToastMessage({ type: 'error', message: e });
- }
- },
- condition({ message, subscription, user }) {
- if (subscription == null) {
- return false;
- }
-
- return Boolean(message.starred?.find((star) => star._id === user?._id));
- },
- order: 101,
- group: 'menu',
- });
-});
diff --git a/apps/meteor/app/message-star/client/index.js b/apps/meteor/app/message-star/client/index.js
deleted file mode 100644
index 68b7c6434334..000000000000
--- a/apps/meteor/app/message-star/client/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import './actionButton';
-import './starMessage';
-import './tabBar';
-import './views/starredMessages.html';
-import './views/starredMessages';
-import './views/stylesheets/messagestar.css';
diff --git a/apps/meteor/app/message-star/client/starMessage.js b/apps/meteor/app/message-star/client/starMessage.js
deleted file mode 100644
index 41e2c7ac7775..000000000000
--- a/apps/meteor/app/message-star/client/starMessage.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
-
-import { settings } from '../../settings';
-import { ChatMessage, Subscriptions } from '../../models/client';
-import { dispatchToastMessage } from '../../../client/lib/toast';
-
-Meteor.methods({
- starMessage(message) {
- if (!Meteor.userId()) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
- return false;
- }
- if (Subscriptions.findOne({ rid: message.rid }) == null) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
- return false;
- }
- if (!ChatMessage.findOneByRoomIdAndMessageId(message.rid, message._id)) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
- return false;
- }
- if (!settings.get('Message_AllowStarring')) {
- dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
- return false;
- }
- if (message.starred) {
- dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_starred') });
- } else {
- dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_unstarred') });
- }
- return ChatMessage.update(
- {
- _id: message._id,
- },
- {
- $addToSet: {
- starred: !!message.starred,
- },
- },
- );
- },
-});
diff --git a/apps/meteor/app/message-star/client/views/starredMessages.html b/apps/meteor/app/message-star/client/views/starredMessages.html
deleted file mode 100644
index 1f2e71150085..000000000000
--- a/apps/meteor/app/message-star/client/views/starredMessages.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
- {{#if Template.subscriptionsReady}}
- {{#unless hasMessages}}
-
- {{/unless}}
- {{/if}}
-
-
- {{# with messageContext}}
- {{#each msg in messages}}
- {{>message msg=msg context="starred" room=room groupable=false subscription=subscription settings=settings u=u}}
- {{/each}}
- {{/with}}
-
- {{#if hasMore}}
-
- {{> loading}}
-
- {{/if}}
-
-
diff --git a/apps/meteor/app/message-star/client/views/starredMessages.js b/apps/meteor/app/message-star/client/views/starredMessages.js
deleted file mode 100644
index 4c486444036f..000000000000
--- a/apps/meteor/app/message-star/client/views/starredMessages.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import _ from 'underscore';
-import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
-import { Template } from 'meteor/templating';
-import { Mongo } from 'meteor/mongo';
-
-import { createMessageContext } from '../../../ui-utils/client/lib/messageContext';
-import { Messages } from '../../../models/client';
-import { upsertMessageBulk } from '../../../ui-utils/client/lib/RoomHistoryManager';
-import { APIClient } from '../../../utils/client';
-import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
-
-const LIMIT_DEFAULT = 50;
-
-Template.starredMessages.helpers({
- hasMessages() {
- return Template.instance().messages.find().count();
- },
- messages() {
- const instance = Template.instance();
- return instance.messages.find({}, { limit: instance.limit.get(), sort: { ts: -1 } });
- },
- hasMore() {
- return Template.instance().hasMore.get();
- },
- messageContext: createMessageContext,
-});
-
-Template.starredMessages.onCreated(function () {
- this.rid = this.data.rid;
- this.messages = new Mongo.Collection(null);
- this.hasMore = new ReactiveVar(true);
- this.limit = new ReactiveVar(LIMIT_DEFAULT);
-
- this.autorun(() => {
- const query = {
- '_hidden': { $ne: true },
- 'starred._id': Meteor.userId(),
- 'rid': this.rid,
- '_updatedAt': {
- $gt: new Date(),
- },
- };
-
- this.cursor && this.cursor.stop();
-
- this.limit.set(LIMIT_DEFAULT);
-
- this.cursor = Messages.find(query).observe({
- added: ({ _id, ...message }) => {
- this.messages.upsert({ _id }, message);
- },
- changed: ({ _id, ...message }) => {
- this.messages.upsert({ _id }, message);
- },
- removed: ({ _id }) => {
- this.messages.remove({ _id });
- },
- });
- });
-
- this.autorun(async () => {
- const limit = this.limit.get();
- const { messages, total } = await APIClient.get('/v1/chat.getStarredMessages', {
- roomId: this.rid,
- count: limit,
- });
-
- upsertMessageBulk({ msgs: messages }, this.messages);
-
- this.hasMore.set(total > limit);
- });
-});
-
-Template.mentionsFlexTab.onDestroyed(function () {
- this.cursor.stop();
-});
-
-Template.starredMessages.events({
- ...getCommonRoomEvents(),
- 'scroll .js-list': _.throttle(function (e, instance) {
- if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight) {
- return instance.limit.set(instance.limit.get() + 50);
- }
- }, 200),
-});
diff --git a/apps/meteor/app/message-star/client/views/stylesheets/messagestar.css b/apps/meteor/app/message-star/client/views/stylesheets/messagestar.css
deleted file mode 100644
index 12ac25a3efb3..000000000000
--- a/apps/meteor/app/message-star/client/views/stylesheets/messagestar.css
+++ /dev/null
@@ -1,21 +0,0 @@
-.starred-messages-list {
- & li.empty {
- margin-top: 60px;
-
- text-align: center;
-
- color: #7f7f7f;
- }
-
- & .load-more {
- text-align: center;
- text-transform: lowercase;
-
- font-style: italic;
- line-height: 40px;
-
- & .load-more-loading {
- color: #aaaaaa;
- }
- }
-}
diff --git a/apps/meteor/app/models/client/models/ChatMessage.ts b/apps/meteor/app/models/client/models/ChatMessage.ts
index a3d0356ef8dd..fbe1d3bd376d 100644
--- a/apps/meteor/app/models/client/models/ChatMessage.ts
+++ b/apps/meteor/app/models/client/models/ChatMessage.ts
@@ -1,7 +1,12 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import { Mongo } from 'meteor/mongo';
-class ChatMessageCollection extends Mongo.Collection {
+import type { MinimongoCollection } from '../../../../client/definitions/MinimongoCollection';
+
+class ChatMessageCollection
+ extends Mongo.Collection
+ implements MinimongoCollection
+{
constructor() {
super(null);
}
@@ -14,14 +19,13 @@ class ChatMessageCollection extends Mongo.Collection & { ignored?: boolean },
- IMessage & { ignored?: boolean }
-> & {
- direct: Mongo.Collection, IMessage>;
+ public declare _collection: MinimongoCollection['_collection'];
+
+ public declare direct: MinimongoCollection['direct'];
+
+ public declare queries: MinimongoCollection['queries'];
+}
- queries: unknown[];
-};
+/** @deprecated */
+export const ChatMessage = new ChatMessageCollection();
diff --git a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts
index 84100252b47c..b563e37757a8 100644
--- a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts
+++ b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts
@@ -11,18 +11,7 @@ import { call } from '../../../../client/lib/utils/call';
import { CachedCollectionManager } from './CachedCollectionManager';
import { withDebouncing } from '../../../../lib/utils/highOrderFunctions';
import { isTruthy } from '../../../../lib/isTruthy';
-
-type Collection = Mongo.Collection & {
- _collection: Mongo.Collection & {
- queries: Record;
- _docs: {
- _idStringify: (id: string) => string;
- _map: Map;
- };
- _recomputeResults: (query: unknown) => void;
- };
- direct: Mongo.Collection;
-};
+import type { MinimongoCollection } from '../../../../client/definitions/MinimongoCollection';
type EventType = Extract;
type Name = 'rooms' | 'subscriptions' | 'permissions' | 'public-settings' | 'private-settings';
@@ -47,7 +36,7 @@ const hasUnserializedUpdatedAt = (record: T): record is T & { _updatedAt: Con
export class CachedCollection extends Emitter<{ changed: T; removed: T }> {
private static MAX_CACHE_TIME = 60 * 60 * 24 * 30;
- public collection: Collection;
+ public collection: MinimongoCollection;
public ready = new ReactiveVar(false);
@@ -68,7 +57,7 @@ export class CachedCollection extends Emitter<{ changed: T; re
constructor({ name, eventType = 'onUser', userRelated = true }: { name: Name; eventType?: EventType; userRelated?: boolean }) {
super();
- this.collection = new Mongo.Collection(null) as Collection;
+ this.collection = new Mongo.Collection(null) as MinimongoCollection;
this.name = name;
this.eventType = eventType;
diff --git a/apps/meteor/app/ui-login/username/username.ts b/apps/meteor/app/ui-login/username/username.ts
index 6180c2cbbdbc..157a208dca96 100644
--- a/apps/meteor/app/ui-login/username/username.ts
+++ b/apps/meteor/app/ui-login/username/username.ts
@@ -1,7 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
-import type { Blaze } from 'meteor/blaze';
import { escapeHTML } from '@rocket.chat/string-helpers';
import { settings } from '../../settings/client';
@@ -11,28 +10,7 @@ import { callbacks } from '../../../lib/callbacks';
import { dispatchToastMessage } from '../../../client/lib/toast';
import './username.html';
-type UsernameTemplateInstance = Blaze.TemplateInstance> & {
- customFields: ReactiveVar | null>;
- username: ReactiveVar<{
- ready: boolean;
- username: string;
- empty?: boolean;
- error?: boolean;
- invalid?: boolean;
- escaped?: string;
- blocked?: boolean;
- unavailable?: boolean;
- }>;
- validate: () => unknown;
-};
-Template.username.onCreated(function (this: UsernameTemplateInstance) {
+Template.username.onCreated(function () {
this.customFields = new ReactiveVar(null);
this.username = new ReactiveVar({
ready: false,
@@ -118,7 +96,7 @@ Template.username.onCreated(function (this: UsernameTemplateInstance) {
Template.username.helpers({
username() {
- return (Template.instance() as UsernameTemplateInstance).username.get();
+ return Template.instance<'username'>().username.get();
},
backgroundUrl() {
@@ -143,7 +121,7 @@ Template.username.events({
'reset #login-card'() {
Meteor.logout();
},
- 'submit #login-card'(event: JQuery.SubmitEvent, instance: UsernameTemplateInstance) {
+ 'submit #login-card'(event: JQuery.SubmitEvent, instance) {
event.preventDefault();
const formData = instance.validate();
diff --git a/apps/meteor/app/ui-message/client/messageBox/messageBox.ts b/apps/meteor/app/ui-message/client/messageBox/messageBox.ts
index d7f3f4d543c2..6b22ca91a94d 100644
--- a/apps/meteor/app/ui-message/client/messageBox/messageBox.ts
+++ b/apps/meteor/app/ui-message/client/messageBox/messageBox.ts
@@ -1,53 +1,5 @@
-import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings';
-import type { Blaze } from 'meteor/blaze';
-import type { ContextType } from 'react';
-
-import type { ChatContext } from '../../../../client/views/room/contexts/ChatContext';
import './messageBoxActions';
-export type MessageBoxTemplateInstance = Blaze.TemplateInstance<{
- rid: IRoom['_id'];
- tmid?: IMessage['_id'];
- readOnly: boolean;
- onSend?: (params: { value: string; tshow?: boolean }) => Promise;
- onJoin?: () => Promise;
- onResize?: () => void;
- onTyping?: () => void;
- onEscape?: () => void;
- onNavigateToPreviousMessage?: () => void;
- onNavigateToNextMessage?: () => void;
- onUploadFiles?: (files: readonly File[]) => void;
- tshow?: IMessage['tshow'];
- subscription?: ISubscription;
- showFormattingTips: boolean;
- isEmbedded?: boolean;
- chatContext: ContextType;
-}> & {
- state: ReactiveDict<{
- mustJoinWithCode?: boolean;
- isBlockedOrBlocker?: boolean;
- room?: boolean;
- }>;
- popupConfig: ReactiveVar<{
- rid: string;
- tmid?: string;
- getInput: () => HTMLTextAreaElement;
- } | null>;
- replyMessageData: ReactiveVar;
- isMicrophoneDenied: ReactiveVar;
- isSendIconVisible: ReactiveVar;
- input: HTMLTextAreaElement;
- source?: HTMLTextAreaElement;
- autogrow: {
- update: () => void;
- destroy: () => void;
- } | null;
- set: (value: string) => void;
- insertNewLine: () => void;
- send: (event: Event) => void;
- sendIconDisabled: ReactiveVar;
-};
-
const lastFocusedInput: HTMLTextAreaElement | undefined = undefined;
export const refocusComposer = () => {
diff --git a/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts b/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
index db0bdffb6dfb..960d886e5fa2 100644
--- a/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
+++ b/apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
@@ -18,6 +18,7 @@ import {
clearHighlightMessage,
} from '../../../../client/views/room/MessageList/providers/messageHighlightSubscription';
import { normalizeThreadMessage } from '../../../../client/lib/normalizeThreadMessage';
+import type { MinimongoCollection } from '../../../../client/definitions/MinimongoCollection';
export async function upsertMessage(
{
@@ -29,7 +30,7 @@ export async function upsertMessage(
subscription?: ISubscription;
uid?: IUser['_id'];
},
- { direct } = ChatMessage,
+ { direct }: MinimongoCollection = ChatMessage,
) {
const userId = msg.u?._id;
@@ -58,10 +59,13 @@ export async function upsertMessage(
);
}
- return direct.upsert({ _id }, messageToUpsert);
+ return direct.upsert({ _id }, { $set: messageToUpsert });
}
-export function upsertMessageBulk({ msgs, subscription }: { msgs: IMessage[]; subscription?: ISubscription }, collection = ChatMessage) {
+export function upsertMessageBulk(
+ { msgs, subscription }: { msgs: IMessage[]; subscription?: ISubscription },
+ collection: MinimongoCollection = ChatMessage,
+) {
const uid = Tracker.nonreactive(() => Meteor.userId()) ?? undefined;
const { queries } = collection;
collection.queries = [];
diff --git a/apps/meteor/app/ui-utils/lib/MessageTypes.ts b/apps/meteor/app/ui-utils/lib/MessageTypes.ts
index 514b36798c25..a4f77d10cbf7 100644
--- a/apps/meteor/app/ui-utils/lib/MessageTypes.ts
+++ b/apps/meteor/app/ui-utils/lib/MessageTypes.ts
@@ -11,7 +11,8 @@ export type MessageType = {
message: TranslationKey;
data?: (message: IMessage) => Record;
};
-class MessageTypesClass {
+
+class MessageTypes {
private types = new Map();
registerType(options: MessageType): MessageType {
@@ -34,4 +35,7 @@ class MessageTypesClass {
return Boolean(type?.system);
}
}
-export const MessageTypes = new MessageTypesClass();
+
+const instance = new MessageTypes();
+
+export { instance as MessageTypes };
diff --git a/apps/meteor/app/ui/client/views/app/roomSearch.ts b/apps/meteor/app/ui/client/views/app/roomSearch.ts
index 88d13d4e7f83..f9fd91e0d73b 100644
--- a/apps/meteor/app/ui/client/views/app/roomSearch.ts
+++ b/apps/meteor/app/ui/client/views/app/roomSearch.ts
@@ -3,7 +3,7 @@ import { Template } from 'meteor/templating';
import { roomCoordinator } from '../../../../../client/lib/rooms/roomCoordinator';
Template.roomSearch.helpers({
- roomIcon() {
+ roomIcon(this: any) {
if (this.type === 'u') {
return 'icon-at';
}
@@ -11,7 +11,7 @@ Template.roomSearch.helpers({
return roomCoordinator.getIcon(this);
}
},
- userStatus() {
+ userStatus(this: any) {
if (this.type === 'u') {
return `status-${this.status}`;
}
diff --git a/apps/meteor/client/components/message/ToolboxHolder.tsx b/apps/meteor/client/components/message/ToolboxHolder.tsx
index 97e3f1acb4aa..295fe798fc3b 100644
--- a/apps/meteor/client/components/message/ToolboxHolder.tsx
+++ b/apps/meteor/client/components/message/ToolboxHolder.tsx
@@ -1,14 +1,15 @@
-import type { IMessage, ToolboxMessageType } from '@rocket.chat/core-typings';
+import type { IMessage } from '@rocket.chat/core-typings';
import { MessageToolboxWrapper } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import React, { memo, useRef } from 'react';
+import type { MessageActionContext } from '../../../app/ui-utils/client/lib/MessageAction';
import { useIsVisible } from '../../views/room/hooks/useIsVisible';
import Toolbox from './toolbox/Toolbox';
type ToolboxHolderProps = {
message: IMessage;
- context?: ToolboxMessageType;
+ context?: MessageActionContext;
};
export const ToolboxHolder = ({ message, context }: ToolboxHolderProps): ReactElement => {
diff --git a/apps/meteor/client/components/message/hooks/useMessageNormalization.ts b/apps/meteor/client/components/message/hooks/useMessageNormalization.ts
new file mode 100644
index 000000000000..a4ed5d6c52b3
--- /dev/null
+++ b/apps/meteor/client/components/message/hooks/useMessageNormalization.ts
@@ -0,0 +1,33 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { useSetting } from '@rocket.chat/ui-contexts';
+import { useMemo } from 'react';
+
+import type { MessageWithMdEnforced } from '../../../lib/parseMessageTextToAstMarkdown';
+import { parseMessageTextToAstMarkdown, removePossibleNullMessageValues } from '../../../lib/parseMessageTextToAstMarkdown';
+import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate';
+import { useKatex } from '../../../views/room/MessageList/hooks/useKatex';
+import { useRoomSubscription } from '../../../views/room/contexts/RoomContext';
+
+export const useMessageNormalization = (): ((message: TMessage) => MessageWithMdEnforced) => {
+ const { katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled } = useKatex();
+
+ const subscription = useRoomSubscription();
+ const autoTranslateOptions = useAutoTranslate(subscription);
+ const showColors = useSetting('HexColorPreview_Enabled');
+
+ return useMemo(() => {
+ const parseOptions = {
+ colors: showColors,
+ emoticons: true,
+ ...(katexEnabled && {
+ katex: {
+ dollarSyntax: katexDollarSyntaxEnabled,
+ parenthesisSyntax: katexParenthesisSyntaxEnabled,
+ },
+ }),
+ };
+
+ return (message: TTMessage): MessageWithMdEnforced =>
+ parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateOptions);
+ }, [showColors, katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled, autoTranslateOptions]);
+};
diff --git a/apps/meteor/client/components/message/toolbox/DesktopToolboxDropdown.tsx b/apps/meteor/client/components/message/toolbox/DesktopToolboxDropdown.tsx
index 3e8392fc6c79..3d19f352bb28 100644
--- a/apps/meteor/client/components/message/toolbox/DesktopToolboxDropdown.tsx
+++ b/apps/meteor/client/components/message/toolbox/DesktopToolboxDropdown.tsx
@@ -1,29 +1,54 @@
import { Tile } from '@rocket.chat/fuselage';
import { useMergedRefs, usePosition } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, Ref, RefObject } from 'react';
-import React, { useRef, forwardRef } from 'react';
+import React, { useMemo, useRef, forwardRef } from 'react';
+
+const getDropdownContainer = (descendant: HTMLElement | null) => {
+ for (let element = descendant ?? document.body; element !== document.body; element = element.parentElement ?? document.body) {
+ if (
+ getComputedStyle(element).transform !== 'none' ||
+ getComputedStyle(element).position === 'fixed' ||
+ getComputedStyle(element).willChange === 'transform'
+ ) {
+ return element;
+ }
+ }
+
+ return document.body;
+};
+
+const useDropdownPosition = (reference: RefObject, target: RefObject) => {
+ const innerContainer = getDropdownContainer(reference.current);
+ const boundingRect = innerContainer.getBoundingClientRect();
+
+ const { style } = usePosition(reference, target, {
+ watch: true,
+ placement: 'bottom-end',
+ container: innerContainer,
+ });
+
+ const left = `${parseFloat(style.left) - boundingRect.left}px`;
+ const top = `${parseFloat(style.top) - boundingRect.top}px`;
+
+ return useMemo(() => ({ ...style, left, top }), [style, left, top]);
+};
type DesktopToolboxDropdownProps = {
children: ReactNode;
reference: RefObject;
- container: Element;
};
const DesktopToolboxDropdown = forwardRef(function ToolboxDropdownDesktop(
- { reference, container, children, ...props }: DesktopToolboxDropdownProps,
+ { reference, children }: DesktopToolboxDropdownProps,
ref: Ref,
) {
const targetRef = useRef(null);
const mergedRef = useMergedRefs(ref, targetRef);
- const { style } = usePosition(reference, targetRef, {
- watch: true,
- placement: 'bottom-end',
- container,
- });
+ const style = useDropdownPosition(reference, targetRef);
return (
-
+
{children}
);
diff --git a/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx b/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx
index 3772ea5351a7..f438e91d3d8b 100644
--- a/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx
+++ b/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx
@@ -34,8 +34,6 @@ export const MessageActionMenu = ({ options, ...props }: MessageActionMenuProps)
[key: string]: MessageActionConfigOption[];
};
- const messagesContainer = document.querySelector('.messages-container') || document.body;
-
return (
{visible && (
-
+
{Object.entries(groupOptions).map(([, options], index, arr) => (
{options.map((option) => (
diff --git a/apps/meteor/client/components/message/toolbox/Toolbox.tsx b/apps/meteor/client/components/message/toolbox/Toolbox.tsx
index 59910fbc2f9c..c4787818c225 100644
--- a/apps/meteor/client/components/message/toolbox/Toolbox.tsx
+++ b/apps/meteor/client/components/message/toolbox/Toolbox.tsx
@@ -1,4 +1,4 @@
-import type { IMessage, IRoom, ITranslatedMessage, ToolboxMessageType } from '@rocket.chat/core-typings';
+import type { IMessage, IRoom, ITranslatedMessage } from '@rocket.chat/core-typings';
import { isThreadMessage, isRoomFederated } from '@rocket.chat/core-typings';
import { MessageToolbox, MessageToolboxItem } from '@rocket.chat/fuselage';
import { useUser, useSettings, useTranslation } from '@rocket.chat/ui-contexts';
@@ -15,22 +15,29 @@ import { useRoom, useRoomSubscription } from '../../../views/room/contexts/RoomC
import { useToolboxContext } from '../../../views/room/contexts/ToolboxContext';
import MessageActionMenu from './MessageActionMenu';
-const getMessageContext = (message: IMessage, room: IRoom, context?: ToolboxMessageType): MessageActionContext => {
+const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => {
+ if (context) {
+ return context;
+ }
+
if (message.t === 'videoconf') {
return 'videoconf';
}
+
if (isRoomFederated(room)) {
return 'federated';
}
- if (isThreadMessage(message) || context === 'thread') {
+
+ if (isThreadMessage(message)) {
return 'threads';
}
+
return 'message';
};
type ToolboxProps = {
message: IMessage & Partial;
- messageContext?: ToolboxMessageType;
+ messageContext?: MessageActionContext;
};
const Toolbox = ({ message, messageContext }: ToolboxProps): ReactElement | null => {
diff --git a/apps/meteor/client/components/message/toolbox/ToolboxDropdown.tsx b/apps/meteor/client/components/message/toolbox/ToolboxDropdown.tsx
index 0e532be592fa..5be1aadf27e1 100644
--- a/apps/meteor/client/components/message/toolbox/ToolboxDropdown.tsx
+++ b/apps/meteor/client/components/message/toolbox/ToolboxDropdown.tsx
@@ -1,4 +1,3 @@
-import { css } from '@rocket.chat/css-in-js';
import { Box } from '@rocket.chat/fuselage';
import { useLayout } from '@rocket.chat/ui-contexts';
import type { ReactNode, ReactElement } from 'react';
@@ -7,20 +6,15 @@ import React, { useRef } from 'react';
import DesktopToolboxDropdown from './DesktopToolboxDropdown';
import MobileToolboxDropdown from './MobileToolboxDropdown';
-const style = css`
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
-`;
-
type ToolboxDropdownProps = {
children: ReactNode;
reference: React.RefObject;
- container: Element;
};
-const ToolboxDropdown = ({ children, reference, container, ...props }: ToolboxDropdownProps): ReactElement => {
+const ToolboxDropdown = ({
+ children,
+ reference,
+}: ToolboxDropdownProps): ReactElement => {
const { isMobile } = useLayout();
const target = useRef(null);
@@ -28,8 +22,8 @@ const ToolboxDropdown = ({ children, reference, container
return (
<>
-
-
+
+
{children}
>
diff --git a/apps/meteor/client/components/message/variants/RoomMessage.tsx b/apps/meteor/client/components/message/variants/RoomMessage.tsx
index 1f7868d9c6ad..783479f7a1a4 100644
--- a/apps/meteor/client/components/message/variants/RoomMessage.tsx
+++ b/apps/meteor/client/components/message/variants/RoomMessage.tsx
@@ -1,11 +1,12 @@
+import type { IMessage } from '@rocket.chat/core-typings';
import { Message, MessageLeftContainer, MessageContainer, CheckBox } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
import { useUserId } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { memo } from 'react';
+import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction';
import { useUserCard } from '../../../hooks/useUserCard';
-import type { MessageWithMdEnforced } from '../../../lib/parseMessageTextToAstMarkdown';
import { useIsMessageHighlight } from '../../../views/room/MessageList/contexts/MessageHighlightContext';
import {
useIsSelecting,
@@ -21,14 +22,15 @@ import ToolboxHolder from '../ToolboxHolder';
import RoomMessageContent from './room/RoomMessageContent';
type RoomMessageProps = {
- message: MessageWithMdEnforced;
+ message: IMessage;
sequential: boolean;
unread: boolean;
mention: boolean;
all: boolean;
+ context?: MessageActionContext;
};
-const RoomMessage = ({ message, sequential, all, mention, unread }: RoomMessageProps): ReactElement => {
+const RoomMessage = ({ message, sequential, all, mention, unread, context }: RoomMessageProps): ReactElement => {
const uid = useUserId();
const editing = useIsMessageHighlight(message._id);
const [ignored, toggleIgnoring] = useToggle((message as { ignored?: boolean }).ignored ?? false);
@@ -79,7 +81,7 @@ const RoomMessage = ({ message, sequential, all, mention, unread }: RoomMessageP
)}
- {!message.private && }
+ {!message.private && }
);
};
diff --git a/apps/meteor/client/components/message/variants/ThreadMessage.tsx b/apps/meteor/client/components/message/variants/ThreadMessage.tsx
index 6b79aa63b079..4f4dfdfec8b4 100644
--- a/apps/meteor/client/components/message/variants/ThreadMessage.tsx
+++ b/apps/meteor/client/components/message/variants/ThreadMessage.tsx
@@ -1,20 +1,17 @@
import type { IThreadMessage, IThreadMainMessage } from '@rocket.chat/core-typings';
import { Message, MessageLeftContainer, MessageContainer } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
-import { useUserId, useUserSubscription } from '@rocket.chat/ui-contexts';
+import { useUserId } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
-import React, { useMemo, memo } from 'react';
+import React, { memo } from 'react';
import { useUserCard } from '../../../hooks/useUserCard';
-import { parseMessageTextToAstMarkdown, removePossibleNullMessageValues } from '../../../lib/parseMessageTextToAstMarkdown';
import { useIsMessageHighlight } from '../../../views/room/MessageList/contexts/MessageHighlightContext';
-import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate';
import UserAvatar from '../../avatar/UserAvatar';
import IgnoredContent from '../IgnoredContent';
import MessageHeader from '../MessageHeader';
import StatusIndicators from '../StatusIndicators';
import ToolboxHolder from '../ToolboxHolder';
-import { useMessageListContext } from '../list/MessageListContext';
import ThreadMessageContent from './thread/ThreadMessageContent';
type ThreadMessageProps = {
@@ -29,27 +26,6 @@ const ThreadMessage = ({ message, sequential, unread }: ThreadMessageProps): Rea
const [ignored, toggleIgnoring] = useToggle((message as { ignored?: boolean }).ignored);
const { open: openUserCard } = useUserCard();
- const { katex, showColors } = useMessageListContext();
- const subscription = useUserSubscription(message.rid);
- const autoTranslateOptions = useAutoTranslate(subscription);
-
- const normalizeMessage = useMemo(() => {
- const parseOptions = {
- colors: showColors,
- emoticons: true,
- ...(Boolean(katex) && {
- katex: {
- dollarSyntax: katex?.dollarSyntaxEnabled,
- parenthesisSyntax: katex?.parenthesisSyntaxEnabled,
- },
- }),
- };
- return (message: TMessage) =>
- parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateOptions);
- }, [katex, showColors, autoTranslateOptions]);
-
- const normalizedMessage = useMemo(() => normalizeMessage(message), [message, normalizeMessage]);
-
return (
{!sequential && }
- {ignored ? : }
+ {ignored ? : }
- {!message.private && }
+ {!message.private && }
);
};
diff --git a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx
index c8b05a5ca06b..15de13bc5ca0 100644
--- a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx
+++ b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx
@@ -3,10 +3,9 @@ import { isDiscussionMessage, isThreadMainMessage, isE2EEMessage } from '@rocket
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useSetting, useTranslation, useUserId } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
-import React, { memo } from 'react';
+import React, { useMemo, memo } from 'react';
import { useUserData } from '../../../../hooks/useUserData';
-import type { MessageWithMdEnforced } from '../../../../lib/parseMessageTextToAstMarkdown';
import type { UserPresence } from '../../../../lib/presence';
import { useRoomSubscription } from '../../../../views/room/contexts/RoomContext';
import MessageContentBody from '../../MessageContentBody';
@@ -20,10 +19,11 @@ import Reactions from '../../content/Reactions';
import ThreadMetrics from '../../content/ThreadMetrics';
import UiKitSurface from '../../content/UiKitSurface';
import UrlPreviews from '../../content/UrlPreviews';
+import { useMessageNormalization } from '../../hooks/useMessageNormalization';
import { useOembedLayout } from '../../hooks/useOembedLayout';
type RoomMessageContentProps = {
- message: MessageWithMdEnforced;
+ message: IMessage;
unread: boolean;
mention: boolean;
all: boolean;
@@ -39,27 +39,34 @@ const RoomMessageContent = ({ message, unread, all, mention }: RoomMessageConten
const t = useTranslation();
+ const normalizeMessage = useMessageNormalization();
+ const normalizedMessage = useMemo(() => normalizeMessage(message), [message, normalizeMessage]);
+
return (
<>
- {!message.blocks?.length && !!message.md?.length && (
+ {!normalizedMessage.blocks?.length && !!normalizedMessage.md?.length && (
<>
- {(!encrypted || message.e2e === 'done') && (
-
+ {(!encrypted || normalizedMessage.e2e === 'done') && (
+
)}
- {encrypted && message.e2e === 'pending' && t('E2E_message_encrypted_placeholder')}
+ {encrypted && normalizedMessage.e2e === 'pending' && t('E2E_message_encrypted_placeholder')}
>
)}
- {message.blocks && }
+ {normalizedMessage.blocks && (
+
+ )}
- {!!message?.attachments?.length && }
+ {!!normalizedMessage?.attachments?.length && (
+
+ )}
- {oembedEnabled && !!message.urls?.length && }
+ {oembedEnabled && !!normalizedMessage.urls?.length && }
- {message.actionLinks?.length && (
+ {normalizedMessage.actionLinks?.length && (
({
+ message={normalizedMessage}
+ actions={normalizedMessage.actionLinks.map(({ method_id: methodId, i18nLabel, ...action }) => ({
methodId,
i18nLabel: i18nLabel as TranslationKey,
...action,
@@ -67,31 +74,38 @@ const RoomMessageContent = ({ message, unread, all, mention }: RoomMessageConten
/>
)}
- {message.reactions && Object.keys(message.reactions).length && }
+ {normalizedMessage.reactions && Object.keys(normalizedMessage.reactions).length && }
- {isThreadMainMessage(message) && (
+ {isThreadMainMessage(normalizedMessage) && (
-1)}
- mid={message._id}
- rid={message.rid}
- lm={message.tlm}
+ counter={normalizedMessage.tcount}
+ following={Boolean(uid && normalizedMessage?.replies?.indexOf(uid) > -1)}
+ mid={normalizedMessage._id}
+ rid={normalizedMessage.rid}
+ lm={normalizedMessage.tlm}
unread={unread}
mention={mention}
all={all}
- participants={message?.replies?.length}
+ participants={normalizedMessage?.replies?.length}
/>
)}
- {isDiscussionMessage(message) && }
+ {isDiscussionMessage(normalizedMessage) && (
+
+ )}
- {message.location && }
+ {normalizedMessage.location && }
- {broadcast && !!messageUser.username && message.u._id !== uid && (
-
+ {broadcast && !!messageUser.username && normalizedMessage.u._id !== uid && (
+
)}
- {readReceiptEnabled && }
+ {readReceiptEnabled && }
>
);
};
diff --git a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx
index d484c8cebe87..15a2e883be6f 100644
--- a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx
+++ b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx
@@ -3,10 +3,9 @@ import { isE2EEMessage } from '@rocket.chat/core-typings';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useSetting, useUserId, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
-import React, { memo } from 'react';
+import React, { useMemo, memo } from 'react';
import { useUserData } from '../../../../hooks/useUserData';
-import type { MessageWithMdEnforced } from '../../../../lib/parseMessageTextToAstMarkdown';
import type { UserPresence } from '../../../../lib/presence';
import { useRoomSubscription } from '../../../../views/room/contexts/RoomContext';
import MessageContentBody from '../../MessageContentBody';
@@ -18,10 +17,11 @@ import MessageActions from '../../content/MessageActions';
import Reactions from '../../content/Reactions';
import UiKitSurface from '../../content/UiKitSurface';
import UrlPreviews from '../../content/UrlPreviews';
+import { useMessageNormalization } from '../../hooks/useMessageNormalization';
import { useOembedLayout } from '../../hooks/useOembedLayout';
type ThreadMessageContentProps = {
- message: MessageWithMdEnforced;
+ message: IThreadMessage | IThreadMainMessage;
};
const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElement => {
@@ -34,27 +34,32 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem
const t = useTranslation();
+ const normalizeMessage = useMessageNormalization();
+ const normalizedMessage = useMemo(() => normalizeMessage(message), [message, normalizeMessage]);
+
return (
<>
- {!message.blocks?.length && !!message.md?.length && (
+ {!normalizedMessage.blocks?.length && !!normalizedMessage.md?.length && (
<>
- {(!encrypted || message.e2e === 'done') && (
-
+ {(!encrypted || normalizedMessage.e2e === 'done') && (
+
)}
- {encrypted && message.e2e === 'pending' && t('E2E_message_encrypted_placeholder')}
+ {encrypted && normalizedMessage.e2e === 'pending' && t('E2E_message_encrypted_placeholder')}
>
)}
- {message.blocks && }
+ {normalizedMessage.blocks && (
+
+ )}
- {message.attachments && }
+ {normalizedMessage.attachments && }
- {oembedEnabled && !!message.urls?.length && }
+ {oembedEnabled && !!normalizedMessage.urls?.length && }
- {message.actionLinks?.length && (
+ {normalizedMessage.actionLinks?.length && (
({
+ message={normalizedMessage}
+ actions={normalizedMessage.actionLinks.map(({ method_id: methodId, i18nLabel, ...action }) => ({
methodId,
i18nLabel: i18nLabel as TranslationKey,
...action,
@@ -62,15 +67,15 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem
/>
)}
- {message.reactions && Object.keys(message.reactions).length && }
+ {normalizedMessage.reactions && Object.keys(normalizedMessage.reactions).length && }
- {message.location && }
+ {normalizedMessage.location && }
- {broadcast && !!messageUser.username && message.u._id !== uid && (
-
+ {broadcast && !!messageUser.username && normalizedMessage.u._id !== uid && (
+
)}
- {readReceiptEnabled && }
+ {readReceiptEnabled && }
>
);
};
diff --git a/apps/meteor/client/definitions/MinimongoCollection.ts b/apps/meteor/client/definitions/MinimongoCollection.ts
new file mode 100644
index 000000000000..1c3b846dff59
--- /dev/null
+++ b/apps/meteor/client/definitions/MinimongoCollection.ts
@@ -0,0 +1,14 @@
+import type { Mongo } from 'meteor/mongo';
+
+export type MinimongoCollection = Mongo.Collection & {
+ _collection: Mongo.Collection & {
+ queries: Record;
+ _docs: {
+ _idStringify: (id: string) => string;
+ _map: Map;
+ };
+ _recomputeResults: (query: unknown) => void;
+ };
+ direct: Mongo.Collection;
+ queries: unknown[];
+};
diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts
index d0d15739dcfe..3dc430e1eda6 100644
--- a/apps/meteor/client/importPackages.ts
+++ b/apps/meteor/client/importPackages.ts
@@ -27,12 +27,9 @@ import '../app/lib/client';
import '../app/livestream/client';
import '../app/logger/client';
import '../app/markdown/client';
-import '../app/mentions-flextab/client';
import '../app/message-attachments/client';
import '../app/message-mark-as-unread/client';
-import '../app/message-pin/client';
import '../app/message-snippet/client';
-import '../app/message-star/client';
import '../app/nextcloud/client';
import '../app/oauth2-server-config/client';
import '../app/oembed/client';
diff --git a/apps/meteor/client/lib/utils/jumpToMessage.ts b/apps/meteor/client/lib/utils/jumpToMessage.ts
new file mode 100644
index 000000000000..4ade65112439
--- /dev/null
+++ b/apps/meteor/client/lib/utils/jumpToMessage.ts
@@ -0,0 +1,28 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { FlowRouter } from 'meteor/kadira:flow-router';
+
+import { ChatRoom } from '../../../app/models/client';
+import { RoomHistoryManager } from '../../../app/ui-utils/client';
+
+export const jumpToMessage = (message: IMessage) => {
+ if (matchMedia('(max-width: 500px)').matches) {
+ (Template.instance() as any).tabBar.close();
+ }
+
+ if (message.tmid) {
+ return FlowRouter.go(
+ FlowRouter.getRouteName(),
+ {
+ tab: 'thread',
+ context: message.tmid,
+ rid: message.rid,
+ jump: message._id,
+ name: ChatRoom.findOne({ _id: message.rid })?.name ?? '',
+ },
+ {
+ jump: message._id,
+ },
+ );
+ }
+ return RoomHistoryManager.getSurroundingMessages(message);
+};
diff --git a/apps/meteor/client/main.ts b/apps/meteor/client/main.ts
index 80be3c1a9f14..36f0a29eabc3 100644
--- a/apps/meteor/client/main.ts
+++ b/apps/meteor/client/main.ts
@@ -10,11 +10,7 @@ import './importPackages';
import '../ee/client';
import './templateHelpers';
-import './methods/hideRoom';
-import './methods/openRoom';
-import './methods/setUserActiveStatus';
-import './methods/toggleFavorite';
-import './methods/updateMessage';
+import './methods';
import './startup';
import './views/admin';
import './views/account';
diff --git a/apps/meteor/client/methods/index.ts b/apps/meteor/client/methods/index.ts
new file mode 100644
index 000000000000..990a3a20f24c
--- /dev/null
+++ b/apps/meteor/client/methods/index.ts
@@ -0,0 +1,8 @@
+import './hideRoom';
+import './openRoom';
+import './pinMessage';
+import './setUserActiveStatus';
+import './starMessage';
+import './toggleFavorite';
+import './unpinMessage';
+import './updateMessage';
diff --git a/apps/meteor/client/methods/pinMessage.ts b/apps/meteor/client/methods/pinMessage.ts
new file mode 100644
index 000000000000..6b1c50a7b8b7
--- /dev/null
+++ b/apps/meteor/client/methods/pinMessage.ts
@@ -0,0 +1,40 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { Meteor } from 'meteor/meteor';
+
+import { ChatMessage, ChatSubscription } from '../../app/models/client';
+import { settings } from '../../app/settings/client';
+import { t } from '../../app/utils/client';
+import { dispatchToastMessage } from '../lib/toast';
+
+Meteor.methods({
+ pinMessage(message: IMessage) {
+ if (!Meteor.userId()) {
+ dispatchToastMessage({ type: 'error', message: t('error-not-authorized') });
+ return false;
+ }
+ if (!settings.get('Message_AllowPinning')) {
+ dispatchToastMessage({ type: 'error', message: t('pinning-not-allowed') });
+ return false;
+ }
+ if (!ChatSubscription.findOne({ rid: message.rid })) {
+ dispatchToastMessage({ type: 'error', message: t('error-pinning-message') });
+ return false;
+ }
+ if (typeof message._id !== 'string') {
+ dispatchToastMessage({ type: 'error', message: t('error-pinning-message') });
+ return false;
+ }
+ dispatchToastMessage({ type: 'success', message: t('Message_has_been_pinned') });
+ return ChatMessage.update(
+ {
+ _id: message._id,
+ rid: message.rid,
+ },
+ {
+ $set: {
+ pinned: true,
+ },
+ },
+ );
+ },
+});
diff --git a/apps/meteor/client/methods/starMessage.ts b/apps/meteor/client/methods/starMessage.ts
new file mode 100644
index 000000000000..5eeebf7700b4
--- /dev/null
+++ b/apps/meteor/client/methods/starMessage.ts
@@ -0,0 +1,57 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { Meteor } from 'meteor/meteor';
+
+import { ChatMessage, ChatSubscription } from '../../app/models/client';
+import { settings } from '../../app/settings/client';
+import { t } from '../../app/utils/client';
+import { dispatchToastMessage } from '../lib/toast';
+
+Meteor.methods({
+ starMessage(message: Omit & { starred: boolean }) {
+ const uid = Meteor.userId();
+
+ if (!uid) {
+ dispatchToastMessage({ type: 'error', message: t('error-starring-message') });
+ return false;
+ }
+
+ if (!ChatSubscription.findOne({ rid: message.rid })) {
+ dispatchToastMessage({ type: 'error', message: t('error-starring-message') });
+ return false;
+ }
+
+ if (!ChatMessage.findOneByRoomIdAndMessageId(message.rid, message._id)) {
+ dispatchToastMessage({ type: 'error', message: t('error-starring-message') });
+ return false;
+ }
+
+ if (!settings.get('Message_AllowStarring')) {
+ dispatchToastMessage({ type: 'error', message: t('error-starring-message') });
+ return false;
+ }
+
+ if (message.starred) {
+ ChatMessage.update(
+ { _id: message._id },
+ {
+ $addToSet: {
+ starred: { _id: uid },
+ },
+ },
+ );
+
+ dispatchToastMessage({ type: 'success', message: t('Message_has_been_starred') });
+ } else {
+ ChatMessage.update(
+ { _id: message._id },
+ {
+ $pull: {
+ starred: { _id: uid },
+ },
+ },
+ );
+
+ dispatchToastMessage({ type: 'success', message: t('Message_has_been_unstarred') });
+ }
+ },
+});
diff --git a/apps/meteor/client/methods/unpinMessage.ts b/apps/meteor/client/methods/unpinMessage.ts
new file mode 100644
index 000000000000..44ad40765c74
--- /dev/null
+++ b/apps/meteor/client/methods/unpinMessage.ts
@@ -0,0 +1,40 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { Meteor } from 'meteor/meteor';
+
+import { ChatMessage, ChatSubscription } from '../../app/models/client';
+import { settings } from '../../app/settings/client';
+import { t } from '../../app/utils/client';
+import { dispatchToastMessage } from '../lib/toast';
+
+Meteor.methods({
+ unpinMessage(message: IMessage) {
+ if (!Meteor.userId()) {
+ dispatchToastMessage({ type: 'error', message: t('error-not-authorized') });
+ return false;
+ }
+ if (!settings.get('Message_AllowPinning')) {
+ dispatchToastMessage({ type: 'error', message: t('unpinning-not-allowed') });
+ return false;
+ }
+ if (!ChatSubscription.findOne({ rid: message.rid })) {
+ dispatchToastMessage({ type: 'error', message: t('error-unpinning-message') });
+ return false;
+ }
+ if (typeof message._id !== 'string') {
+ dispatchToastMessage({ type: 'error', message: t('error-unpinning-message') });
+ return false;
+ }
+ dispatchToastMessage({ type: 'success', message: t('Message_has_been_unpinned') });
+ return ChatMessage.update(
+ {
+ _id: message._id,
+ rid: message.rid,
+ },
+ {
+ $set: {
+ pinned: false,
+ },
+ },
+ );
+ },
+});
diff --git a/apps/meteor/client/polyfills/cssVars.ts b/apps/meteor/client/polyfills/cssVars.ts
index a8dd9bdcbbdf..8b0b88c60125 100644
--- a/apps/meteor/client/polyfills/cssVars.ts
+++ b/apps/meteor/client/polyfills/cssVars.ts
@@ -1,4 +1,4 @@
-import _ from 'underscore';
+import { withDebouncing } from '../../lib/utils/highOrderFunctions';
type Variables = {
[name: string]: (variables: Variables) => string;
@@ -27,7 +27,7 @@ const replaceReferences = (code: string, variables: Variables): string =>
let cssVariablesElement: HTMLElement;
const originalCodes = new Map();
-const update = _.debounce(() => {
+const update = withDebouncing({ wait: 100 })(() => {
const declarations = ([] as [string, Variables[keyof Variables]][]).concat(
...Array.from(originalCodes.values(), findDeclarations),
findDeclarations(cssVariablesElement.innerHTML),
@@ -52,7 +52,7 @@ const update = _.debounce(() => {
}
sheet.insertRule(`@media all {${patchedCode}}`, 0);
});
-}, 100);
+});
const findAndPatchFromLinkElements = (): void => {
Array.from(document.querySelectorAll('link[type="text/css"].__meteor-css__')).forEach(async (linkElement) => {
diff --git a/apps/meteor/client/startup/actionButtons/index.ts b/apps/meteor/client/startup/actionButtons/index.ts
new file mode 100644
index 000000000000..b238cb531ecd
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/index.ts
@@ -0,0 +1,10 @@
+import './jumpToMessage';
+import './jumpToPinMessage';
+import './jumpToStarMessage';
+import './permalinkPinned';
+import './permalinkStar';
+import './pinMessage';
+import './readReceipt';
+import './starMessage';
+import './unpinMessage';
+import './unstarMessage';
diff --git a/apps/meteor/client/startup/actionButtons/jumpToMessage.ts b/apps/meteor/client/startup/actionButtons/jumpToMessage.ts
new file mode 100644
index 000000000000..f9446a3424e6
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/jumpToMessage.ts
@@ -0,0 +1,20 @@
+import { Meteor } from 'meteor/meteor';
+
+import { MessageAction } from '../../../app/ui-utils/client';
+import { jumpToMessage } from '../../lib/utils/jumpToMessage';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'jump-to-message',
+ icon: 'jump',
+ label: 'Jump_to_message',
+ context: ['mentions', 'threads'],
+ action(_, props) {
+ const { message = messageArgs(this).msg } = props;
+ jumpToMessage(message);
+ },
+ order: 100,
+ group: ['message', 'menu'],
+ });
+});
diff --git a/apps/meteor/client/startup/actionButtons/jumpToPinMessage.ts b/apps/meteor/client/startup/actionButtons/jumpToPinMessage.ts
new file mode 100644
index 000000000000..72f3bac1ee24
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/jumpToPinMessage.ts
@@ -0,0 +1,23 @@
+import { Meteor } from 'meteor/meteor';
+
+import { MessageAction } from '../../../app/ui-utils/client';
+import { jumpToMessage } from '../../lib/utils/jumpToMessage';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'jump-to-pin-message',
+ icon: 'jump',
+ label: 'Jump_to_message',
+ context: ['pinned', 'message-mobile', 'direct'],
+ action(_, props) {
+ const { message = messageArgs(this).msg } = props;
+ jumpToMessage(message);
+ },
+ condition({ subscription }) {
+ return !!subscription;
+ },
+ order: 100,
+ group: ['message', 'menu'],
+ });
+});
diff --git a/apps/meteor/client/startup/actionButtons/jumpToStarMessage.ts b/apps/meteor/client/startup/actionButtons/jumpToStarMessage.ts
new file mode 100644
index 000000000000..b1d644e0e41d
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/jumpToStarMessage.ts
@@ -0,0 +1,28 @@
+import { Meteor } from 'meteor/meteor';
+
+import { settings } from '../../../app/settings/client';
+import { MessageAction } from '../../../app/ui-utils/client';
+import { jumpToMessage } from '../../lib/utils/jumpToMessage';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'jump-to-star-message',
+ icon: 'jump',
+ label: 'Jump_to_message',
+ context: ['starred', 'threads', 'message-mobile'],
+ action(_, props) {
+ const { message = messageArgs(this).msg } = props;
+ jumpToMessage(message);
+ },
+ condition({ message, subscription, user }) {
+ if (subscription == null || !settings.get('Message_AllowStarring')) {
+ return false;
+ }
+
+ return Boolean(message.starred?.find((star) => star._id === user?._id));
+ },
+ order: 100,
+ group: ['message', 'menu'],
+ });
+});
diff --git a/apps/meteor/client/startup/actionButtons/permalinkPinned.ts b/apps/meteor/client/startup/actionButtons/permalinkPinned.ts
new file mode 100644
index 000000000000..f67e884f6462
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/permalinkPinned.ts
@@ -0,0 +1,30 @@
+import { Meteor } from 'meteor/meteor';
+
+import { MessageAction } from '../../../app/ui-utils/client';
+import { t } from '../../../app/utils/client';
+import { dispatchToastMessage } from '../../lib/toast';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'permalink-pinned',
+ icon: 'permalink',
+ label: 'Get_link',
+ context: ['pinned'],
+ async action(_, props) {
+ try {
+ const { message = messageArgs(this).msg } = props;
+ const permalink = await MessageAction.getPermaLink(message._id);
+ navigator.clipboard.writeText(permalink);
+ dispatchToastMessage({ type: 'success', message: t('Copied') });
+ } catch (e) {
+ dispatchToastMessage({ type: 'error', message: e });
+ }
+ },
+ condition({ subscription }) {
+ return !!subscription;
+ },
+ order: 101,
+ group: 'menu',
+ });
+});
diff --git a/apps/meteor/client/startup/actionButtons/permalinkStar.ts b/apps/meteor/client/startup/actionButtons/permalinkStar.ts
new file mode 100644
index 000000000000..0acbce7661f4
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/permalinkStar.ts
@@ -0,0 +1,35 @@
+import { Meteor } from 'meteor/meteor';
+
+import { MessageAction } from '../../../app/ui-utils/client';
+import { t } from '../../../app/utils/client';
+import { dispatchToastMessage } from '../../lib/toast';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'permalink-star',
+ icon: 'permalink',
+ label: 'Get_link',
+ // classes: 'clipboard',
+ context: ['starred', 'threads'],
+ async action(_, props) {
+ try {
+ const { message = messageArgs(this).msg } = props;
+ const permalink = await MessageAction.getPermaLink(message._id);
+ navigator.clipboard.writeText(permalink);
+ dispatchToastMessage({ type: 'success', message: t('Copied') });
+ } catch (e) {
+ dispatchToastMessage({ type: 'error', message: e });
+ }
+ },
+ condition({ message, subscription, user }) {
+ if (subscription == null) {
+ return false;
+ }
+
+ return Boolean(message.starred?.find((star) => star._id === user?._id));
+ },
+ order: 101,
+ group: 'menu',
+ });
+});
diff --git a/apps/meteor/client/startup/actionButtons/pinMessage.ts b/apps/meteor/client/startup/actionButtons/pinMessage.ts
new file mode 100644
index 000000000000..70164fcdba70
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/pinMessage.ts
@@ -0,0 +1,41 @@
+import { Meteor } from 'meteor/meteor';
+
+import { hasAtLeastOnePermission } from '../../../app/authorization/client';
+import { settings } from '../../../app/settings/client';
+import { MessageAction } from '../../../app/ui-utils/client';
+import { queryClient } from '../../lib/queryClient';
+import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
+import { dispatchToastMessage } from '../../lib/toast';
+import { call } from '../../lib/utils/call';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'pin-message',
+ icon: 'pin',
+ label: 'Pin',
+ context: ['pinned', 'message', 'message-mobile', 'threads', 'direct'],
+ async action(_, props) {
+ const { message = messageArgs(this).msg } = props;
+ message.pinned = true;
+ try {
+ await call('pinMessage', message);
+ queryClient.invalidateQueries(['rooms', message.rid, 'pinned-messages']);
+ } catch (error) {
+ dispatchToastMessage({ type: 'error', message: error });
+ }
+ },
+ condition({ message, subscription, room }) {
+ if (!settings.get('Message_AllowPinning') || message.pinned || !subscription) {
+ return false;
+ }
+ const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
+ if (isLivechatRoom) {
+ return false;
+ }
+ return hasAtLeastOnePermission('pin-message', message.rid);
+ },
+ order: 7,
+ group: 'menu',
+ });
+});
diff --git a/apps/meteor/client/startup/readReceipt.ts b/apps/meteor/client/startup/actionButtons/readReceipt.ts
similarity index 69%
rename from apps/meteor/client/startup/readReceipt.ts
rename to apps/meteor/client/startup/actionButtons/readReceipt.ts
index 306f07082dac..dbc9fc670bc0 100644
--- a/apps/meteor/client/startup/readReceipt.ts
+++ b/apps/meteor/client/startup/actionButtons/readReceipt.ts
@@ -1,11 +1,11 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
-import { settings } from '../../app/settings/client';
-import { MessageAction } from '../../app/ui-utils/client';
-import { imperativeModal } from '../lib/imperativeModal';
-import { messageArgs } from '../lib/utils/messageArgs';
-import ReadReceiptsModal from '../views/room/modals/ReadReceiptsModal';
+import { settings } from '../../../app/settings/client';
+import { MessageAction } from '../../../app/ui-utils/client';
+import { imperativeModal } from '../../lib/imperativeModal';
+import { messageArgs } from '../../lib/utils/messageArgs';
+import ReadReceiptsModal from '../../views/room/modals/ReadReceiptsModal';
Meteor.startup(() => {
Tracker.autorun(() => {
diff --git a/apps/meteor/client/startup/actionButtons/starMessage.ts b/apps/meteor/client/startup/actionButtons/starMessage.ts
new file mode 100644
index 000000000000..fd069d75e874
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/starMessage.ts
@@ -0,0 +1,41 @@
+import { Meteor } from 'meteor/meteor';
+
+import { settings } from '../../../app/settings/client';
+import { MessageAction } from '../../../app/ui-utils/client';
+import { queryClient } from '../../lib/queryClient';
+import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
+import { dispatchToastMessage } from '../../lib/toast';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'star-message',
+ icon: 'star',
+ label: 'Star',
+ context: ['starred', 'message', 'message-mobile', 'threads', 'federated'],
+ action(_, props) {
+ const { message = messageArgs(this).msg } = props;
+ Meteor.call('starMessage', { ...message, starred: true }, (error: any) => {
+ if (error) {
+ dispatchToastMessage({ type: 'error', message: error });
+ return;
+ }
+
+ queryClient.invalidateQueries(['rooms', message.rid, 'starred-messages']);
+ });
+ },
+ condition({ message, subscription, user, room }) {
+ if (subscription == null && settings.get('Message_AllowStarring')) {
+ return false;
+ }
+ const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
+ if (isLivechatRoom) {
+ return false;
+ }
+
+ return !Array.isArray(message.starred) || !message.starred.find((star: any) => star._id === user?._id);
+ },
+ order: 9,
+ group: 'menu',
+ });
+});
diff --git a/apps/meteor/client/startup/actionButtons/unpinMessage.ts b/apps/meteor/client/startup/actionButtons/unpinMessage.ts
new file mode 100644
index 000000000000..3c7e305dc7e5
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/unpinMessage.ts
@@ -0,0 +1,37 @@
+import { Meteor } from 'meteor/meteor';
+
+import { hasAtLeastOnePermission } from '../../../app/authorization/client';
+import { settings } from '../../../app/settings/client';
+import { MessageAction } from '../../../app/ui-utils/client';
+import { queryClient } from '../../lib/queryClient';
+import { dispatchToastMessage } from '../../lib/toast';
+import { call } from '../../lib/utils/call';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'unpin-message',
+ icon: 'pin',
+ label: 'Unpin',
+ context: ['pinned', 'message', 'message-mobile', 'threads', 'direct'],
+ async action(_, props) {
+ const { message = messageArgs(this).msg } = props;
+ message.pinned = false;
+ try {
+ await call('unpinMessage', message);
+ queryClient.invalidateQueries(['rooms', message.rid, 'pinned-messages']);
+ } catch (error) {
+ dispatchToastMessage({ type: 'error', message: error });
+ }
+ },
+ condition({ message, subscription }) {
+ if (!subscription || !settings.get('Message_AllowPinning') || !message.pinned) {
+ return false;
+ }
+
+ return hasAtLeastOnePermission('pin-message', message.rid);
+ },
+ order: 8,
+ group: 'menu',
+ });
+});
diff --git a/apps/meteor/client/startup/actionButtons/unstarMessage.ts b/apps/meteor/client/startup/actionButtons/unstarMessage.ts
new file mode 100644
index 000000000000..da2a1a9f261b
--- /dev/null
+++ b/apps/meteor/client/startup/actionButtons/unstarMessage.ts
@@ -0,0 +1,37 @@
+import { Meteor } from 'meteor/meteor';
+
+import { settings } from '../../../app/settings/client';
+import { MessageAction } from '../../../app/ui-utils/client';
+import { queryClient } from '../../lib/queryClient';
+import { dispatchToastMessage } from '../../lib/toast';
+import { messageArgs } from '../../lib/utils/messageArgs';
+
+Meteor.startup(() => {
+ MessageAction.addButton({
+ id: 'unstar-message',
+ icon: 'star',
+ label: 'Unstar_Message',
+ context: ['starred', 'message', 'message-mobile', 'threads', 'federated'],
+ action(_, props) {
+ const { message = messageArgs(this).msg } = props;
+
+ Meteor.call('starMessage', { ...message, starred: false }, (error?: any) => {
+ if (error) {
+ dispatchToastMessage({ type: 'error', message: error });
+ return;
+ }
+
+ queryClient.invalidateQueries(['rooms', message.rid, 'starred-messages']);
+ });
+ },
+ condition({ message, subscription, user }) {
+ if (subscription == null && settings.get('Message_AllowStarring')) {
+ return false;
+ }
+
+ return Boolean(message.starred?.find((star: any) => star._id === user?._id));
+ },
+ order: 9,
+ group: 'menu',
+ });
+});
diff --git a/apps/meteor/client/startup/contextualBar/exportMessages.ts b/apps/meteor/client/startup/contextualBar/exportMessagesTab.ts
similarity index 100%
rename from apps/meteor/client/startup/contextualBar/exportMessages.ts
rename to apps/meteor/client/startup/contextualBar/exportMessagesTab.ts
diff --git a/apps/meteor/client/startup/contextualBar/index.ts b/apps/meteor/client/startup/contextualBar/index.ts
index 46bf4c2cb7cc..cde839b9be06 100644
--- a/apps/meteor/client/startup/contextualBar/index.ts
+++ b/apps/meteor/client/startup/contextualBar/index.ts
@@ -1 +1,4 @@
-import './exportMessages';
+import './exportMessagesTab';
+import './mentionsTab';
+import './pinnedMessagesTab';
+import './starredMessagesTab';
diff --git a/apps/meteor/client/startup/contextualBar/mentionsTab.ts b/apps/meteor/client/startup/contextualBar/mentionsTab.ts
new file mode 100644
index 000000000000..510a22ecbf99
--- /dev/null
+++ b/apps/meteor/client/startup/contextualBar/mentionsTab.ts
@@ -0,0 +1,12 @@
+import { lazy } from 'react';
+
+import { addAction } from '../../views/room/lib/Toolbox';
+
+addAction('mentions', {
+ groups: ['channel', 'group', 'team'],
+ id: 'mentions',
+ title: 'Mentions',
+ icon: 'at',
+ template: lazy(() => import('../../views/room/contextualBar/MentionsTab')),
+ order: 9,
+});
diff --git a/apps/meteor/app/message-pin/client/tabBar.ts b/apps/meteor/client/startup/contextualBar/pinnedMessagesTab.ts
similarity index 78%
rename from apps/meteor/app/message-pin/client/tabBar.ts
rename to apps/meteor/client/startup/contextualBar/pinnedMessagesTab.ts
index 6065172f7cb6..1488e7d3a9e6 100644
--- a/apps/meteor/app/message-pin/client/tabBar.ts
+++ b/apps/meteor/client/startup/contextualBar/pinnedMessagesTab.ts
@@ -1,8 +1,8 @@
-import { useMemo } from 'react';
-import { useSetting } from '@rocket.chat/ui-contexts';
import { isRoomFederated } from '@rocket.chat/core-typings';
+import { useSetting } from '@rocket.chat/ui-contexts';
+import { lazy, useMemo } from 'react';
-import { addAction } from '../../../client/views/room/lib/Toolbox';
+import { addAction } from '../../views/room/lib/Toolbox';
addAction('pinned-messages', ({ room }) => {
const pinningAllowed = useSetting('Message_AllowPinning');
@@ -15,7 +15,7 @@ addAction('pinned-messages', ({ room }) => {
id: 'pinned-messages',
title: 'Pinned_Messages',
icon: 'pin',
- template: 'pinnedMessages',
+ template: lazy(() => import('../../views/room/contextualBar/PinnedMessagesTab')),
...(federated && {
'data-tooltip': 'Pinned_messages_unavailable_for_federation',
'disabled': true,
diff --git a/apps/meteor/app/message-star/client/tabBar.ts b/apps/meteor/client/startup/contextualBar/starredMessagesTab.ts
similarity index 51%
rename from apps/meteor/app/message-star/client/tabBar.ts
rename to apps/meteor/client/startup/contextualBar/starredMessagesTab.ts
index d4cf0680ba64..4b3677db7600 100644
--- a/apps/meteor/app/message-star/client/tabBar.ts
+++ b/apps/meteor/client/startup/contextualBar/starredMessagesTab.ts
@@ -1,10 +1,12 @@
-import { addAction } from '../../../client/views/room/lib/Toolbox';
+import { lazy } from 'react';
+
+import { addAction } from '../../views/room/lib/Toolbox';
addAction('starred-messages', {
groups: ['channel', 'group', 'direct', 'direct_multiple', 'team'],
id: 'starred-messages',
title: 'Starred_Messages',
icon: 'star',
- template: 'starredMessages',
+ template: lazy(() => import('../../views/room/contextualBar/StarredMessagesTab')),
order: 10,
});
diff --git a/apps/meteor/client/startup/index.ts b/apps/meteor/client/startup/index.ts
index 4a2566dfd937..240cdb46d717 100644
--- a/apps/meteor/client/startup/index.ts
+++ b/apps/meteor/client/startup/index.ts
@@ -1,4 +1,6 @@
+import '../lib/rooms/roomTypes';
import './absoluteUrl';
+import './actionButtons';
import './afterLogoutCleanUp';
import './appRoot';
import './banners';
@@ -22,7 +24,6 @@ import './oauth';
import './openedRoom';
import './otr';
import './readMessage';
-import './readReceipt';
import './reloadRoomAfterLogin';
import './renderMessage';
import './renderNotification';
@@ -38,4 +39,3 @@ import './UserDeleted';
import './userRoles';
import './usersObserve';
import './userStatusManuallySet';
-import '../lib/rooms/roomTypes';
diff --git a/apps/meteor/client/startup/messageTypes.ts b/apps/meteor/client/startup/messageTypes.ts
index 6b4d0d104b21..4ad514335016 100644
--- a/apps/meteor/client/startup/messageTypes.ts
+++ b/apps/meteor/client/startup/messageTypes.ts
@@ -57,4 +57,10 @@ Meteor.startup(() => {
};
},
});
+
+ MessageTypes.registerType({
+ id: 'message_pinned',
+ system: true,
+ message: 'Pinned_a_message',
+ });
});
diff --git a/apps/meteor/client/views/room/MessageList/MessageList.tsx b/apps/meteor/client/views/room/MessageList/MessageList.tsx
index 61609e7a05bf..c85974065456 100644
--- a/apps/meteor/client/views/room/MessageList/MessageList.tsx
+++ b/apps/meteor/client/views/room/MessageList/MessageList.tsx
@@ -32,9 +32,7 @@ export const MessageList = ({ rid }: MessageListProps): ReactElement => {
return (
- {messages.map((message, index, arr) => {
- const previous = arr[index - 1];
-
+ {messages.map((message, index, { [index - 1]: previous }) => {
const sequential = isMessageSequential(message, previous, messageGroupingPeriod);
const newDay = isMessageNewDay(message, previous);
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts b/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts
index a71f0ec2b03b..fb91bd1d9533 100644
--- a/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts
+++ b/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts
@@ -1,41 +1,17 @@
import type { IRoom, IMessage, MessageTypesValues } from '@rocket.chat/core-typings';
import { useStableArray } from '@rocket.chat/fuselage-hooks';
-import { useSetting, useUserSubscription } from '@rocket.chat/ui-contexts';
+import { useSetting } from '@rocket.chat/ui-contexts';
import type { Mongo } from 'meteor/mongo';
import { useCallback, useMemo } from 'react';
-import { Messages } from '../../../../../app/models/client';
+import { ChatMessage } from '../../../../../app/models/client';
import { useReactiveValue } from '../../../../hooks/useReactiveValue';
-import type { MessageWithMdEnforced } from '../../../../lib/parseMessageTextToAstMarkdown';
-import { parseMessageTextToAstMarkdown, removePossibleNullMessageValues } from '../../../../lib/parseMessageTextToAstMarkdown';
-import { useAutoTranslate } from './useAutoTranslate';
-import { useKatex } from './useKatex';
-export const useMessages = ({ rid }: { rid: IRoom['_id'] }): MessageWithMdEnforced[] => {
- const { katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled } = useKatex();
- const subscription = useUserSubscription(rid);
-
- const autoTranslateOptions = useAutoTranslate(subscription);
- const showColors = Boolean(useSetting('HexColorPreview_Enabled'));
+export const useMessages = ({ rid }: { rid: IRoom['_id'] }): IMessage[] => {
const hideSysMes = useSetting('Hide_System_Messages');
const hideSysMessages = useStableArray(Array.isArray(hideSysMes) ? hideSysMes : []);
- const normalizeMessage = useMemo(() => {
- const parseOptions = {
- colors: showColors,
- emoticons: true,
- ...(katexEnabled && {
- katex: {
- dollarSyntax: katexDollarSyntaxEnabled,
- parenthesisSyntax: katexParenthesisSyntaxEnabled,
- },
- }),
- };
- return (message: IMessage): MessageWithMdEnforced =>
- parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateOptions);
- }, [showColors, katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled, autoTranslateOptions]);
-
const query: Mongo.Query = useMemo(
() => ({
rid,
@@ -46,17 +22,15 @@ export const useMessages = ({ rid }: { rid: IRoom['_id'] }): MessageWithMdEnforc
[rid, hideSysMessages],
);
- return useReactiveValue(
+ return useReactiveValue(
useCallback(
() =>
- Messages.find(query, {
+ ChatMessage.find(query, {
sort: {
ts: 1,
},
- })
- .fetch()
- .map(normalizeMessage),
- [query, normalizeMessage],
+ }).fetch(),
+ [query],
),
);
};
diff --git a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx
index 6f3ea86ad8e5..5d90f6d569da 100644
--- a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx
+++ b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx
@@ -1,3 +1,4 @@
+import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings';
import { Button, Tag, Box } from '@rocket.chat/fuselage';
import { useContentBoxSize, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import {
@@ -26,7 +27,6 @@ import { useSubscription } from 'use-subscription';
import { EmojiPicker } from '../../../../../../../app/emoji/client';
import { createComposerAPI } from '../../../../../../../app/ui-message/client/messageBox/createComposerAPI';
-import type { MessageBoxTemplateInstance } from '../../../../../../../app/ui-message/client/messageBox/messageBox';
import type { FormattingButton } from '../../../../../../../app/ui-message/client/messageBox/messageBoxFormatting';
import { formattingButtons } from '../../../../../../../app/ui-message/client/messageBox/messageBoxFormatting';
import { messageBox, popover } from '../../../../../../../app/ui-utils/client';
@@ -80,7 +80,23 @@ const getEmptyFalse = () => false;
const a: any[] = [];
const getEmptyArray = () => a;
-type MessageBoxProps = Omit;
+type MessageBoxProps = {
+ rid: IRoom['_id'];
+ tmid?: IMessage['_id'];
+ readOnly: boolean;
+ onSend?: (params: { value: string; tshow?: boolean }) => Promise;
+ onJoin?: () => Promise;
+ onResize?: () => void;
+ onTyping?: () => void;
+ onEscape?: () => void;
+ onNavigateToPreviousMessage?: () => void;
+ onNavigateToNextMessage?: () => void;
+ onUploadFiles?: (files: readonly File[]) => void;
+ tshow?: IMessage['tshow'];
+ subscription?: ISubscription;
+ showFormattingTips: boolean;
+ isEmbedded?: boolean;
+};
const MessageBox = ({
rid,
diff --git a/apps/meteor/client/views/room/components/contextualBar/MessageListTab.tsx b/apps/meteor/client/views/room/components/contextualBar/MessageListTab.tsx
new file mode 100644
index 000000000000..2f136067d96b
--- /dev/null
+++ b/apps/meteor/client/views/room/components/contextualBar/MessageListTab.tsx
@@ -0,0 +1,125 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import type { Icon } from '@rocket.chat/fuselage';
+import { Box, MessageDivider, Throbber } from '@rocket.chat/fuselage';
+import { useTranslation } from '@rocket.chat/ui-contexts';
+import type { UseQueryResult } from '@tanstack/react-query';
+import type { ReactElement, ComponentProps, ReactNode } from 'react';
+import React, { useCallback } from 'react';
+import { Virtuoso } from 'react-virtuoso';
+
+import { MessageTypes } from '../../../../../app/ui-utils/client';
+import type { MessageActionContext } from '../../../../../app/ui-utils/client/lib/MessageAction';
+import ScrollableContentWrapper from '../../../../components/ScrollableContentWrapper';
+import VerticalBarClose from '../../../../components/VerticalBar/VerticalBarClose';
+import VerticalBarContent from '../../../../components/VerticalBar/VerticalBarContent';
+import VerticalBarHeader from '../../../../components/VerticalBar/VerticalBarHeader';
+import VerticalBarIcon from '../../../../components/VerticalBar/VerticalBarIcon';
+import VerticalBarText from '../../../../components/VerticalBar/VerticalBarText';
+import RoomMessage from '../../../../components/message/variants/RoomMessage';
+import SystemMessage from '../../../../components/message/variants/SystemMessage';
+import { useFormatDate } from '../../../../hooks/useFormatDate';
+import MessageListErrorBoundary from '../../MessageList/MessageListErrorBoundary';
+import { isMessageFirstUnread } from '../../MessageList/lib/isMessageFirstUnread';
+import { isMessageNewDay } from '../../MessageList/lib/isMessageNewDay';
+import MessageListProvider from '../../MessageList/providers/MessageListProvider';
+import { useRoomSubscription } from '../../contexts/RoomContext';
+import { useTabBarClose } from '../../contexts/ToolboxContext';
+
+type MessageListTabProps = {
+ iconName: ComponentProps['name'];
+ title: ReactNode;
+ emptyResultMessage: ReactNode;
+ context: MessageActionContext;
+ queryResult: UseQueryResult;
+};
+
+const MessageListTab = ({ iconName, title, emptyResultMessage, context, queryResult }: MessageListTabProps): ReactElement => {
+ const t = useTranslation();
+ const formatDate = useFormatDate();
+
+ const closeTabBar = useTabBarClose();
+ const handleTabBarCloseButtonClick = useCallback(() => {
+ closeTabBar();
+ }, [closeTabBar]);
+
+ const subscription = useRoomSubscription();
+
+ return (
+ <>
+
+
+ {title}
+
+
+
+ {queryResult.isLoading && (
+
+
+
+ )}
+ {queryResult.isSuccess && (
+ <>
+ {queryResult.data.length === 0 && (
+
+ {emptyResultMessage}
+
+ )}
+
+ {queryResult.data.length > 0 && (
+
+
+
+ {
+ const previous = queryResult.data[index - 1];
+
+ const newDay = isMessageNewDay(message, previous);
+ const firstUnread = isMessageFirstUnread(subscription, message, previous);
+ const showDivider = newDay || firstUnread;
+
+ const system = MessageTypes.isSystemMessage(message);
+
+ const unread = subscription?.tunread?.includes(message._id) ?? false;
+ const mention = subscription?.tunreadUser?.includes(message._id) ?? false;
+ const all = subscription?.tunreadGroup?.includes(message._id) ?? false;
+
+ return (
+ <>
+ {showDivider && (
+
+ {newDay && formatDate(message.ts)}
+
+ )}
+
+ {system ? (
+
+ ) : (
+
+ )}
+ >
+ );
+ }}
+ />
+
+
+
+ )}
+ >
+ )}
+
+ >
+ );
+};
+
+export default MessageListTab;
diff --git a/apps/meteor/client/views/room/contextualBar/MentionsTab.tsx b/apps/meteor/client/views/room/contextualBar/MentionsTab.tsx
new file mode 100644
index 000000000000..2777157e2a19
--- /dev/null
+++ b/apps/meteor/client/views/room/contextualBar/MentionsTab.tsx
@@ -0,0 +1,44 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
+import { useQuery } from '@tanstack/react-query';
+import type { ReactElement } from 'react';
+import React from 'react';
+
+import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
+import MessageListTab from '../components/contextualBar/MessageListTab';
+import { useRoom } from '../contexts/RoomContext';
+
+const MentionsTab = (): ReactElement => {
+ const getMentionedMessages = useEndpoint('GET', '/v1/chat.getMentionedMessages');
+
+ const room = useRoom();
+
+ const mentionedMessagesQueryResult = useQuery(['rooms', room._id, 'mentioned-messages'] as const, async () => {
+ const messages: IMessage[] = [];
+
+ for (
+ let offset = 0, result = await getMentionedMessages({ roomId: room._id, offset: 0 });
+ result.count > 0;
+ // eslint-disable-next-line no-await-in-loop
+ offset += result.count, result = await getMentionedMessages({ roomId: room._id, offset })
+ ) {
+ messages.push(...result.messages.map(mapMessageFromApi));
+ }
+
+ return messages;
+ });
+
+ const t = useTranslation();
+
+ return (
+
+ );
+};
+
+export default MentionsTab;
diff --git a/apps/meteor/client/views/room/contextualBar/PinnedMessagesTab.tsx b/apps/meteor/client/views/room/contextualBar/PinnedMessagesTab.tsx
new file mode 100644
index 000000000000..3c212e018d28
--- /dev/null
+++ b/apps/meteor/client/views/room/contextualBar/PinnedMessagesTab.tsx
@@ -0,0 +1,44 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
+import { useQuery } from '@tanstack/react-query';
+import type { ReactElement } from 'react';
+import React from 'react';
+
+import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
+import MessageListTab from '../components/contextualBar/MessageListTab';
+import { useRoom } from '../contexts/RoomContext';
+
+const PinnedMessagesTab = (): ReactElement => {
+ const getPinnedMessages = useEndpoint('GET', '/v1/chat.getPinnedMessages');
+
+ const room = useRoom();
+
+ const pinnedMessagesQueryResult = useQuery(['rooms', room._id, 'pinned-messages'] as const, async () => {
+ const messages: IMessage[] = [];
+
+ for (
+ let offset = 0, result = await getPinnedMessages({ roomId: room._id, offset: 0 });
+ result.count > 0;
+ // eslint-disable-next-line no-await-in-loop
+ offset += result.count, result = await getPinnedMessages({ roomId: room._id, offset })
+ ) {
+ messages.push(...result.messages.map(mapMessageFromApi));
+ }
+
+ return messages;
+ });
+
+ const t = useTranslation();
+
+ return (
+
+ );
+};
+
+export default PinnedMessagesTab;
diff --git a/apps/meteor/client/views/room/contextualBar/StarredMessagesTab.tsx b/apps/meteor/client/views/room/contextualBar/StarredMessagesTab.tsx
new file mode 100644
index 000000000000..eee3be6808cf
--- /dev/null
+++ b/apps/meteor/client/views/room/contextualBar/StarredMessagesTab.tsx
@@ -0,0 +1,44 @@
+import type { IMessage } from '@rocket.chat/core-typings';
+import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
+import { useQuery } from '@tanstack/react-query';
+import type { ReactElement } from 'react';
+import React from 'react';
+
+import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
+import MessageListTab from '../components/contextualBar/MessageListTab';
+import { useRoom } from '../contexts/RoomContext';
+
+const StarredMessagesTab = (): ReactElement => {
+ const getStarredMessages = useEndpoint('GET', '/v1/chat.getStarredMessages');
+
+ const room = useRoom();
+
+ const starredMessagesQueryResult = useQuery(['rooms', room._id, 'starred-messages'] as const, async () => {
+ const messages: IMessage[] = [];
+
+ for (
+ let offset = 0, result = await getStarredMessages({ roomId: room._id, offset: 0 });
+ result.count > 0;
+ // eslint-disable-next-line no-await-in-loop
+ offset += result.count, result = await getStarredMessages({ roomId: room._id, offset })
+ ) {
+ messages.push(...result.messages.map(mapMessageFromApi));
+ }
+
+ return messages;
+ });
+
+ const t = useTranslation();
+
+ return (
+
+ );
+};
+
+export default StarredMessagesTab;
diff --git a/apps/meteor/definition/externals/meteor/templating.d.ts b/apps/meteor/definition/externals/meteor/templating.d.ts
new file mode 100644
index 000000000000..5ec2bb975589
--- /dev/null
+++ b/apps/meteor/definition/externals/meteor/templating.d.ts
@@ -0,0 +1,116 @@
+import 'meteor/templating';
+import type { Blaze } from 'meteor/blaze';
+import type { ReactiveVar } from 'meteor/reactive-var';
+
+declare module 'meteor/blaze' {
+ namespace Blaze {
+ interface Template {
+ events(
+ eventsMap: Record<
+ string,
+ (
+ this: TInstance,
+ event: {
+ [K in keyof JQuery.TriggeredEvent]: any;
+ },
+ instance: TInstance,
+ ) => void
+ >,
+ ): void;
+ }
+ }
+}
+
+declare module 'meteor/templating' {
+ interface TemplateStatic {
+ requiresPermission: Blaze.Template>;
+ ChatpalAdmin: Blaze.Template>;
+ ChatpalSearchResultTemplate: Blaze.Template>;
+ ChatpalSearchSingleTemplate: Blaze.Template>;
+ ChatpalSearchSingleUser: Blaze.Template>;
+ ChatpalSearchSingleRoom: Blaze.Template>;
+ ChatpalSuggestionItemTemplate: Blaze.Template>;
+ emojiPicker: Blaze.Template>;
+ lazyloadImage: Blaze.Template>;
+ customFieldsForm: Blaze.Template>;
+ ExternalFrameContainer: Blaze.Template>;
+ broadcastView: Blaze.Template>;
+ liveStreamBroadcast: Blaze.Template>;
+ liveStreamTab: Blaze.Template>;
+ liveStreamView: Blaze.Template>;
+ snippetPage: Blaze.Template>;
+ snippetedMessages: Blaze.Template>;
+ inputAutocomplete: Blaze.Template>;
+ textareaAutocomplete: Blaze.Template>;
+ _autocompleteContainer: Blaze.Template>;
+ _noMatch: Blaze.Template>;
+ authorize: Blaze.Template>;
+ oauth404: Blaze.Template>;
+ oembedBaseWidget: Blaze.Template>;
+ oembedAudioWidget: Blaze.Template>;
+ oembedFrameWidget: Blaze.Template>;
+ oembedImageWidget: Blaze.Template>;
+ oembedUrlWidget: Blaze.Template>;
+ oembedVideoWidget: Blaze.Template>;
+ oembedYoutubeWidget: Blaze.Template>;
+ DefaultSearchResultTemplate: Blaze.Template>;
+ DefaultSuggestionItemTemplate: Blaze.Template>;
+ RocketSearch: Blaze.Template>;
+ icon: Blaze.Template>;
+ popupList: Blaze.Template>;
+ popupList_default: Blaze.Template>;
+ popupList_item_default: Blaze.Template>;
+ popupList_loading: Blaze.Template>;
+ popupList_item_channel: Blaze.Template>;
+ popupList_item_custom: Blaze.Template>;
+ selectDropdown: Blaze.Template>;
+ CodeMirror: Blaze.Template>;
+ photoswipeContent: Blaze.Template>;
+ roomSearch: Blaze.Template>;
+ roomSearchEmpty: Blaze.Template>;
+ avatar: Blaze.Template>;
+ username: Blaze.Template<
+ Record,
+ Blaze.TemplateInstance> & {
+ customFields: ReactiveVar | null>;
+ username: ReactiveVar<{
+ ready: boolean;
+ username: string;
+ empty?: boolean;
+ error?: boolean;
+ invalid?: boolean;
+ escaped?: string;
+ blocked?: boolean;
+ unavailable?: boolean;
+ }>;
+ validate: () => unknown;
+ }
+ >;
+ error: Blaze.Template>;
+ loading: Blaze.Template>;
+ message: Blaze.Template>;
+ messageThread: Blaze.Template>;
+ messagePopup: Blaze.Template>;
+ messagePopupChannel: Blaze.Template>;
+ messagePopupConfig: Blaze.Template>;
+ messagePopupEmoji: Blaze.Template>;
+ messagePopupSlashCommand: Blaze.Template>;
+ messagePopupSlashCommandPreview: Blaze.Template>;
+ messagePopupUser: Blaze.Template>;
+ collapseArrow: Blaze.Template>;
+ rc_modal: Blaze.Template>;
+ popout: Blaze.Template>;
+ popover: Blaze.Template>;
+ audit: Blaze.Template>;
+ messagePopupCannedResponse: Blaze.Template>;
+
+ instance(): TemplateStatic[TTemplateName] extends Blaze.Template ? I : never;
+ }
+}
diff --git a/packages/core-typings/src/IMessage/IMessage.ts b/packages/core-typings/src/IMessage/IMessage.ts
index e9fffaa5e81e..c88101fcd108 100644
--- a/packages/core-typings/src/IMessage/IMessage.ts
+++ b/packages/core-typings/src/IMessage/IMessage.ts
@@ -89,6 +89,7 @@ export type MessageTypesValues =
| 'room-disallowed-reacting'
| 'command'
| 'videoconf'
+ | 'message_pinned'
| LivechatMessageTypes
| TeamMessageTypes
| VoipMessageTypesValues
@@ -186,8 +187,6 @@ export interface IMessage extends IRocketChatRecord {
};
}
-export type ToolboxMessageType = 'message' | 'thread' | 'federated';
-
export type MessageSystem = {
t: 'system';
};
diff --git a/packages/rest-typings/package.json b/packages/rest-typings/package.json
index 2857df0ca438..a939f93b0dc7 100644
--- a/packages/rest-typings/package.json
+++ b/packages/rest-typings/package.json
@@ -29,5 +29,8 @@
"@rocket.chat/message-parser": "next",
"@rocket.chat/ui-kit": "next",
"ajv": "^8.11.0"
+ },
+ "volta": {
+ "extends": "../../package.json"
}
}
diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts
index 0995896edcf4..0df97e5b0275 100644
--- a/packages/rest-typings/src/v1/chat.ts
+++ b/packages/rest-typings/src/v1/chat.ts
@@ -405,6 +405,102 @@ const ChatGetMessageReadReceiptsSchema = {
export const isChatGetMessageReadReceiptsProps = ajv.compile(ChatGetMessageReadReceiptsSchema);
+type GetStarredMessages = {
+ roomId: IRoom['_id'];
+ count?: number;
+ offset?: number;
+ sort?: string;
+};
+
+const GetStarredMessagesSchema = {
+ type: 'object',
+ properties: {
+ roomId: {
+ type: 'string',
+ },
+ count: {
+ type: 'number',
+ nullable: true,
+ },
+ offset: {
+ type: 'number',
+ nullable: true,
+ },
+ sort: {
+ type: 'string',
+ nullable: true,
+ },
+ },
+ required: ['roomId'],
+ additionalProperties: false,
+};
+
+export const isChatGetStarredMessagesPayload = ajv.compile(GetStarredMessagesSchema);
+
+type GetPinnedMessages = {
+ roomId: IRoom['_id'];
+ count?: number;
+ offset?: number;
+ sort?: string;
+};
+
+const GetPinnedMessagesSchema = {
+ type: 'object',
+ properties: {
+ roomId: {
+ type: 'string',
+ },
+ count: {
+ type: 'number',
+ nullable: true,
+ },
+ offset: {
+ type: 'number',
+ nullable: true,
+ },
+ sort: {
+ type: 'string',
+ nullable: true,
+ },
+ },
+ required: ['roomId'],
+ additionalProperties: false,
+};
+
+export const isChatGetPinnedMessagesPayload = ajv.compile(GetPinnedMessagesSchema);
+
+type GetMentionedMessages = {
+ roomId: IRoom['_id'];
+ count?: number;
+ offset?: number;
+ sort?: string;
+};
+
+const GetMentionedMessagesSchema = {
+ type: 'object',
+ properties: {
+ roomId: {
+ type: 'string',
+ },
+ count: {
+ type: 'number',
+ nullable: true,
+ },
+ offset: {
+ type: 'number',
+ nullable: true,
+ },
+ sort: {
+ type: 'string',
+ nullable: true,
+ },
+ },
+ required: ['roomId'],
+ additionalProperties: false,
+};
+
+export const isChatGetMentionedMessagesPayload = ajv.compile(GetMentionedMessagesSchema);
+
export type ChatEndpoints = {
'/v1/chat.sendMessage': {
POST: (params: ChatSendMessage) => IMessage;
@@ -481,4 +577,28 @@ export type ChatEndpoints = {
'/v1/chat.getMessageReadReceipts': {
GET: (params: ChatGetMessageReadReceipts) => { receipts: ReadReceipt[] };
};
+ '/v1/chat.getStarredMessages': {
+ GET: (params: GetStarredMessages) => {
+ messages: IMessage[];
+ count: number;
+ offset: number;
+ total: number;
+ };
+ };
+ '/v1/chat.getPinnedMessages': {
+ GET: (params: GetPinnedMessages) => {
+ messages: IMessage[];
+ count: number;
+ offset: number;
+ total: number;
+ };
+ };
+ '/v1/chat.getMentionedMessages': {
+ GET: (params: GetMentionedMessages) => {
+ messages: IMessage[];
+ count: number;
+ offset: number;
+ total: number;
+ };
+ };
};
diff --git a/packages/ui-contexts/src/ServerContext/methods.ts b/packages/ui-contexts/src/ServerContext/methods.ts
index 0ee4f5b6c178..d40a943e0d5c 100644
--- a/packages/ui-contexts/src/ServerContext/methods.ts
+++ b/packages/ui-contexts/src/ServerContext/methods.ts
@@ -266,6 +266,8 @@ export interface ServerMethods {
'permissions/get': (updatedSince?: Date) => IPermission[] | { update: IPermission[]; remove: IPermission[] };
'public-settings/get': (updatedSince?: Date) => ISetting[] | { update: ISetting[]; remove: ISetting[] };
'private-settings/get': (updatedSince?: Date) => ISetting[] | { update: ISetting[]; remove: ISetting[] };
+ 'pinMessage': (message: IMessage) => void;
+ 'unpinMessage': (message: IMessage) => void;
}
export type ServerMethodName = keyof ServerMethods;