Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

151 lines
4.7 KiB
TypeScript

import { useDebouncedState, useStableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, ContextType } from 'react';
import { useState, useCallback, useMemo, useSyncExternalStore } from 'react';
import { useUpdateCustomEmoji } from './useUpdateCustomEmoji';
import { EmojiPickerContext } from '../../contexts/EmojiPickerContext';
import { emoji, getFrequentEmoji, createEmojiListByCategorySubscription } from '../../lib/emoji';
import EmojiPicker from '../../views/composer/EmojiPicker';
const DEFAULT_ITEMS_LIMIT = 90;
// limit recent emojis to 27 (3 rows of 9)
const RECENT_EMOJIS_LIMIT = 27;
export type EmojiPickerProviderProps = { children: ReactNode };
const EmojiPickerProvider = ({ children }: EmojiPickerProviderProps) => {
const [emojiPicker, setEmojiPicker] = useState<ReactNode>(null);
const [emojiToPreview, setEmojiToPreview] = useDebouncedState<{ emoji: string; name: string } | null>(null, 100);
const [recentEmojis, setRecentEmojis] = useLocalStorage<string[]>('emoji.recent', []);
const [frequentEmojis, setFrequentEmojis] = useLocalStorage<[string, number][]>('emoji.frequent', []);
const [actualTone, setActualTone] = useLocalStorage('emoji.tone', 0);
const [currentCategory, setCurrentCategory] = useState('recent');
const [customItemsLimit, setCustomItemsLimit] = useState(DEFAULT_ITEMS_LIMIT);
const [quickReactions, _setQuickReactions] = useState<{ emoji: string; image: string }[]>(() =>
getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji)),
);
const setQuickReactions = useStableCallback(() => _setQuickReactions(getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji))));
const [sub, getSnapshot] = useMemo(() => {
return createEmojiListByCategorySubscription(customItemsLimit, actualTone, recentEmojis, setRecentEmojis, setQuickReactions);
}, [customItemsLimit, actualTone, recentEmojis, setRecentEmojis, setQuickReactions]);
const [emojiListByCategory, categoriesIndexes] = useSyncExternalStore(sub, getSnapshot);
useUpdateCustomEmoji();
const addFrequentEmojis = useCallback(
(emoji: string) => {
const empty: [string, number][] = frequentEmojis.some(([emojiName]) => emojiName === emoji) ? [] : [[emoji, 0]];
const sortedFrequent = [...empty, ...frequentEmojis]
.map(([emojiName, count]) => {
return emojiName === emoji
? ([emojiName, Math.min(count + 5, 100)] as [string, number])
: ([emojiName, Math.max(count - 1, 0)] as [string, number]);
})
.sort(([, frequentA], [, frequentB]) => frequentB - frequentA);
setFrequentEmojis(sortedFrequent);
_setQuickReactions(getFrequentEmoji(sortedFrequent.map(([emoji]) => emoji)));
},
[frequentEmojis, setFrequentEmojis],
);
const addRecentEmoji = useCallback(
(_emoji: string) => {
addFrequentEmojis(_emoji);
const recent = recentEmojis || [];
const pos = recent.indexOf(_emoji);
if (pos !== -1) {
recent.splice(pos, 1);
}
recent.unshift(_emoji);
recent.splice(RECENT_EMOJIS_LIMIT);
// If this value is not cloned, the recent list will not be updated
setRecentEmojis([...recent]);
emoji.packages.base.emojisByCategory.recent = recent;
},
[recentEmojis, setRecentEmojis, addFrequentEmojis],
);
const open = useCallback((ref: Element, callback: (emoji: string) => void) => {
return setEmojiPicker(<EmojiPicker reference={ref} onClose={() => setEmojiPicker(null)} onPickEmoji={(emoji) => callback(emoji)} />);
}, []);
const close = useCallback(() => setEmojiPicker(null), []);
const handlePreview = useCallback(
(emoji: string, name: string) =>
setEmojiToPreview((preview) => {
if (preview?.emoji === emoji && preview?.name === name) {
return preview;
}
return { emoji, name };
}),
[setEmojiToPreview],
);
const handleRemovePreview = useCallback(() => setEmojiToPreview(null), [setEmojiToPreview]);
const contextValue = useMemo(
(): ContextType<typeof EmojiPickerContext> => ({
isOpen: emojiPicker !== null,
close,
open,
emojiToPreview,
handlePreview,
handleRemovePreview,
addRecentEmoji,
emojiListByCategory,
recentEmojis,
setRecentEmojis,
actualTone,
currentCategory,
setCurrentCategory,
categoriesIndexes,
customItemsLimit,
setCustomItemsLimit,
setActualTone,
quickReactions,
}),
[
emojiPicker,
open,
close,
emojiToPreview,
addRecentEmoji,
emojiListByCategory,
categoriesIndexes,
recentEmojis,
setRecentEmojis,
actualTone,
currentCategory,
setCurrentCategory,
customItemsLimit,
setActualTone,
quickReactions,
handlePreview,
handleRemovePreview,
],
);
return (
<EmojiPickerContext.Provider value={contextValue}>
{children}
{emojiPicker}
</EmojiPickerContext.Provider>
);
};
export default EmojiPickerProvider;