diff --git a/apps/meteor/app/importer-slack/server/SlackImporter.ts b/apps/meteor/app/importer-slack/server/SlackImporter.ts index 7d56c7784b76..0ef81c69a1e0 100644 --- a/apps/meteor/app/importer-slack/server/SlackImporter.ts +++ b/apps/meteor/app/importer-slack/server/SlackImporter.ts @@ -408,7 +408,7 @@ export class SlackImporter extends Importer { parseMentions(newMessage: IImportMessage): void { const mentionsParser = new MentionsParser({ pattern: () => '[0-9a-zA-Z]+', - useRealName: () => settings.get('UI_Use_Real_Name'), + useRealName: () => settings.get('UI_Use_Real_Name'), me: () => 'me', }); diff --git a/apps/meteor/app/lib/server/functions/sendMessage.js b/apps/meteor/app/lib/server/functions/sendMessage.js index e2f45d38fcbc..4886b13afba5 100644 --- a/apps/meteor/app/lib/server/functions/sendMessage.js +++ b/apps/meteor/app/lib/server/functions/sendMessage.js @@ -249,9 +249,10 @@ export const sendMessage = async function (user, message, room, upsert = false, parseUrlsInMessage(message, previewUrls); + message = await Message.beforeSave({ message, room, user }); + message = await callbacks.run('beforeSaveMessage', message, room); - message = await Message.beforeSave({ message, room, user }); if (message) { if (message.t === 'otr') { const otrStreamer = notifications.streamRoomMessage; diff --git a/apps/meteor/app/mentions/lib/MentionsParser.js b/apps/meteor/app/mentions/lib/MentionsParser.js deleted file mode 100644 index 87329ac9f120..000000000000 --- a/apps/meteor/app/mentions/lib/MentionsParser.js +++ /dev/null @@ -1,135 +0,0 @@ -import { escapeHTML } from '@rocket.chat/string-helpers'; - -const userTemplateDefault = ({ prefix, className, mention, title, label, type = 'username' }) => - `${prefix}${label}`; -const roomTemplateDefault = ({ prefix, reference, mention }) => - `${prefix}${`#${mention}`}`; -export class MentionsParser { - constructor({ pattern, useRealName, me, roomTemplate = roomTemplateDefault, userTemplate = userTemplateDefault }) { - this.pattern = pattern; - this.useRealName = useRealName; - this.me = me; - this.userTemplate = userTemplate; - this.roomTemplate = roomTemplate; - } - - set me(m) { - this._me = m; - } - - get me() { - return typeof this._me === 'function' ? this._me() : this._me; - } - - set pattern(p) { - this._pattern = p; - } - - get pattern() { - return typeof this._pattern === 'function' ? this._pattern() : this._pattern; - } - - set useRealName(s) { - this._useRealName = s; - } - - get useRealName() { - return typeof this._useRealName === 'function' ? this._useRealName() : this._useRealName; - } - - get userMentionRegex() { - return new RegExp(`(^|\\s|>)@(${this.pattern}(@(${this.pattern}))?(:([0-9a-zA-Z-_.]+))?)`, 'gm'); - } - - get channelMentionRegex() { - return new RegExp(`(^|\\s|>)#(${this.pattern}(@(${this.pattern}))?)`, 'gm'); - } - - replaceUsers = (msg, { mentions, temp }, me) => - msg.replace(this.userMentionRegex, (match, prefix, mention) => { - const classNames = ['mention-link']; - - if (mention === 'all') { - classNames.push('mention-link--all'); - classNames.push('mention-link--group'); - } else if (mention === 'here') { - classNames.push('mention-link--here'); - classNames.push('mention-link--group'); - } else if (mention === me) { - classNames.push('mention-link--me'); - classNames.push('mention-link--user'); - } else { - classNames.push('mention-link--user'); - } - - const className = classNames.join(' '); - - if (mention === 'all' || mention === 'here') { - return this.userTemplate({ prefix, className, mention, label: mention, type: 'group' }); - } - - const filterUser = ({ username, type }) => (!type || type === 'user') && username === mention; - const filterTeam = ({ name, type }) => type === 'team' && name === mention; - - const [mentionObj] = (mentions || []).filter((m) => filterUser(m) || filterTeam(m)); - - const label = temp - ? mention && escapeHTML(mention) - : mentionObj && escapeHTML(mentionObj.type === 'team' || this.useRealName ? mentionObj.name : mentionObj.username); - - if (!label) { - return match; - } - - return this.userTemplate({ - prefix, - className, - mention, - label, - type: mentionObj?.type === 'team' ? 'team' : 'username', - title: this.useRealName ? mention : label, - }); - }); - - replaceChannels = (msg, { temp, channels }) => - msg.replace(/'/g, "'").replace(this.channelMentionRegex, (match, prefix, mention) => { - if ( - !temp && - !( - channels && - channels.find((c) => { - return c.dname ? c.dname === mention : c.name === mention; - }) - ) - ) { - return match; - } - - const channel = - channels && - channels.find(({ name, dname }) => { - return dname ? dname === mention : name === mention; - }); - const reference = channel ? channel._id : mention; - return this.roomTemplate({ prefix, reference, channel, mention }); - }); - - getUserMentions(str) { - return (str.match(this.userMentionRegex) || []).map((match) => match.trim()); - } - - getChannelMentions(str) { - return (str.match(this.channelMentionRegex) || []).map((match) => match.trim()); - } - - parse(message) { - let msg = (message && message.html) || ''; - if (!msg.trim()) { - return message; - } - msg = this.replaceUsers(msg, message, this.me); - msg = this.replaceChannels(msg, message, this.me); - message.html = msg; - return message; - } -} diff --git a/apps/meteor/app/mentions/lib/MentionsParser.ts b/apps/meteor/app/mentions/lib/MentionsParser.ts new file mode 100644 index 000000000000..c4180eecf845 --- /dev/null +++ b/apps/meteor/app/mentions/lib/MentionsParser.ts @@ -0,0 +1,140 @@ +import type { IMessage } from '@rocket.chat/core-typings'; +import { escapeHTML } from '@rocket.chat/string-helpers'; + +export type MentionsParserArgs = { + pattern: () => string; + useRealName?: () => boolean; + me?: () => string; + roomTemplate?: (args: { prefix: string; reference: string; mention: string; channel?: any }) => string; + userTemplate?: (args: { prefix: string; className: string; mention: string; title?: string; label: string; type?: string }) => string; +}; + +const userTemplateDefault = ({ + prefix, + className, + mention, + title = '', + label, + type = 'username', +}: { + prefix: string; + className: string; + mention: string; + title?: string; + label?: string; + type?: string; +}) => `${prefix}${label}`; + +const roomTemplateDefault = ({ prefix, reference, mention }: { prefix: string; reference: string; mention: string }) => + `${prefix}${`#${mention}`}`; + +export class MentionsParser { + me: () => string; + + pattern: MentionsParserArgs['pattern']; + + userTemplate: (args: { prefix: string; className: string; mention: string; title?: string; label: string; type?: string }) => string; + + roomTemplate: (args: { prefix: string; reference: string; mention: string; channel?: any }) => string; + + useRealName: () => boolean; + + constructor({ pattern, useRealName, me, roomTemplate = roomTemplateDefault, userTemplate = userTemplateDefault }: MentionsParserArgs) { + this.pattern = pattern; + this.useRealName = useRealName || (() => false); + this.me = me || (() => ''); + this.userTemplate = userTemplate; + this.roomTemplate = roomTemplate; + } + + get userMentionRegex() { + return new RegExp(`(^|\\s|>)@(${this.pattern()}(@(${this.pattern()}))?(:([0-9a-zA-Z-_.]+))?)`, 'gm'); + } + + get channelMentionRegex() { + return new RegExp(`(^|\\s|>)#(${this.pattern()}(@(${this.pattern()}))?)`, 'gm'); + } + + replaceUsers = (msg: string, { mentions, temp }: IMessage, me: string) => + msg.replace(this.userMentionRegex, (match, prefix, mention) => { + const classNames = ['mention-link']; + + if (mention === 'all') { + classNames.push('mention-link--all'); + classNames.push('mention-link--group'); + } else if (mention === 'here') { + classNames.push('mention-link--here'); + classNames.push('mention-link--group'); + } else if (mention === me) { + classNames.push('mention-link--me'); + classNames.push('mention-link--user'); + } else { + classNames.push('mention-link--user'); + } + + const className = classNames.join(' '); + + if (mention === 'all' || mention === 'here') { + return this.userTemplate({ prefix, className, mention, label: mention, type: 'group' }); + } + + const filterUser = ({ username, type }: { username?: string; type?: string }) => (!type || type === 'user') && username === mention; + const filterTeam = ({ name, type }: { name?: string; type?: string }) => type === 'team' && name === mention; + + const [mentionObj] = (mentions || []).filter((m) => m && (filterUser(m) || filterTeam(m))); + + const label = temp + ? mention && escapeHTML(mention) + : mentionObj && escapeHTML((mentionObj.type === 'team' || this.useRealName() ? mentionObj.name : mentionObj.username) || ''); + + if (!label) { + return match; + } + + return this.userTemplate({ + prefix, + className, + mention, + label, + type: mentionObj?.type === 'team' ? 'team' : 'username', + title: this.useRealName() ? mention : label, + }); + }); + + replaceChannels = (msg: string, { temp, channels }: IMessage) => + msg.replace(/'/g, "'").replace(this.channelMentionRegex, (match, prefix, mention) => { + if ( + !temp && + !channels?.find((c) => { + return c.name === mention; + }) + ) { + return match; + } + + const channel = channels?.find(({ name }) => { + return name === mention; + }); + const reference = channel ? channel._id : mention; + return this.roomTemplate({ prefix, reference, channel, mention }); + }); + + getUserMentions(str: string) { + return (str.match(this.userMentionRegex) || []).map((match) => match.trim()); + } + + getChannelMentions(str: string) { + return (str.match(this.channelMentionRegex) || []).map((match) => match.trim()); + } + + parse(message: IMessage) { + let msg = message?.html || ''; + if (!msg.trim()) { + return message; + } + msg = this.replaceUsers(msg, message, this.me()); + msg = this.replaceChannels(msg, message); + message.html = msg; + return message; + } +} diff --git a/apps/meteor/app/mentions/server/Mentions.js b/apps/meteor/app/mentions/server/Mentions.js deleted file mode 100644 index 91518fd74855..000000000000 --- a/apps/meteor/app/mentions/server/Mentions.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Mentions is a named function that will process Mentions - * @param {Object} message - The message object - */ -import { MentionsParser } from '../lib/MentionsParser'; - -export default class MentionsServer extends MentionsParser { - constructor(args) { - super(args); - this.messageMaxAll = args.messageMaxAll; - this.getChannel = args.getChannel; - this.getChannels = args.getChannels; - this.getUsers = args.getUsers; - this.getUser = args.getUser; - this.getTotalChannelMembers = args.getTotalChannelMembers; - this.onMaxRoomMembersExceeded = args.onMaxRoomMembersExceeded || (() => {}); - } - - set getUsers(m) { - this._getUsers = m; - } - - get getUsers() { - return typeof this._getUsers === 'function' ? this._getUsers : async () => this._getUsers; - } - - set getChannels(m) { - this._getChannels = m; - } - - get getChannels() { - return typeof this._getChannels === 'function' ? this._getChannels : () => this._getChannels; - } - - set getChannel(m) { - this._getChannel = m; - } - - get getChannel() { - return typeof this._getChannel === 'function' ? this._getChannel : () => this._getChannel; - } - - set messageMaxAll(m) { - this._messageMaxAll = m; - } - - get messageMaxAll() { - return typeof this._messageMaxAll === 'function' ? this._messageMaxAll() : this._messageMaxAll; - } - - async getUsersByMentions({ msg, rid, u: sender }) { - let mentions = this.getUserMentions(msg); - const mentionsAll = []; - const userMentions = []; - - for await (const m of mentions) { - const mention = m.trim().substr(1); - if (mention !== 'all' && mention !== 'here') { - userMentions.push(mention); - continue; - } - if (this.messageMaxAll > 0 && (await this.getTotalChannelMembers(rid)) > this.messageMaxAll) { - await this.onMaxRoomMembersExceeded({ sender, rid }); - continue; - } - mentionsAll.push({ - _id: mention, - username: mention, - }); - } - mentions = userMentions.length ? await this.getUsers(userMentions) : []; - return [...mentionsAll, ...mentions]; - } - - async getChannelbyMentions({ msg }) { - const channels = this.getChannelMentions(msg); - return this.getChannels(channels.map((c) => c.trim().substr(1))); - } - - async execute(message) { - const mentionsAll = await this.getUsersByMentions(message); - const channels = await this.getChannelbyMentions(message); - - message.mentions = mentionsAll; - message.channels = channels; - - return message; - } -} diff --git a/apps/meteor/app/mentions/server/Mentions.ts b/apps/meteor/app/mentions/server/Mentions.ts new file mode 100644 index 000000000000..9eda56fea21c --- /dev/null +++ b/apps/meteor/app/mentions/server/Mentions.ts @@ -0,0 +1,84 @@ +/* + * Mentions is a named function that will process Mentions + * @param {Object} message - The message object + */ +import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; + +import { type MentionsParserArgs, MentionsParser } from '../lib/MentionsParser'; + +type MentionsServerArgs = MentionsParserArgs & { + messageMaxAll: () => number; + getChannels: (c: string[]) => Promise[]>; + getUsers: (u: string[]) => Promise<{ type: 'team' | 'user'; _id: string; username?: string; name?: string }[]>; + getUser: (u: string) => Promise; + getTotalChannelMembers: (rid: string) => Promise; + onMaxRoomMembersExceeded: ({ sender, rid }: { sender: IMessage['u']; rid: string }) => Promise; +}; + +export class MentionsServer extends MentionsParser { + messageMaxAll: MentionsServerArgs['messageMaxAll']; + + getChannels: MentionsServerArgs['getChannels']; + + getUsers: MentionsServerArgs['getUsers']; + + getUser: MentionsServerArgs['getUser']; + + getTotalChannelMembers: MentionsServerArgs['getTotalChannelMembers']; + + onMaxRoomMembersExceeded: MentionsServerArgs['onMaxRoomMembersExceeded']; + + constructor(args: MentionsServerArgs) { + super(args); + + this.messageMaxAll = args.messageMaxAll; + this.getChannels = args.getChannels; + this.getUsers = args.getUsers; + this.getUser = args.getUser; + this.getTotalChannelMembers = args.getTotalChannelMembers; + this.onMaxRoomMembersExceeded = + args.onMaxRoomMembersExceeded || + (() => { + /* do nothing */ + }); + } + + async getUsersByMentions({ msg, rid, u: sender }: Pick): Promise { + const mentions = this.getUserMentions(msg); + const mentionsAll: { _id: string; username: string }[] = []; + const userMentions = []; + + for await (const m of mentions) { + const mention = m.trim().substr(1); + if (mention !== 'all' && mention !== 'here') { + userMentions.push(mention); + continue; + } + if (this.messageMaxAll() > 0 && (await this.getTotalChannelMembers(rid)) > this.messageMaxAll()) { + await this.onMaxRoomMembersExceeded({ sender, rid }); + continue; + } + mentionsAll.push({ + _id: mention, + username: mention, + }); + } + + return [...mentionsAll, ...(userMentions.length ? await this.getUsers(userMentions) : [])]; + } + + async getChannelbyMentions({ msg }: Pick) { + const channels = this.getChannelMentions(msg); + return this.getChannels(channels.map((c) => c.trim().substr(1))); + } + + async execute(message: IMessage) { + const mentionsAll = await this.getUsersByMentions(message); + const channels = await this.getChannelbyMentions(message); + + message.mentions = mentionsAll; + message.channels = channels; + + return message; + } +} diff --git a/apps/meteor/app/mentions/server/index.ts b/apps/meteor/app/mentions/server/index.ts index a04af05b9db1..b16d62185a64 100644 --- a/apps/meteor/app/mentions/server/index.ts +++ b/apps/meteor/app/mentions/server/index.ts @@ -1,3 +1,2 @@ import './getMentionedTeamMembers'; import './methods/getUserMentionsByChannel'; -import './server'; diff --git a/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts b/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts index 41a55aefc5cf..70a2a6008e56 100644 --- a/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts +++ b/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts @@ -15,7 +15,7 @@ export function normalizeThreadTitle({ ...message }: Readonly) { return filteredMessage; } const uid = Meteor.userId(); - const me = uid && Users.findOne(uid, { fields: { username: 1 } })?.username; + const me = (uid && Users.findOne(uid, { fields: { username: 1 } })?.username) || ''; const pattern = settings.get('UTF8_User_Names_Validation'); const useRealName = settings.get('UI_Use_Real_Name'); diff --git a/apps/meteor/client/components/GazzodownText.tsx b/apps/meteor/client/components/GazzodownText.tsx index 76868f84e3af..fe86cbd86fd1 100644 --- a/apps/meteor/client/components/GazzodownText.tsx +++ b/apps/meteor/client/components/GazzodownText.tsx @@ -15,7 +15,7 @@ import { useMessageListHighlights } from './message/list/MessageListContext'; type GazzodownTextProps = { children: JSX.Element; mentions?: { - type: 'user' | 'team'; + type?: 'user' | 'team'; _id: string; username?: string; name?: string; diff --git a/apps/meteor/app/mentions/server/server.ts b/apps/meteor/server/services/messages/hooks/BeforeSaveMentions.ts similarity index 66% rename from apps/meteor/app/mentions/server/server.ts rename to apps/meteor/server/services/messages/hooks/BeforeSaveMentions.ts index 13765e99d856..bcf022587e8a 100644 --- a/apps/meteor/app/mentions/server/server.ts +++ b/apps/meteor/server/services/messages/hooks/BeforeSaveMentions.ts @@ -1,23 +1,20 @@ -import { api, Team } from '@rocket.chat/core-services'; -import type { IUser, IRoom, ITeam } from '@rocket.chat/core-typings'; +import { api, Team, MeteorError } from '@rocket.chat/core-services'; +import type { IMessage, IUser, IRoom } from '@rocket.chat/core-typings'; import { Subscriptions, Users, Rooms } from '@rocket.chat/models'; -import { Meteor } from 'meteor/meteor'; -import { callbacks } from '../../../lib/callbacks'; -import { i18n } from '../../../server/lib/i18n'; -import { settings } from '../../settings/server'; -import MentionsServer from './Mentions'; +import { MentionsServer } from '../../../../app/mentions/server/Mentions'; +import { settings } from '../../../../app/settings/server'; +import { i18n } from '../../../lib/i18n'; -export class MentionQueries { - async getUsers( - usernames: string[], - ): Promise<((Pick & { type: 'user' }) | (Pick & { type: 'team' }))[]> { +class MentionQueries { + async getUsers(usernames: string[]): Promise<{ type: 'team' | 'user'; _id: string; username?: string; name?: string }[]> { const uniqueUsernames = [...new Set(usernames)]; + const teams = await Team.listByNames(uniqueUsernames, { projection: { name: 1 } }); - const users = await Users.find( + const users = await Users.find>( { username: { $in: uniqueUsernames } }, - { projection: { _id: true, username: true, name: 1 } }, + { projection: { _id: 1, username: 1, name: 1 } }, ).toArray(); const taggedUsers = users.map((user) => ({ @@ -65,27 +62,26 @@ export class MentionQueries { const queries = new MentionQueries(); -const mention = new MentionsServer({ +export const mentionServer = new MentionsServer({ pattern: () => settings.get('UTF8_User_Names_Validation'), messageMaxAll: () => settings.get('Message_MaxAll'), getUsers: async (usernames: string[]) => queries.getUsers(usernames), getUser: async (userId: string) => queries.getUser(userId), getTotalChannelMembers: (rid: string) => queries.getTotalChannelMembers(rid), getChannels: (channels: string[]) => queries.getChannels(channels), - async onMaxRoomMembersExceeded({ sender, rid }: { sender: IUser; rid: string }) { + async onMaxRoomMembersExceeded({ sender, rid }: { sender: IMessage['u']; rid: string }): Promise { // Get the language of the user for the error notification. - const { language } = await this.getUser(sender._id); - const msg = i18n.t('Group_mentions_disabled_x_members', { total: this.messageMaxAll, lng: language }); + const { language } = (await this.getUser(sender._id)) || {}; + const msg = i18n.t('Group_mentions_disabled_x_members', { total: this.messageMaxAll(), lng: language }); void api.broadcast('notify.ephemeralMessage', sender._id, rid, { msg, }); // Also throw to stop propagation of 'sendMessage'. - throw new Meteor.Error('error-action-not-allowed', msg, { + throw new MeteorError('error-action-not-allowed', msg, { method: 'filterATAllTag', action: msg, }); }, }); -callbacks.add('beforeSaveMessage', async (message) => mention.execute(message), callbacks.priority.HIGH, 'mentions'); diff --git a/apps/meteor/server/services/messages/service.ts b/apps/meteor/server/services/messages/service.ts index 85d71d08ae12..22b05029a65e 100644 --- a/apps/meteor/server/services/messages/service.ts +++ b/apps/meteor/server/services/messages/service.ts @@ -16,6 +16,7 @@ import { BeforeSaveBadWords } from './hooks/BeforeSaveBadWords'; import { BeforeSaveCheckMAC } from './hooks/BeforeSaveCheckMAC'; import { BeforeSaveJumpToMessage } from './hooks/BeforeSaveJumpToMessage'; import { BeforeSaveMarkdownParser } from './hooks/BeforeSaveMarkdownParser'; +import { mentionServer } from './hooks/BeforeSaveMentions'; import { BeforeSavePreventMention } from './hooks/BeforeSavePreventMention'; import { BeforeSaveSpotify } from './hooks/BeforeSaveSpotify'; @@ -134,6 +135,7 @@ export class MessageService extends ServiceClassInternal implements IMessageServ // TODO looks like this one was not being used (so I'll left it commented) // await this.joinDiscussionOnMessage({ message, room, user }); + message = await mentionServer.execute(message); message = await this.cannedResponse.replacePlaceholders({ message, room, user }); message = await this.markdownParser.parseMarkdown({ message, config: this.getMarkdownConfig() }); message = await this.badWords.filterBadWords({ message }); diff --git a/apps/meteor/tests/unit/app/mentions/client.tests.js b/apps/meteor/tests/unit/app/mentions/client.tests.js index 9981eee4aab9..70ac7b943201 100644 --- a/apps/meteor/tests/unit/app/mentions/client.tests.js +++ b/apps/meteor/tests/unit/app/mentions/client.tests.js @@ -5,73 +5,12 @@ import { MentionsParser } from '../../../../app/mentions/lib/MentionsParser'; let mentionsParser; beforeEach(() => { mentionsParser = new MentionsParser({ - pattern: '[0-9a-zA-Z-_.]+', + pattern: () => '[0-9a-zA-Z-_.]+', me: () => 'me', }); }); describe('Mention', () => { - describe('get pattern', () => { - const regexp = '[0-9a-zA-Z-_.]+'; - beforeEach(() => { - mentionsParser.pattern = () => regexp; - }); - - describe('by function', () => { - it(`should be equal to ${regexp}`, () => { - expect(regexp).to.be.equal(mentionsParser.pattern); - }); - }); - - describe('by const', () => { - it(`should be equal to ${regexp}`, () => { - expect(regexp).to.be.equal(mentionsParser.pattern); - }); - }); - }); - - describe('get useRealName', () => { - beforeEach(() => { - mentionsParser.useRealName = () => true; - }); - - describe('by function', () => { - it('should be true', () => { - expect(true).to.be.equal(mentionsParser.useRealName); - }); - }); - - describe('by const', () => { - it('should be true', () => { - expect(true).to.be.equal(mentionsParser.useRealName); - }); - }); - }); - - describe('get me', () => { - const me = 'me'; - - describe('by function', () => { - beforeEach(() => { - mentionsParser.me = () => me; - }); - - it(`should be equal to ${me}`, () => { - expect(me).to.be.equal(mentionsParser.me); - }); - }); - - describe('by const', () => { - beforeEach(() => { - mentionsParser.me = me; - }); - - it(`should be equal to ${me}`, () => { - expect(me).to.be.equal(mentionsParser.me); - }); - }); - }); - describe('getUserMentions', () => { describe('for simple text, no mentions', () => { const result = []; diff --git a/apps/meteor/tests/unit/app/mentions/server.tests.js b/apps/meteor/tests/unit/app/mentions/server.tests.js index b0d82f02195a..335f72af491d 100644 --- a/apps/meteor/tests/unit/app/mentions/server.tests.js +++ b/apps/meteor/tests/unit/app/mentions/server.tests.js @@ -1,12 +1,12 @@ import { expect } from 'chai'; -import MentionsServer from '../../../../app/mentions/server/Mentions'; +import { MentionsServer } from '../../../../app/mentions/server/Mentions'; let mention; beforeEach(() => { mention = new MentionsServer({ - pattern: '[0-9a-zA-Z-_.]+', + pattern: () => '[0-9a-zA-Z-_.]+', messageMaxAll: () => 4, // || RocketChat.settings.get('Message_MaxAll') getUsers: async (usernames) => [ @@ -224,67 +224,4 @@ describe('Mention Server', () => { expect(result).to.be.deep.equal(expected); }); }); - - describe('getters and setters', () => { - describe('messageMaxAll', () => { - const mention = new MentionsServer({}); - describe('constant', () => { - it('should return the informed value', () => { - mention.messageMaxAll = 4; - expect(mention.messageMaxAll).to.be.deep.equal(4); - }); - }); - describe('function', () => { - it('should return the informed value', () => { - mention.messageMaxAll = () => 4; - expect(mention.messageMaxAll).to.be.deep.equal(4); - }); - }); - }); - describe('getUsers', () => { - const mention = new MentionsServer({}); - describe('constant', () => { - it('should return the informed value', async () => { - mention.getUsers = 4; - expect(await mention.getUsers()).to.be.deep.equal(4); - }); - }); - describe('function', () => { - it('should return the informed value', async () => { - mention.getUsers = () => 4; - expect(await mention.getUsers()).to.be.deep.equal(4); - }); - }); - }); - describe('getChannels', () => { - const mention = new MentionsServer({}); - describe('constant', () => { - it('should return the informed value', () => { - mention.getChannels = 4; - expect(mention.getChannels()).to.be.deep.equal(4); - }); - }); - describe('function', () => { - it('should return the informed value', () => { - mention.getChannels = () => 4; - expect(mention.getChannels()).to.be.deep.equal(4); - }); - }); - }); - describe('getChannel', () => { - const mention = new MentionsServer({}); - describe('constant', () => { - it('should return the informed value', () => { - mention.getChannel = true; - expect(mention.getChannel()).to.be.deep.equal(true); - }); - }); - describe('function', () => { - it('should return the informed value', () => { - mention.getChannel = () => true; - expect(mention.getChannel()).to.be.deep.equal(true); - }); - }); - }); - }); }); diff --git a/packages/core-typings/src/IMessage/IMessage.ts b/packages/core-typings/src/IMessage/IMessage.ts index 3ed5e470585f..4d6845735898 100644 --- a/packages/core-typings/src/IMessage/IMessage.ts +++ b/packages/core-typings/src/IMessage/IMessage.ts @@ -13,8 +13,6 @@ import type { IUser } from '../IUser'; import type { FileProp } from './MessageAttachment/Files/FileProp'; import type { MessageAttachment } from './MessageAttachment/MessageAttachment'; -type MentionType = 'user' | 'team'; - type MessageUrl = { url: string; source?: string; @@ -121,15 +119,20 @@ export type TokenExtra = { noHtml?: string; }; +export type MessageMention = { + type?: 'user' | 'team'; // mentions for 'all' and 'here' doesn't have type + _id: string; + name?: string; + username?: string; +}; + export interface IMessage extends IRocketChatRecord { rid: RoomID; msg: string; tmid?: string; tshow?: boolean; ts: Date; - mentions?: ({ - type: MentionType; - } & Pick)[]; + mentions?: MessageMention[]; groupable?: boolean; channels?: Pick[]; diff --git a/packages/gazzodown/src/MarkupInteractionContext.ts b/packages/gazzodown/src/MarkupInteractionContext.ts index 40acaf634802..70fc32d33496 100644 --- a/packages/gazzodown/src/MarkupInteractionContext.ts +++ b/packages/gazzodown/src/MarkupInteractionContext.ts @@ -1,19 +1,19 @@ -import type { IRoom, IUser } from '@rocket.chat/core-typings'; +import type { MessageMention } from '@rocket.chat/core-typings'; import type * as MessageParser from '@rocket.chat/message-parser'; import { createContext, FormEvent, UIEvent } from 'react'; -export type UserMention = Pick; -export type ChannelMention = Pick; +export type UserMention = MessageMention; +export type ChannelMention = MessageMention; type MarkupInteractionContextValue = { detectEmoji?: (text: string) => { name: string; className: string; image?: string; content: string }[]; highlightRegex?: () => RegExp; markRegex?: () => RegExp; onTaskChecked?: (task: MessageParser.Task) => ((e: FormEvent) => void) | undefined; - resolveUserMention?: (mention: string) => UserMention | undefined; - onUserMentionClick?: (mentionedUser: UserMention) => ((e: UIEvent) => void) | undefined; - resolveChannelMention?: (mention: string) => ChannelMention | undefined; - onChannelMentionClick?: (mentionedChannel: ChannelMention) => ((e: UIEvent) => void) | undefined; + resolveUserMention?: (mention: string) => MessageMention | undefined; + onUserMentionClick?: (mentionedUser: MessageMention) => ((e: UIEvent) => void) | undefined; + resolveChannelMention?: (mention: string) => MessageMention | undefined; + onChannelMentionClick?: (mentionedChannel: MessageMention) => ((e: UIEvent) => void) | undefined; convertAsciiToEmoji?: boolean; useEmoji?: boolean; useRealName?: boolean;