From 64da99a32218171d41b3af5acc14783de8dbdf49 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 29 Mar 2026 19:17:12 -0500 Subject: [PATCH] refac --- src/lib/components/common/EmojiPicker.svelte | 49 +++++++++++++++++++- src/lib/stores/index.ts | 1 + 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/lib/components/common/EmojiPicker.svelte b/src/lib/components/common/EmojiPicker.svelte index aa3df2e584..de8ba54acf 100644 --- a/src/lib/components/common/EmojiPicker.svelte +++ b/src/lib/components/common/EmojiPicker.svelte @@ -11,6 +11,9 @@ import emojiGroups from '$lib/emoji-groups.json'; import emojiShortCodes from '$lib/emoji-shortcodes.json'; + import { settings } from '$lib/stores'; + import { updateUserSettings } from '$lib/apis/users'; + const i18n = getContext('i18n'); export let onClose = () => {}; @@ -20,12 +23,37 @@ export let user = null; export let selected = null; + const MAX_RECENT = 30; + let show = false; let emojis = emojiShortCodes; let search = ''; let flattenedEmojis = []; let emojiRows = []; + let saveDebounceTimer: ReturnType | null = null; + + $: recentEmojiNames = ($settings?.recentEmojis ?? []) + .filter((name) => emojiShortCodes[name]) + .slice(0, MAX_RECENT); + + function saveRecentEmoji(emojiName: string) { + // Remove if already present, then prepend + const updated = [emojiName, ...recentEmojiNames.filter((n) => n !== emojiName)].slice( + 0, + MAX_RECENT + ); + + // Update store immediately (reactive UI) + settings.set({ ...$settings, recentEmojis: updated }); + + // Debounce backend save (avoid API spam on rapid picks) + if (saveDebounceTimer) clearTimeout(saveDebounceTimer); + saveDebounceTimer = setTimeout(async () => { + await updateUserSettings(localStorage.token, { ui: { ...$settings, recentEmojis: updated } }); + }, 1000); + } + // Reactive statement to filter the emojis based on search query $: { if (search) { @@ -55,6 +83,22 @@ // Flatten emoji groups and group them into rows of 8 for virtual scrolling $: { flattenedEmojis = []; + + // Add "Recently Used" group first (only when not searching) + if (!search && recentEmojiNames.length > 0) { + flattenedEmojis.push({ type: 'group', label: $i18n.t('Recently Used') }); + flattenedEmojis.push( + ...recentEmojiNames.map((emoji) => ({ + type: 'emoji', + name: emoji, + shortCodes: + typeof emojiShortCodes[emoji] === 'string' + ? [emojiShortCodes[emoji]] + : emojiShortCodes[emoji] + })) + ); + } + Object.keys(emojiGroups).forEach((group) => { const groupEmojis = emojiGroups[group].filter((emoji) => emojis[emoji]); if (groupEmojis.length > 0) { @@ -97,6 +141,7 @@ // Handle emoji selection function selectEmoji(emoji) { const selectedCode = emoji.shortCodes[0]; + saveRecentEmoji(emoji.name); if (selected === selectedCode) { onSubmit(null); } else { @@ -140,10 +185,10 @@ {:else}
-
+
{#if item.length === 1 && item[0].type === 'group'} -
+
{item[0].label}
{:else} diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index a6697f082d..5cd7f0d632 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -219,6 +219,7 @@ type Settings = { chatDirection?: 'LTR' | 'RTL' | 'auto'; ctrlEnterToSend?: boolean; renderMarkdownInPreviews?: boolean; + recentEmojis?: string[]; system?: string; seed?: number;