codeant-ai-for-open-source[bot] commented on code in PR #36417: URL: https://github.com/apache/superset/pull/36417#discussion_r2587288670
########## superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/EmojiTextArea.test.tsx: ########## @@ -0,0 +1,170 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { render, screen, userEvent } from '@superset-ui/core/spec'; +import { EmojiTextArea } from '.'; +import { filterEmojis, EMOJI_DATA } from './emojiData'; + +test('renders EmojiTextArea with placeholder', () => { + render(<EmojiTextArea placeholder="Type something..." />); + expect(screen.getByPlaceholderText('Type something...')).toBeInTheDocument(); +}); + +test('renders EmojiTextArea as textarea element', () => { + render(<EmojiTextArea placeholder="Type here" />); + const textarea = screen.getByPlaceholderText('Type here'); + expect(textarea.tagName.toLowerCase()).toBe('textarea'); +}); + +test('allows typing in the textarea', async () => { + render(<EmojiTextArea placeholder="Type here" />); + const textarea = screen.getByPlaceholderText('Type here'); + await userEvent.type(textarea, 'Hello world'); + expect(textarea).toHaveValue('Hello world'); +}); + +test('calls onChange when typing', async () => { + const onChange = jest.fn(); + render(<EmojiTextArea placeholder="Type here" onChange={onChange} />); + const textarea = screen.getByPlaceholderText('Type here'); + await userEvent.type(textarea, 'Hi'); + expect(onChange).toHaveBeenCalled(); +}); + +test('passes through rows prop', () => { + render(<EmojiTextArea placeholder="Type here" rows={5} />); + const textarea = screen.getByPlaceholderText('Type here'); + expect(textarea).toHaveAttribute('rows', '5'); +}); + +test('forwards ref to underlying component', () => { + const ref = { current: null }; + render(<EmojiTextArea ref={ref} placeholder="Type here" />); + expect(ref.current).not.toBeNull(); +}); + +test('renders controlled component with value prop', () => { + render(<EmojiTextArea value="Hello" onChange={() => {}} />); + expect(screen.getByDisplayValue('Hello')).toBeInTheDocument(); +}); + +// ============================================ +// Unit tests for filterEmojis utility function +// ============================================ + +test('filterEmojis returns matching emojis by shortcode', () => { + const results = filterEmojis('smile'); + expect(results.length).toBeGreaterThan(0); + expect(results[0].shortcode).toBe('smile'); +}); + +test('filterEmojis returns matching emojis by partial shortcode', () => { + const results = filterEmojis('sm'); + expect(results.length).toBeGreaterThan(0); + // Should include smile, smirk, etc. + expect(results.some(e => e.shortcode.includes('sm'))).toBe(true); +}); + +test('filterEmojis returns matching emojis by keyword', () => { + const results = filterEmojis('happy'); + expect(results.length).toBeGreaterThan(0); + // Should include emojis with 'happy' keyword + expect(results.some(e => e.keywords?.includes('happy'))).toBe(true); +}); + +test('filterEmojis is case insensitive', () => { + const results1 = filterEmojis('SMILE'); + const results2 = filterEmojis('smile'); + expect(results1.length).toBe(results2.length); + expect(results1[0].shortcode).toBe(results2[0].shortcode); Review Comment: **Suggestion:** Fragile case-insensitivity test: the test compares lengths and the first element of the two result arrays, which can fail if order differs even when the sets are the same. Compare the sets of shortcodes in a deterministic way (sort them) to assert case-insensitive behavior robustly. [possible bug] **Severity Level:** Critical ๐จ ```suggestion // Compare shortcode sets in a deterministic way rather than relying on array order const sc1 = results1.map(r => r.shortcode).sort(); const sc2 = results2.map(r => r.shortcode).sort(); expect(sc1).toEqual(sc2); ``` <details> <summary><b>Why it matters? โญ </b></summary> Comparing lengths and the first element is brittle because arrays can be the same set but in different order. Comparing deterministic sets (e.g., sorted shortcodes) accurately verifies case-insensitivity and is a small, meaningful improvement. </details> <details> <summary><b>Prompt for AI Agent ๐ค </b></summary> ```mdx This is a comment left during a code review. **Path:** superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/EmojiTextArea.test.tsx **Line:** 93:94 **Comment:** *Possible Bug: Fragile case-insensitivity test: the test compares lengths and the first element of the two result arrays, which can fail if order differs even when the sets are the same. Compare the sets of shortcodes in a deterministic way (sort them) to assert case-insensitive behavior robustly. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. ``` </details> ########## superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/EmojiTextArea.stories.tsx: ########## @@ -0,0 +1,331 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useState } from 'react'; Review Comment: **Suggestion:** The file references `React.FormEvent` in the `handleSubmit` parameter but only imports `useState` from 'react' (no `React` namespace). This will cause a TypeScript "React is not defined" type error (or require a separate import) because the `React` identifier is used in a type position without being imported. Import the React namespace alongside `useState` so the `React` type is available. [type error] **Severity Level:** Minor โ ๏ธ ```suggestion import React, { useState } from 'react'; ``` <details> <summary><b>Why it matters? โญ </b></summary> The story uses the `React` namespace in a type position: `handleSubmit = (e: React.FormEvent) => { ... }`. Without importing `React` (or importing the FormEvent type directly), TypeScript will complain that `React` is not defined. This is a real type error, not a style nit. Adding `import React, { useState } from 'react';` or `import type { FormEvent } from 'react'` and adjusting the signature fixes it. </details> <details> <summary><b>Prompt for AI Agent ๐ค </b></summary> ```mdx This is a comment left during a code review. **Path:** superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/EmojiTextArea.stories.tsx **Line:** 19:19 **Comment:** *Type Error: The file references `React.FormEvent` in the `handleSubmit` parameter but only imports `useState` from 'react' (no `React` namespace). This will cause a TypeScript "React is not defined" type error (or require a separate import) because the `React` identifier is used in a type position without being imported. Import the React namespace alongside `useState` so the `React` type is available. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. ``` </details> ########## superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/EmojiTextArea.test.tsx: ########## @@ -0,0 +1,170 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { render, screen, userEvent } from '@superset-ui/core/spec'; +import { EmojiTextArea } from '.'; +import { filterEmojis, EMOJI_DATA } from './emojiData'; + +test('renders EmojiTextArea with placeholder', () => { + render(<EmojiTextArea placeholder="Type something..." />); + expect(screen.getByPlaceholderText('Type something...')).toBeInTheDocument(); +}); + +test('renders EmojiTextArea as textarea element', () => { + render(<EmojiTextArea placeholder="Type here" />); + const textarea = screen.getByPlaceholderText('Type here'); + expect(textarea.tagName.toLowerCase()).toBe('textarea'); +}); + +test('allows typing in the textarea', async () => { + render(<EmojiTextArea placeholder="Type here" />); + const textarea = screen.getByPlaceholderText('Type here'); + await userEvent.type(textarea, 'Hello world'); + expect(textarea).toHaveValue('Hello world'); +}); + +test('calls onChange when typing', async () => { + const onChange = jest.fn(); + render(<EmojiTextArea placeholder="Type here" onChange={onChange} />); + const textarea = screen.getByPlaceholderText('Type here'); + await userEvent.type(textarea, 'Hi'); + expect(onChange).toHaveBeenCalled(); +}); + +test('passes through rows prop', () => { + render(<EmojiTextArea placeholder="Type here" rows={5} />); + const textarea = screen.getByPlaceholderText('Type here'); + expect(textarea).toHaveAttribute('rows', '5'); +}); + +test('forwards ref to underlying component', () => { + const ref = { current: null }; + render(<EmojiTextArea ref={ref} placeholder="Type here" />); + expect(ref.current).not.toBeNull(); +}); + +test('renders controlled component with value prop', () => { + render(<EmojiTextArea value="Hello" onChange={() => {}} />); + expect(screen.getByDisplayValue('Hello')).toBeInTheDocument(); +}); + +// ============================================ +// Unit tests for filterEmojis utility function +// ============================================ + +test('filterEmojis returns matching emojis by shortcode', () => { + const results = filterEmojis('smile'); + expect(results.length).toBeGreaterThan(0); + expect(results[0].shortcode).toBe('smile'); Review Comment: **Suggestion:** Fragile assertion: the test assumes the first returned match for "smile" will always be the exact `smile` shortcode. If ordering changes (or other shortcodes that include "smile" appear earlier), this assertion will be flaky or fail. Replace the strict index-based check with an existence check that the results include an item with shortcode `smile`. [possible bug] **Severity Level:** Critical ๐จ ```suggestion // Ensure the results include the exact 'smile' shortcode (don't rely on ordering) expect(results.some(e => e.shortcode === 'smile')).toBe(true); ``` <details> <summary><b>Why it matters? โญ </b></summary> The current assertion relies on ordering (results[0]) which can be flaky if the filter implementation or emoji ordering changes. The proposed change (asserting that some result has shortcode 'smile') correctly preserves the intent of the test without depending on order, and it fixes a real brittleness in the test. </details> <details> <summary><b>Prompt for AI Agent ๐ค </b></summary> ```mdx This is a comment left during a code review. **Path:** superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/EmojiTextArea.test.tsx **Line:** 73:73 **Comment:** *Possible Bug: Fragile assertion: the test assumes the first returned match for "smile" will always be the exact `smile` shortcode. If ordering changes (or other shortcodes that include "smile" appear earlier), this assertion will be flaky or fail. Replace the strict index-based check with an existence check that the results include an item with shortcode `smile`. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. ``` </details> ########## superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/index.tsx: ########## @@ -0,0 +1,247 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { forwardRef, useCallback, useMemo, useState, useRef } from 'react'; +import { Mentions } from 'antd'; +import type { MentionsRef, MentionsProps } from 'antd/es/mentions'; +import { filterEmojis, type EmojiItem } from './emojiData'; + +const MIN_CHARS_BEFORE_POPUP = 2; + +// Regex to match emoji characters (simplified, covers most common emojis) +const EMOJI_REGEX = + /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]/u; + +export interface EmojiTextAreaProps + extends Omit<MentionsProps, 'prefix' | 'options' | 'onSelect'> { + /** + * Minimum characters after colon before showing popup. + * @default 2 (Slack-like behavior) + */ + minCharsBeforePopup?: number; + /** + * Maximum number of emoji suggestions to show. + * @default 10 + */ + maxSuggestions?: number; + /** + * Called when an emoji is selected from the popup. + */ + onEmojiSelect?: (emoji: EmojiItem) => void; +} + +/** + * A TextArea component with Slack-like emoji autocomplete. + * + * Features: + * - Triggers on `:` prefix (like Slack) + * - Only shows popup after 2+ characters are typed (configurable) + * - Colon must be preceded by a space, start of line, or another emoji + * - Prevents accidental Enter key selection when typing quickly + * + * @example + * ```tsx + * <EmojiTextArea + * placeholder="Type :sm to see emoji suggestions..." + * onChange={(text) => console.log(text)} + * /> + * ``` + */ +export const EmojiTextArea = forwardRef<MentionsRef, EmojiTextAreaProps>( + ( + { + minCharsBeforePopup = MIN_CHARS_BEFORE_POPUP, + maxSuggestions = 10, + onEmojiSelect, + onChange, + onKeyDown, + ...restProps + }, + ref, + ) => { + const [options, setOptions] = useState< + Array<{ value: string; label: React.ReactNode }> + >([]); + const [isPopupVisible, setIsPopupVisible] = useState(false); + const lastSearchRef = useRef<string>(''); + const lastKeyPressTimeRef = useRef<number>(0); + + /** + * Validates whether the colon trigger should activate the popup. + * Implements Slack-like behavior: + * - Colon must be preceded by whitespace, start of text, or emoji + * - At least minCharsBeforePopup characters must be typed after colon + */ + const validateSearch = useCallback( + (text: string, props: MentionsProps): boolean => { + // Get the full value to check what precedes the colon + const fullValue = (props.value as string) || ''; + + // Find where this search text starts in the full value + // The search text is what comes after the `:` prefix + const colonIndex = fullValue.lastIndexOf(`:${text}`); + + if (colonIndex === -1) { + setIsPopupVisible(false); + return false; + } + + // Check what precedes the colon + if (colonIndex > 0) { + const charBefore = fullValue[colonIndex - 1]; + + // Must be preceded by whitespace, newline, or emoji + const isWhitespace = /\s/.test(charBefore); + const isEmoji = EMOJI_REGEX.test(charBefore); + + if (!isWhitespace && !isEmoji) { + setIsPopupVisible(false); + return false; + } + } + + // Check minimum character requirement + if (text.length < minCharsBeforePopup) { + setIsPopupVisible(false); + return false; + } + + setIsPopupVisible(true); + return true; + }, + [minCharsBeforePopup], + ); + + /** + * Handles search and filters emoji suggestions. + */ + const handleSearch = useCallback( + (searchText: string) => { + lastSearchRef.current = searchText; + + if (searchText.length < minCharsBeforePopup) { + setOptions([]); + return; + } + + const filteredEmojis = filterEmojis(searchText, maxSuggestions); + + const newOptions = filteredEmojis.map(item => ({ + value: item.emoji, + label: ( + <span> + <span style={{ marginRight: 8 }}>{item.emoji}</span> + <span style={{ color: 'var(--ant-color-text-secondary)' }}> + :{item.shortcode}: + </span> + </span> + ), + // Store the full item for onSelect callback + data: item, + })); + + setOptions(newOptions); + }, + [minCharsBeforePopup, maxSuggestions], + ); + + /** + * Handles emoji selection from the popup. + */ + const handleSelect = useCallback( + (option: { value: string; data?: EmojiItem }) => { + if (option.data && onEmojiSelect) { + onEmojiSelect(option.data); + } + setIsPopupVisible(false); + }, + [onEmojiSelect], + ); + + /** + * Handles key down events to prevent accidental selection on Enter. + * If the user presses Enter very quickly after typing (< 100ms), + * we treat it as a newline intent rather than selection. + */ + const handleKeyDown = useCallback( + (e: React.KeyboardEvent<HTMLTextAreaElement>) => { + const now = Date.now(); + const timeSinceLastKey = now - lastKeyPressTimeRef.current; + + // If Enter is pressed and popup is visible + if (e.key === 'Enter' && isPopupVisible) { + // If typed very quickly (< 100ms since last keypress) and + // there's meaningful search text, allow the Enter to create newline + // This prevents accidental selection when typing something like: + // "let me show you an example:[Enter]" + if (timeSinceLastKey < 100 && lastSearchRef.current.length === 0) { Review Comment: **Suggestion:** The Enter-key timing check is inverted: the code intends to treat a quick Enter after typing a meaningful emoji search as a newline (not a selection), but it checks for an empty search (length === 0) instead of a non-empty search. This will allow newline only when there's no search text and prevent the intended newline behavior when there actually is search text, causing accidental emoji selection or wrong newline handling. Change the condition to check for a non-empty search string. [logic error] **Severity Level:** Minor โ ๏ธ ```suggestion if (timeSinceLastKey < 100 && lastSearchRef.current.length > 0) { ``` <details> <summary><b>Why it matters? โญ </b></summary> The comment and intent in the surrounding code say we should treat a very quick Enter after typing a meaningful search as a newline (to avoid accidental selection). The current condition checks for length === 0 (empty search), which is the opposite of the described intent. This is a real logic bug that can cause accidental emoji selection or wrong newline handling. Flipping to length > 0 aligns code with the documented intent and fixes the runtime behavior. </details> <details> <summary><b>Prompt for AI Agent ๐ค </b></summary> ```mdx This is a comment left during a code review. **Path:** superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/index.tsx **Line:** 192:192 **Comment:** *Logic Error: The Enter-key timing check is inverted: the code intends to treat a quick Enter after typing a meaningful emoji search as a newline (not a selection), but it checks for an empty search (length === 0) instead of a non-empty search. This will allow newline only when there's no search text and prevent the intended newline behavior when there actually is search text, causing accidental emoji selection or wrong newline handling. Change the condition to check for a non-empty search string. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. ``` </details> ########## superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/index.tsx: ########## @@ -0,0 +1,247 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { forwardRef, useCallback, useMemo, useState, useRef } from 'react'; +import { Mentions } from 'antd'; +import type { MentionsRef, MentionsProps } from 'antd/es/mentions'; +import { filterEmojis, type EmojiItem } from './emojiData'; + +const MIN_CHARS_BEFORE_POPUP = 2; + +// Regex to match emoji characters (simplified, covers most common emojis) +const EMOJI_REGEX = + /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]/u; + +export interface EmojiTextAreaProps + extends Omit<MentionsProps, 'prefix' | 'options' | 'onSelect'> { + /** + * Minimum characters after colon before showing popup. + * @default 2 (Slack-like behavior) + */ + minCharsBeforePopup?: number; + /** + * Maximum number of emoji suggestions to show. + * @default 10 + */ + maxSuggestions?: number; + /** + * Called when an emoji is selected from the popup. + */ + onEmojiSelect?: (emoji: EmojiItem) => void; +} + +/** + * A TextArea component with Slack-like emoji autocomplete. + * + * Features: + * - Triggers on `:` prefix (like Slack) + * - Only shows popup after 2+ characters are typed (configurable) + * - Colon must be preceded by a space, start of line, or another emoji + * - Prevents accidental Enter key selection when typing quickly + * + * @example + * ```tsx + * <EmojiTextArea + * placeholder="Type :sm to see emoji suggestions..." + * onChange={(text) => console.log(text)} + * /> + * ``` + */ +export const EmojiTextArea = forwardRef<MentionsRef, EmojiTextAreaProps>( + ( + { + minCharsBeforePopup = MIN_CHARS_BEFORE_POPUP, + maxSuggestions = 10, + onEmojiSelect, + onChange, + onKeyDown, + ...restProps + }, + ref, + ) => { + const [options, setOptions] = useState< + Array<{ value: string; label: React.ReactNode }> Review Comment: **Suggestion:** The `options` state is typed as objects with only `value` and `label`, but the code stores a `data` property on each option for later retrieval. This type mismatch can cause type-checking issues and makes the intended shape unclear; extend the state type to include an optional `data?: EmojiItem` property so the stored emoji item is typed and available. [type error] **Severity Level:** Minor โ ๏ธ ```suggestion Array<{ value: string; label: React.ReactNode; data?: EmojiItem }> ``` <details> <summary><b>Why it matters? โญ </b></summary> The code stores a `data` field on each option object (used on select), but the useState type only declares value and label. That mismatch can cause TypeScript excess-property or type confusion. Extending the state type to include an optional `data?: EmojiItem` accurately models the stored shape and prevents type errors while making intent explicit. </details> <details> <summary><b>Prompt for AI Agent ๐ค </b></summary> ```mdx This is a comment left during a code review. **Path:** superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/index.tsx **Line:** 78:78 **Comment:** *Type Error: The `options` state is typed as objects with only `value` and `label`, but the code stores a `data` property on each option for later retrieval. This type mismatch can cause type-checking issues and makes the intended shape unclear; extend the state type to include an optional `data?: EmojiItem` property so the stored emoji item is typed and available. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. ``` </details> ########## superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/emojiData.ts: ########## @@ -0,0 +1,569 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface EmojiItem { + shortcode: string; + emoji: string; + keywords?: string[]; +} + +/** + * Common emoji data with shortcodes. + * This is a curated subset of emojis commonly used in Slack-like applications. + * Can be extended or replaced with a more comprehensive emoji library. + */ +export const EMOJI_DATA: EmojiItem[] = [ + // Smileys & Emotion + { shortcode: 'smile', emoji: '๐', keywords: ['happy', 'joy', 'glad'] }, + { shortcode: 'smiley', emoji: '๐', keywords: ['happy', 'joy'] }, + { shortcode: 'grinning', emoji: '๐', keywords: ['happy', 'smile'] }, + { shortcode: 'blush', emoji: '๐', keywords: ['happy', 'shy', 'smile'] }, + { shortcode: 'wink', emoji: '๐', keywords: ['flirt'] }, + { + shortcode: 'heart_eyes', + emoji: '๐', + keywords: ['love', 'crush', 'adore'], + }, + { shortcode: 'kissing_heart', emoji: '๐', keywords: ['love', 'kiss'] }, + { shortcode: 'laughing', emoji: '๐', keywords: ['happy', 'haha', 'lol'] }, + { shortcode: 'sweat_smile', emoji: '๐ ', keywords: ['nervous', 'phew'] }, + { shortcode: 'joy', emoji: '๐', keywords: ['tears', 'laugh', 'lol', 'lmao'] }, + { + shortcode: 'rofl', + emoji: '๐คฃ', + keywords: ['rolling', 'laugh', 'lol', 'lmao'], + }, + { shortcode: 'relaxed', emoji: 'โบ๏ธ', keywords: ['calm', 'peace'] }, + { shortcode: 'yum', emoji: '๐', keywords: ['tasty', 'delicious'] }, + { shortcode: 'relieved', emoji: '๐', keywords: ['calm', 'peaceful'] }, + { shortcode: 'sunglasses', emoji: '๐', keywords: ['cool', 'awesome'] }, + { shortcode: 'smirk', emoji: '๐', keywords: ['sly', 'confident'] }, + { shortcode: 'neutral_face', emoji: '๐', keywords: ['meh', 'blank'] }, + { shortcode: 'expressionless', emoji: '๐', keywords: ['blank', 'meh'] }, + { shortcode: 'unamused', emoji: '๐', keywords: ['bored', 'meh'] }, + { shortcode: 'sweat', emoji: '๐', keywords: ['nervous', 'worried'] }, + { shortcode: 'pensive', emoji: '๐', keywords: ['sad', 'thoughtful'] }, + { shortcode: 'confused', emoji: '๐', keywords: ['puzzled', 'unsure'] }, + { shortcode: 'upside_down', emoji: '๐', keywords: ['silly', 'sarcasm'] }, + { shortcode: 'thinking', emoji: '๐ค', keywords: ['ponder', 'hmm'] }, + { shortcode: 'zipper_mouth', emoji: '๐ค', keywords: ['secret', 'quiet'] }, + { shortcode: 'raised_eyebrow', emoji: '๐คจ', keywords: ['skeptical', 'doubt'] }, + { shortcode: 'rolling_eyes', emoji: '๐', keywords: ['annoyed', 'whatever'] }, + { shortcode: 'grimacing', emoji: '๐ฌ', keywords: ['awkward', 'nervous'] }, + { shortcode: 'lying_face', emoji: '๐คฅ', keywords: ['liar', 'pinocchio'] }, + { shortcode: 'shushing', emoji: '๐คซ', keywords: ['quiet', 'secret'] }, + { shortcode: 'hand_over_mouth', emoji: '๐คญ', keywords: ['oops', 'giggle'] }, + { shortcode: 'face_vomiting', emoji: '๐คฎ', keywords: ['sick', 'gross'] }, + { shortcode: 'exploding_head', emoji: '๐คฏ', keywords: ['mind', 'blown'] }, + { shortcode: 'cowboy', emoji: '๐ค ', keywords: ['western', 'yeehaw'] }, + { shortcode: 'partying', emoji: '๐ฅณ', keywords: ['party', 'celebration'] }, + { shortcode: 'star_struck', emoji: '๐คฉ', keywords: ['excited', 'amazed'] }, + { shortcode: 'sleeping', emoji: '๐ด', keywords: ['zzz', 'tired'] }, + { shortcode: 'drooling', emoji: '๐คค', keywords: ['hungry', 'want'] }, + { shortcode: 'sleepy', emoji: '๐ช', keywords: ['tired', 'zzz'] }, + { shortcode: 'mask', emoji: '๐ท', keywords: ['sick', 'covid'] }, + { shortcode: 'nerd', emoji: '๐ค', keywords: ['geek', 'smart'] }, + { shortcode: 'monocle', emoji: '๐ง', keywords: ['curious', 'inspect'] }, + { shortcode: 'worried', emoji: '๐', keywords: ['concerned', 'anxious'] }, + { shortcode: 'frowning', emoji: '๐', keywords: ['sad', 'unhappy'] }, + { shortcode: 'open_mouth', emoji: '๐ฎ', keywords: ['surprised', 'wow'] }, + { shortcode: 'hushed', emoji: '๐ฏ', keywords: ['surprised', 'quiet'] }, + { shortcode: 'astonished', emoji: '๐ฒ', keywords: ['shocked', 'wow'] }, + { shortcode: 'flushed', emoji: '๐ณ', keywords: ['embarrassed', 'shy'] }, + { shortcode: 'pleading', emoji: '๐ฅบ', keywords: ['puppy', 'please'] }, + { shortcode: 'cry', emoji: '๐ข', keywords: ['sad', 'tear'] }, + { shortcode: 'sob', emoji: '๐ญ', keywords: ['crying', 'sad', 'tears'] }, + { shortcode: 'scream', emoji: '๐ฑ', keywords: ['scared', 'horror'] }, + { shortcode: 'confounded', emoji: '๐', keywords: ['frustrated'] }, + { shortcode: 'persevere', emoji: '๐ฃ', keywords: ['struggling'] }, + { shortcode: 'disappointed', emoji: '๐', keywords: ['sad', 'let down'] }, + { shortcode: 'fearful', emoji: '๐จ', keywords: ['scared', 'afraid'] }, + { shortcode: 'cold_sweat', emoji: '๐ฐ', keywords: ['nervous', 'anxious'] }, + { shortcode: 'weary', emoji: '๐ฉ', keywords: ['tired', 'exhausted'] }, + { shortcode: 'tired_face', emoji: '๐ซ', keywords: ['exhausted'] }, + { shortcode: 'angry', emoji: '๐ ', keywords: ['mad', 'grumpy'] }, + { shortcode: 'rage', emoji: '๐ก', keywords: ['angry', 'furious'] }, + { shortcode: 'triumph', emoji: '๐ค', keywords: ['proud', 'huffing'] }, + { shortcode: 'skull', emoji: '๐', keywords: ['dead', 'death'] }, + { shortcode: 'poop', emoji: '๐ฉ', keywords: ['crap', 'shit'] }, + { shortcode: 'clown', emoji: '๐คก', keywords: ['funny', 'circus'] }, + { shortcode: 'imp', emoji: '๐ฟ', keywords: ['devil', 'evil'] }, + { shortcode: 'ghost', emoji: '๐ป', keywords: ['boo', 'spooky'] }, + { shortcode: 'alien', emoji: '๐ฝ', keywords: ['ufo', 'space'] }, + { shortcode: 'robot', emoji: '๐ค', keywords: ['bot', 'machine'] }, + { shortcode: 'cat', emoji: '๐บ', keywords: ['kitty', 'meow'] }, + { shortcode: 'heart_eyes_cat', emoji: '๐ป', keywords: ['love', 'cat'] }, + { shortcode: 'joy_cat', emoji: '๐น', keywords: ['laugh', 'cat'] }, + { shortcode: 'crying_cat', emoji: '๐ฟ', keywords: ['sad', 'cat'] }, + { shortcode: 'pouting_cat', emoji: '๐พ', keywords: ['angry', 'cat'] }, + { shortcode: 'see_no_evil', emoji: '๐', keywords: ['monkey', 'shy'] }, + { shortcode: 'hear_no_evil', emoji: '๐', keywords: ['monkey'] }, + { shortcode: 'speak_no_evil', emoji: '๐', keywords: ['monkey', 'secret'] }, + + // Gestures & Body + { shortcode: 'wave', emoji: '๐', keywords: ['hello', 'bye', 'hi'] }, + { shortcode: 'raised_hand', emoji: 'โ', keywords: ['stop', 'high five'] }, + { shortcode: 'ok_hand', emoji: '๐', keywords: ['perfect', 'nice'] }, + { shortcode: 'pinching_hand', emoji: '๐ค', keywords: ['small', 'tiny'] }, + { shortcode: 'v', emoji: 'โ๏ธ', keywords: ['peace', 'victory'] }, + { shortcode: 'crossed_fingers', emoji: '๐ค', keywords: ['luck', 'hope'] }, + { shortcode: 'love_you', emoji: '๐ค', keywords: ['ily', 'sign'] }, + { shortcode: 'metal', emoji: '๐ค', keywords: ['rock', 'horns'] }, + { shortcode: 'call_me', emoji: '๐ค', keywords: ['phone', 'shaka'] }, + { shortcode: 'point_left', emoji: '๐', keywords: ['direction'] }, + { shortcode: 'point_right', emoji: '๐', keywords: ['direction'] }, + { shortcode: 'point_up', emoji: '๐', keywords: ['direction'] }, + { shortcode: 'point_down', emoji: '๐', keywords: ['direction'] }, + { shortcode: 'middle_finger', emoji: '๐', keywords: ['flip', 'rude'] }, + { shortcode: 'thumbsup', emoji: '๐', keywords: ['yes', 'good', '+1'] }, + { shortcode: 'thumbsdown', emoji: '๐', keywords: ['no', 'bad', '-1'] }, + { shortcode: 'fist', emoji: 'โ', keywords: ['power', 'punch'] }, + { shortcode: 'punch', emoji: '๐', keywords: ['fist', 'bump'] }, + { shortcode: 'clap', emoji: '๐', keywords: ['applause', 'bravo'] }, + { shortcode: 'raised_hands', emoji: '๐', keywords: ['celebration', 'yay'] }, + { shortcode: 'open_hands', emoji: '๐', keywords: ['hug', 'open'] }, + { shortcode: 'palms_up', emoji: '๐คฒ', keywords: ['prayer', 'request'] }, + { shortcode: 'handshake', emoji: '๐ค', keywords: ['deal', 'agreement'] }, + { shortcode: 'pray', emoji: '๐', keywords: ['please', 'thanks', 'namaste'] }, + { shortcode: 'writing', emoji: 'โ๏ธ', keywords: ['write', 'pen'] }, + { shortcode: 'nail_care', emoji: '๐ ', keywords: ['nails', 'fabulous'] }, + { shortcode: 'selfie', emoji: '๐คณ', keywords: ['photo', 'camera'] }, + { shortcode: 'muscle', emoji: '๐ช', keywords: ['strong', 'flex', 'bicep'] }, + { shortcode: 'leg', emoji: '๐ฆต', keywords: ['kick'] }, + { shortcode: 'foot', emoji: '๐ฆถ', keywords: ['kick', 'step'] }, + { shortcode: 'ear', emoji: '๐', keywords: ['listen', 'hear'] }, + { shortcode: 'nose', emoji: '๐', keywords: ['smell', 'sniff'] }, + { shortcode: 'brain', emoji: '๐ง ', keywords: ['think', 'smart'] }, + { shortcode: 'eyes', emoji: '๐', keywords: ['look', 'see', 'watch'] }, + { shortcode: 'eye', emoji: '๐๏ธ', keywords: ['look', 'see'] }, + { shortcode: 'tongue', emoji: '๐ ', keywords: ['taste', 'lick'] }, + { shortcode: 'lips', emoji: '๐', keywords: ['mouth', 'kiss'] }, + { shortcode: 'baby', emoji: '๐ถ', keywords: ['child', 'infant'] }, + { shortcode: 'person', emoji: '๐ง', keywords: ['human', 'adult'] }, + { shortcode: 'man', emoji: '๐จ', keywords: ['male', 'guy'] }, + { shortcode: 'woman', emoji: '๐ฉ', keywords: ['female', 'lady'] }, + { shortcode: 'older_person', emoji: '๐ง', keywords: ['senior', 'elderly'] }, + + // Hearts & Love + { shortcode: 'heart', emoji: 'โค๏ธ', keywords: ['love', 'red'] }, + { shortcode: 'orange_heart', emoji: '๐งก', keywords: ['love'] }, + { shortcode: 'yellow_heart', emoji: '๐', keywords: ['love'] }, + { shortcode: 'green_heart', emoji: '๐', keywords: ['love'] }, + { shortcode: 'blue_heart', emoji: '๐', keywords: ['love'] }, + { shortcode: 'purple_heart', emoji: '๐', keywords: ['love'] }, + { shortcode: 'black_heart', emoji: '๐ค', keywords: ['love', 'dark'] }, + { shortcode: 'white_heart', emoji: '๐ค', keywords: ['love', 'pure'] }, + { shortcode: 'brown_heart', emoji: '๐ค', keywords: ['love'] }, + { shortcode: 'broken_heart', emoji: '๐', keywords: ['sad', 'heartbreak'] }, + { shortcode: 'heartbeat', emoji: '๐', keywords: ['love', 'pulse'] }, + { shortcode: 'heartpulse', emoji: '๐', keywords: ['love', 'growing'] }, + { shortcode: 'two_hearts', emoji: '๐', keywords: ['love', 'romance'] }, + { shortcode: 'revolving_hearts', emoji: '๐', keywords: ['love'] }, + { shortcode: 'cupid', emoji: '๐', keywords: ['love', 'arrow'] }, + { shortcode: 'sparkling_heart', emoji: '๐', keywords: ['love', 'sparkle'] }, + { shortcode: 'gift_heart', emoji: '๐', keywords: ['love', 'valentine'] }, + { shortcode: 'heart_decoration', emoji: '๐', keywords: ['love'] }, + { shortcode: 'kiss', emoji: '๐', keywords: ['love', 'lips'] }, + { shortcode: 'love_letter', emoji: '๐', keywords: ['email', 'message'] }, + + // Symbols & Objects + { shortcode: 'fire', emoji: '๐ฅ', keywords: ['hot', 'lit', 'flame'] }, + { shortcode: 'star', emoji: 'โญ', keywords: ['favorite', 'rating'] }, + { shortcode: 'sparkles', emoji: 'โจ', keywords: ['shiny', 'new', 'magic'] }, + { shortcode: 'zap', emoji: 'โก', keywords: ['lightning', 'power'] }, + { shortcode: 'boom', emoji: '๐ฅ', keywords: ['explosion', 'collision'] }, + { shortcode: 'dizzy', emoji: '๐ซ', keywords: ['star', 'dazed'] }, + { shortcode: 'speech_balloon', emoji: '๐ฌ', keywords: ['talk', 'chat'] }, + { shortcode: 'thought_balloon', emoji: '๐ญ', keywords: ['think', 'idea'] }, + { shortcode: 'zzz', emoji: '๐ค', keywords: ['sleep', 'tired'] }, + { shortcode: 'wave_emoji', emoji: '๐', keywords: ['ocean', 'water'] }, + { shortcode: 'droplet', emoji: '๐ง', keywords: ['water', 'sweat'] }, + { shortcode: 'sweat_drops', emoji: '๐ฆ', keywords: ['water', 'splash'] }, + { shortcode: 'dash', emoji: '๐จ', keywords: ['wind', 'running'] }, + { shortcode: 'hole', emoji: '๐ณ๏ธ', keywords: ['empty', 'void'] }, + { shortcode: 'bomb', emoji: '๐ฃ', keywords: ['explosive', 'danger'] }, + { shortcode: 'money', emoji: '๐ฐ', keywords: ['bag', 'cash', 'dollar'] }, + { shortcode: 'dollar', emoji: '๐ต', keywords: ['money', 'cash'] }, + { shortcode: 'gem', emoji: '๐', keywords: ['diamond', 'jewel'] }, + { shortcode: 'bulb', emoji: '๐ก', keywords: ['idea', 'light'] }, + { shortcode: 'bell', emoji: '๐', keywords: ['notification', 'alert'] }, + { shortcode: 'loudspeaker', emoji: '๐ข', keywords: ['announce'] }, + { shortcode: 'mega', emoji: '๐ฃ', keywords: ['megaphone', 'announce'] }, + { shortcode: 'lock', emoji: '๐', keywords: ['secure', 'closed'] }, + { shortcode: 'unlock', emoji: '๐', keywords: ['open', 'access'] }, + { shortcode: 'key', emoji: '๐', keywords: ['password', 'access'] }, + { shortcode: 'magnifying_glass', emoji: '๐', keywords: ['search', 'find'] }, + { shortcode: 'link', emoji: '๐', keywords: ['chain', 'url'] }, + { shortcode: 'paperclip', emoji: '๐', keywords: ['attach'] }, + { shortcode: 'scissors', emoji: 'โ๏ธ', keywords: ['cut', 'snip'] }, + { shortcode: 'hammer', emoji: '๐จ', keywords: ['tool', 'build'] }, + { shortcode: 'wrench', emoji: '๐ง', keywords: ['tool', 'fix'] }, + { shortcode: 'gear', emoji: 'โ๏ธ', keywords: ['settings', 'cog'] }, + { shortcode: 'shield', emoji: '๐ก๏ธ', keywords: ['protect', 'security'] }, + { shortcode: 'trophy', emoji: '๐', keywords: ['win', 'first', 'award'] }, + { shortcode: 'medal', emoji: '๐ ', keywords: ['award', 'sports'] }, + { shortcode: 'first_place', emoji: '๐ฅ', keywords: ['gold', 'winner'] }, + { shortcode: 'second_place', emoji: '๐ฅ', keywords: ['silver'] }, + { shortcode: 'third_place', emoji: '๐ฅ', keywords: ['bronze'] }, + { shortcode: 'soccer', emoji: 'โฝ', keywords: ['football', 'sports'] }, + { shortcode: 'basketball', emoji: '๐', keywords: ['sports', 'ball'] }, + { shortcode: 'football', emoji: '๐', keywords: ['sports', 'american'] }, + { shortcode: 'baseball', emoji: 'โพ', keywords: ['sports', 'ball'] }, + { shortcode: 'tennis', emoji: '๐พ', keywords: ['sports', 'ball'] }, + { shortcode: 'dart', emoji: '๐ฏ', keywords: ['target', 'bullseye'] }, + { shortcode: 'video_game', emoji: '๐ฎ', keywords: ['gaming', 'controller'] }, + { shortcode: 'slot_machine', emoji: '๐ฐ', keywords: ['gambling', 'casino'] }, + { shortcode: 'game_die', emoji: '๐ฒ', keywords: ['dice', 'random'] }, + { shortcode: 'jigsaw', emoji: '๐งฉ', keywords: ['puzzle', 'piece'] }, + { shortcode: 'art', emoji: '๐จ', keywords: ['palette', 'paint'] }, + { shortcode: 'performing_arts', emoji: '๐ญ', keywords: ['theater', 'drama'] }, + { shortcode: 'microphone', emoji: '๐ค', keywords: ['sing', 'karaoke'] }, + { shortcode: 'headphones', emoji: '๐ง', keywords: ['music', 'audio'] }, + { shortcode: 'musical_note', emoji: '๐ต', keywords: ['music', 'song'] }, + { shortcode: 'notes', emoji: '๐ถ', keywords: ['music', 'melody'] }, + { shortcode: 'guitar', emoji: '๐ธ', keywords: ['music', 'rock'] }, + { shortcode: 'piano', emoji: '๐น', keywords: ['music', 'keys'] }, + { shortcode: 'drum', emoji: '๐ฅ', keywords: ['music', 'beat'] }, + { shortcode: 'trumpet', emoji: '๐บ', keywords: ['music', 'brass'] }, + { shortcode: 'violin', emoji: '๐ป', keywords: ['music', 'string'] }, + { shortcode: 'movie_camera', emoji: '๐ฅ', keywords: ['film', 'video'] }, + { shortcode: 'camera', emoji: '๐ท', keywords: ['photo', 'picture'] }, + { shortcode: 'tv', emoji: '๐บ', keywords: ['television', 'watch'] }, + { shortcode: 'computer', emoji: '๐ป', keywords: ['laptop', 'pc'] }, + { shortcode: 'keyboard', emoji: 'โจ๏ธ', keywords: ['type', 'computer'] }, + { shortcode: 'phone', emoji: '๐ฑ', keywords: ['mobile', 'cell'] }, + { shortcode: 'email', emoji: '๐ง', keywords: ['mail', 'message'] }, + { shortcode: 'inbox', emoji: '๐ฅ', keywords: ['mail', 'receive'] }, + { shortcode: 'outbox', emoji: '๐ค', keywords: ['mail', 'send'] }, + { shortcode: 'package', emoji: '๐ฆ', keywords: ['box', 'delivery'] }, + { shortcode: 'memo', emoji: '๐', keywords: ['note', 'write'] }, + { shortcode: 'page', emoji: '๐', keywords: ['document', 'file'] }, + { shortcode: 'bookmark', emoji: '๐', keywords: ['save', 'tag'] }, + { shortcode: 'book', emoji: '๐', keywords: ['read', 'open'] }, + { shortcode: 'books', emoji: '๐', keywords: ['library', 'study'] }, + { shortcode: 'newspaper', emoji: '๐ฐ', keywords: ['news', 'article'] }, + { shortcode: 'calendar', emoji: '๐ ', keywords: ['date', 'schedule'] }, + { shortcode: 'chart', emoji: '๐', keywords: ['graph', 'increase'] }, + { shortcode: 'chart_down', emoji: '๐', keywords: ['graph', 'decrease'] }, + { shortcode: 'bar_chart', emoji: '๐', keywords: ['graph', 'stats'] }, + { shortcode: 'clipboard', emoji: '๐', keywords: ['list', 'todo'] }, + { shortcode: 'pushpin', emoji: '๐', keywords: ['pin', 'location'] }, + { shortcode: 'round_pushpin', emoji: '๐', keywords: ['pin', 'location'] }, + { shortcode: 'triangular_ruler', emoji: '๐', keywords: ['math', 'measure'] }, + { shortcode: 'straight_ruler', emoji: '๐', keywords: ['math', 'measure'] }, + { shortcode: 'pencil', emoji: 'โ๏ธ', keywords: ['write', 'draw'] }, + { shortcode: 'pen', emoji: '๐๏ธ', keywords: ['write', 'sign'] }, + { shortcode: 'crayon', emoji: '๐๏ธ', keywords: ['draw', 'color'] }, + { shortcode: 'paintbrush', emoji: '๐๏ธ', keywords: ['art', 'paint'] }, + { shortcode: 'folder', emoji: '๐', keywords: ['file', 'directory'] }, + { shortcode: 'open_folder', emoji: '๐', keywords: ['file', 'directory'] }, + + // Nature & Animals + { shortcode: 'dog', emoji: '๐ถ', keywords: ['puppy', 'pet', 'woof'] }, + { shortcode: 'cat_face', emoji: '๐ฑ', keywords: ['kitty', 'pet', 'meow'] }, + { shortcode: 'mouse', emoji: '๐ญ', keywords: ['rodent'] }, + { shortcode: 'hamster', emoji: '๐น', keywords: ['pet', 'rodent'] }, + { shortcode: 'rabbit', emoji: '๐ฐ', keywords: ['bunny', 'pet'] }, + { shortcode: 'fox', emoji: '๐ฆ', keywords: ['animal'] }, + { shortcode: 'bear', emoji: '๐ป', keywords: ['animal'] }, + { shortcode: 'panda', emoji: '๐ผ', keywords: ['animal', 'cute'] }, + { shortcode: 'koala', emoji: '๐จ', keywords: ['animal', 'australia'] }, + { shortcode: 'tiger', emoji: '๐ฏ', keywords: ['animal', 'cat'] }, + { shortcode: 'lion', emoji: '๐ฆ', keywords: ['animal', 'king'] }, + { shortcode: 'cow', emoji: '๐ฎ', keywords: ['animal', 'farm'] }, + { shortcode: 'pig', emoji: '๐ท', keywords: ['animal', 'farm'] }, + { shortcode: 'frog', emoji: '๐ธ', keywords: ['animal', 'toad'] }, + { shortcode: 'monkey_face', emoji: '๐ต', keywords: ['animal', 'ape'] }, + { shortcode: 'chicken', emoji: '๐', keywords: ['animal', 'farm', 'hen'] }, + { shortcode: 'penguin', emoji: '๐ง', keywords: ['animal', 'bird'] }, + { shortcode: 'bird', emoji: '๐ฆ', keywords: ['animal', 'fly'] }, + { shortcode: 'eagle', emoji: '๐ฆ ', keywords: ['animal', 'bird'] }, + { shortcode: 'duck', emoji: '๐ฆ', keywords: ['animal', 'bird', 'quack'] }, + { shortcode: 'owl', emoji: '๐ฆ', keywords: ['animal', 'bird', 'night'] }, + { shortcode: 'bat', emoji: '๐ฆ', keywords: ['animal', 'night', 'vampire'] }, + { shortcode: 'wolf', emoji: '๐บ', keywords: ['animal'] }, + { shortcode: 'horse', emoji: '๐ด', keywords: ['animal'] }, + { shortcode: 'unicorn', emoji: '๐ฆ', keywords: ['animal', 'magic'] }, + { shortcode: 'bee', emoji: '๐', keywords: ['insect', 'honey'] }, + { shortcode: 'bug', emoji: '๐', keywords: ['insect', 'caterpillar'] }, + { shortcode: 'butterfly', emoji: '๐ฆ', keywords: ['insect', 'pretty'] }, + { shortcode: 'snail', emoji: '๐', keywords: ['slow'] }, + { shortcode: 'lady_beetle', emoji: '๐', keywords: ['insect', 'bug'] }, + { shortcode: 'ant', emoji: '๐', keywords: ['insect', 'bug'] }, + { shortcode: 'spider', emoji: '๐ท๏ธ', keywords: ['insect', 'scary'] }, + { shortcode: 'turtle', emoji: '๐ข', keywords: ['animal', 'slow'] }, + { shortcode: 'snake', emoji: '๐', keywords: ['animal', 'reptile'] }, + { shortcode: 'dragon', emoji: '๐ฒ', keywords: ['animal', 'mythical'] }, + { shortcode: 'dinosaur', emoji: '๐ฆ', keywords: ['animal', 'extinct'] }, + { shortcode: 't_rex', emoji: '๐ฆ', keywords: ['animal', 'dinosaur'] }, + { shortcode: 'whale', emoji: '๐ณ', keywords: ['animal', 'ocean'] }, + { shortcode: 'dolphin', emoji: '๐ฌ', keywords: ['animal', 'ocean'] }, + { shortcode: 'fish', emoji: '๐', keywords: ['animal', 'ocean'] }, + { shortcode: 'tropical_fish', emoji: '๐ ', keywords: ['animal', 'ocean'] }, + { shortcode: 'shark', emoji: '๐ฆ', keywords: ['animal', 'ocean'] }, + { shortcode: 'octopus', emoji: '๐', keywords: ['animal', 'ocean'] }, + { shortcode: 'crab', emoji: '๐ฆ', keywords: ['animal', 'ocean'] }, + { shortcode: 'lobster', emoji: '๐ฆ', keywords: ['animal', 'ocean'] }, + { shortcode: 'shrimp', emoji: '๐ฆ', keywords: ['animal', 'ocean'] }, + + // Plants & Nature + { shortcode: 'bouquet', emoji: '๐', keywords: ['flowers', 'gift'] }, + { shortcode: 'cherry_blossom', emoji: '๐ธ', keywords: ['flower', 'spring'] }, + { shortcode: 'rose', emoji: '๐น', keywords: ['flower', 'love'] }, + { shortcode: 'tulip', emoji: '๐ท', keywords: ['flower', 'spring'] }, + { shortcode: 'sunflower', emoji: '๐ป', keywords: ['flower', 'summer'] }, + { shortcode: 'hibiscus', emoji: '๐บ', keywords: ['flower', 'tropical'] }, + { shortcode: 'seedling', emoji: '๐ฑ', keywords: ['plant', 'grow'] }, + { shortcode: 'evergreen_tree', emoji: '๐ฒ', keywords: ['tree', 'pine'] }, + { shortcode: 'deciduous_tree', emoji: '๐ณ', keywords: ['tree'] }, + { shortcode: 'palm_tree', emoji: '๐ด', keywords: ['tree', 'tropical'] }, + { shortcode: 'cactus', emoji: '๐ต', keywords: ['plant', 'desert'] }, + { shortcode: 'herb', emoji: '๐ฟ', keywords: ['plant', 'leaf'] }, + { shortcode: 'shamrock', emoji: 'โ๏ธ', keywords: ['clover', 'irish'] }, + { shortcode: 'four_leaf_clover', emoji: '๐', keywords: ['luck', 'irish'] }, + { shortcode: 'maple_leaf', emoji: '๐', keywords: ['fall', 'autumn'] }, + { shortcode: 'fallen_leaf', emoji: '๐', keywords: ['fall', 'autumn'] }, + { shortcode: 'leaves', emoji: '๐', keywords: ['leaf', 'wind'] }, + { shortcode: 'mushroom', emoji: '๐', keywords: ['fungus'] }, + + // Food & Drink + { shortcode: 'apple', emoji: '๐', keywords: ['fruit', 'red'] }, + { shortcode: 'green_apple', emoji: '๐', keywords: ['fruit'] }, + { shortcode: 'pear', emoji: '๐', keywords: ['fruit'] }, + { shortcode: 'orange', emoji: '๐', keywords: ['fruit', 'citrus'] }, + { shortcode: 'lemon', emoji: '๐', keywords: ['fruit', 'citrus'] }, + { shortcode: 'banana', emoji: '๐', keywords: ['fruit'] }, + { shortcode: 'watermelon', emoji: '๐', keywords: ['fruit', 'summer'] }, + { shortcode: 'grapes', emoji: '๐', keywords: ['fruit', 'wine'] }, + { shortcode: 'strawberry', emoji: '๐', keywords: ['fruit', 'berry'] }, + { shortcode: 'cherries', emoji: '๐', keywords: ['fruit'] }, + { shortcode: 'peach', emoji: '๐', keywords: ['fruit'] }, + { shortcode: 'mango', emoji: '๐ฅญ', keywords: ['fruit', 'tropical'] }, + { shortcode: 'pineapple', emoji: '๐', keywords: ['fruit', 'tropical'] }, + { shortcode: 'coconut', emoji: '๐ฅฅ', keywords: ['fruit', 'tropical'] }, + { shortcode: 'avocado', emoji: '๐ฅ', keywords: ['fruit', 'guacamole'] }, + { shortcode: 'tomato', emoji: '๐ ', keywords: ['vegetable', 'red'] }, + { shortcode: 'eggplant', emoji: '๐', keywords: ['vegetable', 'purple'] }, + { shortcode: 'potato', emoji: '๐ฅ', keywords: ['vegetable', 'spud'] }, + { shortcode: 'carrot', emoji: '๐ฅ', keywords: ['vegetable', 'orange'] }, + { shortcode: 'corn', emoji: '๐ฝ', keywords: ['vegetable', 'maize'] }, + { shortcode: 'hot_pepper', emoji: '๐ถ๏ธ', keywords: ['spicy', 'chili'] }, + { shortcode: 'broccoli', emoji: '๐ฅฆ', keywords: ['vegetable', 'green'] }, + { shortcode: 'bread', emoji: '๐', keywords: ['food', 'toast'] }, + { shortcode: 'croissant', emoji: '๐ฅ', keywords: ['food', 'french'] }, + { shortcode: 'pretzel', emoji: '๐ฅจ', keywords: ['food', 'snack'] }, + { shortcode: 'bagel', emoji: '๐ฅฏ', keywords: ['food', 'breakfast'] }, + { shortcode: 'cheese', emoji: '๐ง', keywords: ['food', 'dairy'] }, + { shortcode: 'egg', emoji: '๐ฅ', keywords: ['food', 'breakfast'] }, + { shortcode: 'bacon', emoji: '๐ฅ', keywords: ['food', 'breakfast'] }, + { shortcode: 'pancakes', emoji: '๐ฅ', keywords: ['food', 'breakfast'] }, + { shortcode: 'waffle', emoji: '๐ง', keywords: ['food', 'breakfast'] }, + { shortcode: 'steak', emoji: '๐ฅฉ', keywords: ['food', 'meat'] }, + { shortcode: 'poultry_leg', emoji: '๐', keywords: ['food', 'chicken'] }, + { shortcode: 'hamburger', emoji: '๐', keywords: ['food', 'burger'] }, + { shortcode: 'fries', emoji: '๐', keywords: ['food', 'fast'] }, + { shortcode: 'pizza', emoji: '๐', keywords: ['food', 'italian'] }, + { shortcode: 'hot_dog', emoji: '๐ญ', keywords: ['food', 'fast'] }, + { shortcode: 'sandwich', emoji: '๐ฅช', keywords: ['food', 'lunch'] }, + { shortcode: 'taco', emoji: '๐ฎ', keywords: ['food', 'mexican'] }, + { shortcode: 'burrito', emoji: '๐ฏ', keywords: ['food', 'mexican'] }, + { shortcode: 'sushi', emoji: '๐ฃ', keywords: ['food', 'japanese'] }, + { shortcode: 'ramen', emoji: '๐', keywords: ['food', 'noodles'] }, + { shortcode: 'spaghetti', emoji: '๐', keywords: ['food', 'pasta'] }, + { shortcode: 'curry', emoji: '๐', keywords: ['food', 'rice'] }, + { shortcode: 'rice', emoji: '๐', keywords: ['food', 'white'] }, + { shortcode: 'salad', emoji: '๐ฅ', keywords: ['food', 'healthy'] }, + { shortcode: 'popcorn', emoji: '๐ฟ', keywords: ['food', 'movie'] }, + { shortcode: 'cake', emoji: '๐', keywords: ['food', 'birthday'] }, + { shortcode: 'cupcake', emoji: '๐ง', keywords: ['food', 'sweet'] }, + { shortcode: 'pie', emoji: '๐ฅง', keywords: ['food', 'dessert'] }, + { shortcode: 'cookie', emoji: '๐ช', keywords: ['food', 'sweet'] }, + { shortcode: 'chocolate', emoji: '๐ซ', keywords: ['food', 'sweet'] }, + { shortcode: 'candy', emoji: '๐ฌ', keywords: ['food', 'sweet'] }, + { shortcode: 'lollipop', emoji: '๐ญ', keywords: ['food', 'sweet'] }, + { shortcode: 'donut', emoji: '๐ฉ', keywords: ['food', 'sweet'] }, + { shortcode: 'ice_cream', emoji: '๐จ', keywords: ['food', 'dessert'] }, + { shortcode: 'icecream', emoji: '๐ฆ', keywords: ['food', 'dessert', 'cone'] }, + { shortcode: 'coffee', emoji: 'โ', keywords: ['drink', 'caffeine'] }, + { shortcode: 'tea', emoji: '๐ต', keywords: ['drink', 'green'] }, + { shortcode: 'beer', emoji: '๐บ', keywords: ['drink', 'alcohol'] }, + { shortcode: 'beers', emoji: '๐ป', keywords: ['drink', 'cheers'] }, + { shortcode: 'wine_glass', emoji: '๐ท', keywords: ['drink', 'alcohol'] }, + { shortcode: 'cocktail', emoji: '๐ธ', keywords: ['drink', 'alcohol'] }, + { shortcode: 'tropical_drink', emoji: '๐น', keywords: ['drink', 'vacation'] }, + { shortcode: 'champagne', emoji: '๐พ', keywords: ['drink', 'celebrate'] }, + { shortcode: 'milk', emoji: '๐ฅ', keywords: ['drink', 'dairy'] }, + { shortcode: 'baby_bottle', emoji: '๐ผ', keywords: ['drink', 'infant'] }, + { shortcode: 'juice', emoji: '๐ง', keywords: ['drink', 'box'] }, + { shortcode: 'cup_with_straw', emoji: '๐ฅค', keywords: ['drink', 'soda'] }, + + // Weather & Nature + { shortcode: 'sun', emoji: 'โ๏ธ', keywords: ['weather', 'sunny', 'bright'] }, + { shortcode: 'moon', emoji: '๐', keywords: ['night', 'sleep'] }, + { shortcode: 'full_moon', emoji: '๐', keywords: ['night', 'lunar'] }, + { shortcode: 'new_moon', emoji: '๐', keywords: ['night', 'dark'] }, + { shortcode: 'star2', emoji: '๐', keywords: ['glow', 'sparkle'] }, + { shortcode: 'milky_way', emoji: '๐', keywords: ['galaxy', 'space'] }, + { shortcode: 'cloud', emoji: 'โ๏ธ', keywords: ['weather', 'sky'] }, + { shortcode: 'sun_behind_cloud', emoji: 'โ ', keywords: ['weather'] }, + { shortcode: 'cloud_with_rain', emoji: '๐ง๏ธ', keywords: ['weather', 'rainy'] }, + { shortcode: 'thunder', emoji: 'โ๏ธ', keywords: ['weather', 'storm'] }, + { shortcode: 'snowflake', emoji: 'โ๏ธ', keywords: ['weather', 'cold'] }, + { shortcode: 'snowman', emoji: 'โ๏ธ', keywords: ['winter', 'snow'] }, + { shortcode: 'wind_blowing', emoji: '๐ฌ๏ธ', keywords: ['weather', 'air'] }, + { shortcode: 'tornado', emoji: '๐ช๏ธ', keywords: ['weather', 'storm'] }, + { shortcode: 'fog', emoji: '๐ซ๏ธ', keywords: ['weather', 'mist'] }, + { shortcode: 'umbrella', emoji: 'โ๏ธ', keywords: ['rain', 'weather'] }, + { shortcode: 'rainbow', emoji: '๐', keywords: ['weather', 'pride'] }, + { shortcode: 'earth', emoji: '๐', keywords: ['world', 'planet'] }, + { shortcode: 'earth_americas', emoji: '๐', keywords: ['world', 'planet'] }, + { shortcode: 'earth_asia', emoji: '๐', keywords: ['world', 'planet'] }, + { shortcode: 'rocket', emoji: '๐', keywords: ['space', 'launch'] }, + { shortcode: 'satellite', emoji: '๐ฐ๏ธ', keywords: ['space', 'orbit'] }, + { shortcode: 'ufo', emoji: '๐ธ', keywords: ['alien', 'space'] }, + + // Checkmarks & Common Symbols + { shortcode: 'white_check_mark', emoji: 'โ ', keywords: ['done', 'yes', 'ok'] }, + { shortcode: 'check', emoji: 'โ๏ธ', keywords: ['done', 'yes'] }, + { shortcode: 'x', emoji: 'โ', keywords: ['no', 'wrong', 'cancel'] }, + { shortcode: 'cross_mark', emoji: 'โ', keywords: ['no', 'wrong'] }, + { shortcode: 'plus', emoji: 'โ', keywords: ['add', 'math'] }, + { shortcode: 'minus', emoji: 'โ', keywords: ['subtract', 'math'] }, + { shortcode: 'divide', emoji: 'โ', keywords: ['math', 'division'] }, + { shortcode: 'multiply', emoji: 'โ๏ธ', keywords: ['math', 'times'] }, + { shortcode: 'infinity', emoji: 'โพ๏ธ', keywords: ['forever', 'endless'] }, + { shortcode: 'question', emoji: 'โ', keywords: ['ask', 'what'] }, + { shortcode: 'grey_question', emoji: 'โ', keywords: ['ask', 'what'] }, + { shortcode: 'exclamation', emoji: 'โ', keywords: ['alert', 'important'] }, + { shortcode: 'grey_exclamation', emoji: 'โ', keywords: ['alert'] }, + { shortcode: 'warning', emoji: 'โ ๏ธ', keywords: ['alert', 'caution'] }, + { shortcode: 'no_entry', emoji: 'โ', keywords: ['stop', 'forbidden'] }, + { shortcode: 'prohibited', emoji: '๐ซ', keywords: ['stop', 'banned'] }, + { shortcode: 'recycle', emoji: 'โป๏ธ', keywords: ['environment', 'green'] }, + { shortcode: 'arrow_up', emoji: 'โฌ๏ธ', keywords: ['direction', 'north'] }, + { shortcode: 'arrow_down', emoji: 'โฌ๏ธ', keywords: ['direction', 'south'] }, + { shortcode: 'arrow_left', emoji: 'โฌ ๏ธ', keywords: ['direction', 'west'] }, + { shortcode: 'arrow_right', emoji: 'โก๏ธ', keywords: ['direction', 'east'] }, + { + shortcode: 'arrow_upper_right', + emoji: 'โ๏ธ', + keywords: ['direction', 'northeast'], + }, + { + shortcode: 'arrow_lower_right', + emoji: 'โ๏ธ', + keywords: ['direction', 'southeast'], + }, + { + shortcode: 'arrow_lower_left', + emoji: 'โ๏ธ', + keywords: ['direction', 'southwest'], + }, + { + shortcode: 'arrow_upper_left', + emoji: 'โ๏ธ', + keywords: ['direction', 'northwest'], + }, + { + shortcode: 'left_right_arrow', + emoji: 'โ๏ธ', + keywords: ['direction', 'horizontal'], + }, + { + shortcode: 'up_down_arrow', + emoji: 'โ๏ธ', + keywords: ['direction', 'vertical'], + }, + { shortcode: 'arrows_clockwise', emoji: '๐', keywords: ['refresh', 'sync'] }, + { + shortcode: 'arrows_counterclockwise', + emoji: '๐', + keywords: ['refresh', 'sync'], + }, + { shortcode: 'back', emoji: '๐', keywords: ['return', 'previous'] }, + { shortcode: 'end', emoji: '๐', keywords: ['finish', 'last'] }, + { shortcode: 'on', emoji: '๐', keywords: ['active'] }, + { shortcode: 'soon', emoji: '๐', keywords: ['coming', 'future'] }, + { shortcode: 'top', emoji: '๐', keywords: ['best', 'first'] }, + { shortcode: 'new', emoji: '๐', keywords: ['fresh', 'latest'] }, + { shortcode: 'free', emoji: '๐', keywords: ['gratis', 'cost'] }, + { shortcode: 'up', emoji: '๐', keywords: ['increase', 'level'] }, + { shortcode: 'cool', emoji: '๐', keywords: ['nice', 'awesome'] }, + { shortcode: 'ok', emoji: '๐', keywords: ['yes', 'approve'] }, + { shortcode: 'sos', emoji: '๐', keywords: ['help', 'emergency'] }, + { shortcode: 'stop_sign', emoji: '๐', keywords: ['halt', 'cease'] }, + { shortcode: 'a', emoji: '๐ ฐ๏ธ', keywords: ['letter', 'blood'] }, + { shortcode: 'b', emoji: '๐ ฑ๏ธ', keywords: ['letter', 'blood'] }, + { shortcode: 'o', emoji: '๐ พ๏ธ', keywords: ['letter', 'blood'] }, + { shortcode: 'information', emoji: 'โน๏ธ', keywords: ['info', 'help'] }, + { shortcode: 'copyright', emoji: 'ยฉ๏ธ', keywords: ['legal', 'ip'] }, + { shortcode: 'registered', emoji: 'ยฎ๏ธ', keywords: ['legal', 'brand'] }, + { shortcode: 'tm', emoji: 'โข๏ธ', keywords: ['legal', 'trademark'] }, + { shortcode: 'one', emoji: '1๏ธโฃ', keywords: ['number', 'first'] }, + { shortcode: 'two', emoji: '2๏ธโฃ', keywords: ['number', 'second'] }, + { shortcode: 'three', emoji: '3๏ธโฃ', keywords: ['number', 'third'] }, + { shortcode: 'four', emoji: '4๏ธโฃ', keywords: ['number'] }, + { shortcode: 'five', emoji: '5๏ธโฃ', keywords: ['number'] }, + { shortcode: 'six', emoji: '6๏ธโฃ', keywords: ['number'] }, + { shortcode: 'seven', emoji: '7๏ธโฃ', keywords: ['number'] }, + { shortcode: 'eight', emoji: '8๏ธโฃ', keywords: ['number'] }, + { shortcode: 'nine', emoji: '9๏ธโฃ', keywords: ['number'] }, + { shortcode: 'zero', emoji: '0๏ธโฃ', keywords: ['number'] }, + { shortcode: 'keycap_ten', emoji: '๐', keywords: ['number', 'ten'] }, + { shortcode: 'hash', emoji: '#๏ธโฃ', keywords: ['number', 'pound', 'hashtag'] }, + { shortcode: 'asterisk', emoji: '*๏ธโฃ', keywords: ['star', 'symbol'] }, + { shortcode: 'eject', emoji: 'โ๏ธ', keywords: ['media', 'remove'] }, + { shortcode: 'play', emoji: 'โถ๏ธ', keywords: ['media', 'start'] }, + { shortcode: 'pause', emoji: 'โธ๏ธ', keywords: ['media', 'wait'] }, + { shortcode: 'stop', emoji: 'โน๏ธ', keywords: ['media', 'end'] }, + { shortcode: 'record', emoji: 'โบ๏ธ', keywords: ['media', 'red'] }, + { shortcode: 'fast_forward', emoji: 'โฉ', keywords: ['media', 'skip'] }, + { shortcode: 'rewind', emoji: 'โช', keywords: ['media', 'back'] }, + { shortcode: 'next_track', emoji: 'โญ๏ธ', keywords: ['media', 'skip'] }, + { shortcode: 'previous_track', emoji: 'โฎ๏ธ', keywords: ['media', 'back'] }, + { shortcode: 'cinema', emoji: '๐ฆ', keywords: ['movie', 'film'] }, + { shortcode: 'low_brightness', emoji: '๐ ', keywords: ['dim', 'light'] }, + { shortcode: 'high_brightness', emoji: '๐', keywords: ['bright', 'light'] }, + { shortcode: 'signal_strength', emoji: '๐ถ', keywords: ['wifi', 'bars'] }, + { shortcode: 'vibration', emoji: '๐ณ', keywords: ['phone', 'mode'] }, + { shortcode: 'mobile_off', emoji: '๐ด', keywords: ['phone', 'silent'] }, + { shortcode: 'female', emoji: 'โ๏ธ', keywords: ['woman', 'gender'] }, + { shortcode: 'male', emoji: 'โ๏ธ', keywords: ['man', 'gender'] }, + { shortcode: 'medical', emoji: 'โ๏ธ', keywords: ['health', 'doctor'] }, + { shortcode: 'atom', emoji: 'โ๏ธ', keywords: ['science', 'physics'] }, +]; + +/** + * Filter emojis by search text (checks shortcode and keywords) + */ +export function filterEmojis( + searchText: string, + limit: number = 10, +): EmojiItem[] { + if (!searchText) return []; + + const lowerSearch = searchText.toLowerCase(); Review Comment: **Suggestion:** Normalization bug: the function does not trim input or remove leading colon(s) (e.g. ":smile") which is commonly provided by Slack-like autocomplete triggers, so searches with a leading ':' or surrounding whitespace will not match any shortcodes; normalize the input (trim and strip leading colons) before searching. [logic error] **Severity Level:** Minor โ ๏ธ ```suggestion const normalized = searchText?.trim().replace(/^:+/, ''); if (!normalized) return []; const lowerSearch = normalized.toLowerCase(); ``` <details> <summary><b>Why it matters? โญ </b></summary> This is a real usability bug: users commonly type ":smile" (leading colon) or add surrounding whitespace when using Slack-like autocompletes. As shortcodes in EMOJI_DATA don't include the colon, the current code won't match those inputs. Normalizing (trim + strip leading colons) directly fixes the behavior with a small, low-risk change. </details> <details> <summary><b>Prompt for AI Agent ๐ค </b></summary> ```mdx This is a comment left during a code review. **Path:** superset-frontend/packages/superset-ui-core/src/components/EmojiTextArea/emojiData.ts **Line:** 559:561 **Comment:** *Logic Error: Normalization bug: the function does not trim input or remove leading colon(s) (e.g. ":smile") which is commonly provided by Slack-like autocomplete triggers, so searches with a leading ':' or surrounding whitespace will not match any shortcodes; normalize the input (trim and strip leading colons) before searching. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. ``` </details> -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
