Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 6 additions & 25 deletions apps/meteor/app/emoji/lib/rocketchat.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { IEmoji } from '@rocket.chat/core-typings';
import type { TranslationKey } from '@rocket.chat/ui-contexts';

export type EmojiPackage = {
Expand All @@ -13,33 +14,13 @@ export type EmojiPackage = {
_regexp?: RegExp | null;
};

/**
* Standardized emoji packages structure
* Uses IEmoji union type for type-safe emoji list entries
*/
export type EmojiPackages = {
packages: {
[key: string]: EmojiPackage;
};
list: {
[key: keyof NonNullable<EmojiPackages['packages']>]:
| {
category: string;
emojiPackage: string;
shortnames: string[];
uc_base: string;
uc_greedy: string;
uc_match: string;
uc_output: string;
aliases?: string[];
aliasOf?: undefined;
extension?: string;
etag?: string;
unicode?: string;
}
| {
emojiPackage: string;
aliasOf: string;
extension?: undefined;
aliases?: undefined;
shortnames?: undefined;
etag?: string;
};
};
list: Record<string, IEmoji>;
};
64 changes: 46 additions & 18 deletions apps/meteor/client/lib/customEmoji.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { IEmoji } from '@rocket.chat/core-typings';
import type { ICustomEmojiListEntry, IEmojiAlias, IEmojiCustom } from '@rocket.chat/core-typings';
import { escapeRegExp } from '@rocket.chat/string-helpers';

import { emoji, removeFromRecent, replaceEmojiInRecent } from '../../app/emoji/client';
import { getURL } from '../../app/utils/client';

/**
* Custom emoji data with optional previousName for updates
*/
type CustomEmojiUpdate = IEmojiCustom & {
previousName?: string;
};

const isSetNotNull = (fn: () => unknown) => {
let value;
try {
Expand All @@ -14,21 +21,24 @@ const isSetNotNull = (fn: () => unknown) => {
return value !== null && value !== undefined;
};

export const updateEmojiCustom = (emojiData: IEmoji) => {
export const updateEmojiCustom = (emojiData: CustomEmojiUpdate) => {
const previousExists = isSetNotNull(() => emojiData.previousName);
const currentAliases = isSetNotNull(() => emojiData.aliases);

if (previousExists && isSetNotNull(() => emoji.list[`:${emojiData.previousName}:`].aliases)) {
for (const alias of emoji.list[`:${emojiData.previousName}:`].aliases ?? []) {
delete emoji.list[`:${alias}:`];
const aliasIndex = emoji.packages.emojiCustom.list?.indexOf(`:${alias}:`) ?? -1;
if (aliasIndex !== -1) {
emoji.packages.emojiCustom.list?.splice(aliasIndex, 1);
if (previousExists && isSetNotNull(() => emoji.list[`:${emojiData.previousName}:`])) {
const previousEmoji = emoji.list[`:${emojiData.previousName}:`];
if ('aliases' in previousEmoji && previousEmoji.aliases) {
for (const alias of previousEmoji.aliases) {
delete emoji.list[`:${alias}:`];
const aliasIndex = emoji.packages.emojiCustom.list?.indexOf(`:${alias}:`) ?? -1;
if (aliasIndex !== -1) {
emoji.packages.emojiCustom.list?.splice(aliasIndex, 1);
}
}
}
}

if (previousExists && emojiData.name !== emojiData.previousName) {
if (previousExists && emojiData.previousName && emojiData.name !== emojiData.previousName) {
const arrayIndex = emoji.packages.emojiCustom.emojisByCategory.rocket.indexOf(emojiData.previousName);
if (arrayIndex !== -1) {
emoji.packages.emojiCustom.emojisByCategory.rocket.splice(arrayIndex, 1);
Expand All @@ -45,30 +55,36 @@ export const updateEmojiCustom = (emojiData: IEmoji) => {
emoji.packages.emojiCustom.emojisByCategory.rocket.push(`${emojiData.name}`);
emoji.packages.emojiCustom.list?.push(`:${emojiData.name}:`);
}

// Don't inherit fields from a native emoji being overridden (e.g. its unicode), or the pick would output the native emoji
// TODO: Fix the IEmoji type and standardize the emoji packs types
emoji.list[`:${emojiData.name}:`] = {
...emojiData,
const customEmojiEntry: ICustomEmojiListEntry = {
name: emojiData.name,
aliases: emojiData.aliases,
extension: emojiData.extension,
etag: emojiData.etag,
emojiPackage: 'emojiCustom',
} as unknown as (typeof emoji.list)[keyof typeof emoji.list];
};
emoji.list[`:${emojiData.name}:`] = customEmojiEntry;

if (currentAliases) {
for (const alias of emojiData.aliases) {
emoji.packages.emojiCustom.list?.push(`:${alias}:`);
emoji.list[`:${alias}:`] = {
const aliasEntry: IEmojiAlias = {
emojiPackage: 'emojiCustom',
aliasOf: emojiData.name,
};
emoji.list[`:${alias}:`] = aliasEntry;
}
}

if (previousExists) {
if (previousExists && emojiData.previousName) {
replaceEmojiInRecent({ oldEmoji: emojiData.previousName, newEmoji: emojiData.name });
}

emoji.dispatchUpdate();
};

export const deleteEmojiCustom = (emojiData: IEmoji) => {
export const deleteEmojiCustom = (emojiData: CustomEmojiUpdate) => {
delete emoji.list[`:${emojiData.name}:`];
const arrayIndex = emoji.packages.emojiCustom.emojisByCategory.rocket.indexOf(emojiData.name);
if (arrayIndex !== -1) {
Expand Down Expand Up @@ -119,14 +135,26 @@ export const customRender = (html: string) => {
let emojiAlias = shortname.replace(/:/g, '');

let dataCheck = emoji.list[shortname];
if (dataCheck.aliasOf) {
if (!dataCheck) {
return shortname;
}

if ('aliasOf' in dataCheck && dataCheck.aliasOf) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
emojiAlias = dataCheck.aliasOf;
dataCheck = emoji.list[`:${emojiAlias}:`];
}

if (!dataCheck) {
return shortname;
}

if (!('extension' in dataCheck)) {
return shortname;
}

return `<span class="emoji emoji--custom" style="background-image:url(${getEmojiUrlFromName(
emojiAlias,
dataCheck.extension!,
dataCheck.extension,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
dataCheck.etag,
)});" data-emoji="${emojiAlias}" title="${shortname}">${shortname}</span>`;
});
Expand Down
13 changes: 11 additions & 2 deletions apps/meteor/client/views/root/hooks/loggedIn/useCustomEmoji.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ICustomEmojiListEntry, IEmojiAlias } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useEffect } from 'react';
Expand Down Expand Up @@ -38,13 +39,21 @@ export const useCustomEmoji = () => {
for (const currentEmoji of customEmojis) {
emoji.packages.emojiCustom.emojisByCategory.rocket.push(currentEmoji.name);
emoji.packages.emojiCustom.list?.push(`:${currentEmoji.name}:`);
emoji.list[`:${currentEmoji.name}:`] = { ...currentEmoji, emojiPackage: 'emojiCustom' } as any;
const customEmojiEntry: ICustomEmojiListEntry = {
Comment thread
Jbansal2 marked this conversation as resolved.
name: currentEmoji.name,
aliases: currentEmoji.aliases,
extension: currentEmoji.extension,
etag: currentEmoji.etag,
emojiPackage: 'emojiCustom',
};
emoji.list[`:${currentEmoji.name}:`] = customEmojiEntry;
for (const alias of currentEmoji.aliases) {
emoji.packages.emojiCustom.list?.push(`:${alias}:`);
emoji.list[`:${alias}:`] = {
const aliasEntry: IEmojiAlias = {
emojiPackage: 'emojiCustom',
aliasOf: currentEmoji.name,
};
emoji.list[`:${alias}:`] = aliasEntry;
}
}
emoji.dispatchUpdate();
Expand Down
6 changes: 3 additions & 3 deletions packages/core-services/src/events/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import type { ISetting as AppsSetting } from '@rocket.chat/apps-engine/definition/settings';
import type {
IEmailInbox,
IEmoji,
IEmojiCustom,
IInstanceStatus,
IIntegration,
IIntegrationHistory,
Expand Down Expand Up @@ -65,8 +65,8 @@ export type EventSignatures = {
'banner.enabled'(bannerId: string): void;
'banner.disabled'(bannerId: string): void;
'banner.user'(userId: string, banner: IBanner): void;
'emoji.deleteCustom'(emoji: IEmoji): void;
'emoji.updateCustom'(emoji: IEmoji): void;
'emoji.deleteCustom'(emoji: IEmojiCustom): void;
'emoji.updateCustom'(emoji: IEmojiCustom & { previousName?: string; previousExtension?: string }): void;
'license.module'(data: { module: string; valid: boolean }): void;
'license.sync'(): void;
'license.actions'(actions: Record<Partial<LicenseLimitKind>, boolean>): void;
Expand Down
43 changes: 41 additions & 2 deletions packages/core-typings/src/IEmoji.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
export interface IEmoji {
[x: string]: any;
/** Common emoji entry fields. */

interface IEmojiBase {
emojiPackage: string;
}

/**
* Native emoji from emoji packages (like emojione, noto, etc.)
*/
export interface INativeEmoji extends IEmojiBase {
category: string;
shortnames: string[];
uc_base: string;
uc_greedy: string;
uc_match: string;
uc_output: string;
aliases?: string[];
unicode?: string;
}

/**
* Alias entry pointing to another emoji
*/
export interface IEmojiAlias extends IEmojiBase {
aliasOf: string;
etag?: string;
}

/**
* Custom emoji entry in the emoji list
*/
export interface ICustomEmojiListEntry extends IEmojiBase {
name: string;
aliases: string[];
extension: string;
etag?: string;
}

/**
* Union type for all possible emoji list entries
*/
export type IEmoji = INativeEmoji | IEmojiAlias | ICustomEmojiListEntry;
6 changes: 3 additions & 3 deletions packages/ddp-client/src/types/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
ISetting,
ISubscription,
IRole,
IEmoji,
IEmojiCustom,
ICustomSound,
INotificationDesktop,
IUser,
Expand Down Expand Up @@ -203,8 +203,8 @@ export interface StreamerEvents {
];
},
{ key: 'permissions-changed'; args: ['inserted' | 'updated' | 'removed' | 'changed', ISetting] },
{ key: 'deleteEmojiCustom'; args: [{ emojiData: IEmoji }] },
{ key: 'updateEmojiCustom'; args: [{ emojiData: IEmoji }] },
{ key: 'deleteEmojiCustom'; args: [{ emojiData: IEmojiCustom }] },
{ key: 'updateEmojiCustom'; args: [{ emojiData: IEmojiCustom & { previousName?: string } }] },
/* @deprecated */
{ key: 'new-banner'; args: [{ bannerId: string }] },

Expand Down