From be7e4cc00cb9f328aae79268f13f1c2956b7e9be Mon Sep 17 00:00:00 2001 From: Cesar William Date: Wed, 3 Apr 2024 15:28:09 -0700 Subject: [PATCH] feat: emojis in the input value will be converted to images --- README.md | 1 + example/src/App.js | 8 ++++++++ package.json | 3 ++- src/hooks/use-expose.js | 5 +++-- src/hooks/use-sanitize.js | 41 +++++++++++++++++++++++++++++++++++++-- src/index.js | 12 ++++++++---- 6 files changed, 61 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3d8135b..64c60a4 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ export default function Example() { | `onResize` | function | - | A callback function that is invoked when the width or height of the input element changes. It receives the current size value as its argument. | | `placeholder` | string | "Type a message" | Specifies the placeholder text to be displayed when the input is empty. | | `placeholderColor` | string | "#C4C4C4" | Specifies the color of the placeholder text. Accepts any valid CSS color value. | +| `shouldConvertEmojiToImage` | boolean | false | When set to true, emojis in the input value will be converted to images. | | `shouldReturn` | boolean | - | When set to true, allows the user to create a new line using the `Shift + Enter` or `Ctrl + Enter` keyboard shortcuts. | | `theme` | string | - | Specifies the theme for the emoji picker popup. Available values: "light", "dark", "auto". | | `value` | string | "" | The current value of the input element. | diff --git a/example/src/App.js b/example/src/App.js index 2c8c445..3f2bf97 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -327,6 +327,14 @@ export default function App() { Allows the user to use the Shift + Enter or Ctrl + Enter keyboard shortcut to create a new line. + + + shouldConvertEmojiToImage + + + Defaults to false. If set to true, emojis will be converted to images in the result. + + value diff --git a/package.json b/package.json index 3c19d29..213a7df 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "dependencies": { "@emoji-mart/data": "1.1.2", "@emoji-mart/react": "1.1.1", - "emoji-mart": "5.5.2" + "emoji-mart": "5.5.2", + "react-easy-emoji": "^1.8.1" } } diff --git a/src/hooks/use-expose.js b/src/hooks/use-expose.js index eaf005b..b3de727 100644 --- a/src/hooks/use-expose.js +++ b/src/hooks/use-expose.js @@ -9,14 +9,15 @@ import { useSanitize } from "./use-sanitize"; * @property {React.MutableRefObject} textInputRef * @property {(value: string) => void} setValue * @property {() => void} emitChange + * @property {boolean=} shouldConvertEmojiToImage */ /** * * @param {Props} props */ -export function useExpose({ ref, textInputRef, setValue, emitChange }) { - const { sanitize, sanitizedTextRef } = useSanitize(false); +export function useExpose({ ref, textInputRef, setValue, emitChange, shouldConvertEmojiToImage }) { + const { sanitize, sanitizedTextRef } = useSanitize(false, shouldConvertEmojiToImage); useImperativeHandle(ref, () => ({ get value() { diff --git a/src/hooks/use-sanitize.js b/src/hooks/use-sanitize.js index ed02ab8..6a60483 100644 --- a/src/hooks/use-sanitize.js +++ b/src/hooks/use-sanitize.js @@ -1,8 +1,14 @@ // @ts-check import { useCallback, useRef } from "react"; +import { renderToString } from 'react-dom/server' +import emoji from 'react-easy-emoji' import { removeHtmlExceptBr } from "../utils/input-event-utils"; +const EMOJI_REGEX = new RegExp( + /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/g +) + /** * @typedef {import('../types/types').SanitizeFn} SanitizeFn */ @@ -10,8 +16,9 @@ import { removeHtmlExceptBr } from "../utils/input-event-utils"; // eslint-disable-next-line valid-jsdoc /** * @param {boolean} shouldReturn + * @param {boolean} shouldConvertEmojiToImage */ -export function useSanitize(shouldReturn) { +export function useSanitize(shouldReturn, shouldConvertEmojiToImage) { /** @type {React.MutableRefObject} */ const sanitizeFnsRef = useRef([]); @@ -30,10 +37,14 @@ export function useSanitize(shouldReturn) { result = replaceAllHtmlToString(result, shouldReturn); + if (shouldConvertEmojiToImage) { + result = convertEmojiToImage(result); + } + sanitizedTextRef.current = result; return result; - }, []); + }, [shouldReturn, shouldConvertEmojiToImage]); return { addSanitizeFn, sanitize, sanitizedTextRef }; } @@ -59,3 +70,29 @@ export function replaceAllHtmlToString(html, shouldReturn) { return text; } + +/** + * + * @param {string} text + * @return {string} + */ +function convertEmojiToImage(text) { + text = handleEmoji(text) + text = renderToString(emoji(text)) + text = text.replace( + new RegExp('<span class="message-emoji">', 'g'), + '' + ) + text = text.replace(new RegExp('</span>', 'g'), '') + + return text +} + +/** + * + * @param {string} text + * @return {string} + */ +function handleEmoji (text) { + return text.replace(EMOJI_REGEX, '$&') +} diff --git a/src/index.js b/src/index.js index 533802b..c671087 100644 --- a/src/index.js +++ b/src/index.js @@ -43,7 +43,7 @@ import { usePollute } from "./hooks/user-pollute"; * @property {() => void=} onClick * @property {() => void=} onFocus * @property {() => void=} onBlur - * @property {boolean=} shouldReturn + * @property {boolean} shouldReturn * @property {number=} maxLength * @property {boolean=} keepOpened * @property {(event: KeyboardEvent) => void=} onKeyDown @@ -61,6 +61,7 @@ import { usePollute } from "./hooks/user-pollute"; * @property {(text: string) => Promise=} searchMention * @property {HTMLDivElement=} buttonElement * @property {React.MutableRefObject=} buttonRef + * @property {boolean} shouldConvertEmojiToImage */ /** @@ -73,7 +74,6 @@ function InputEmoji(props, ref) { const { onChange, onEnter, - shouldReturn, onResize, onClick, onFocus, @@ -93,6 +93,8 @@ function InputEmoji(props, ref) { searchMention, buttonElement, buttonRef, + shouldReturn, + shouldConvertEmojiToImage, // style borderRadius, borderColor, @@ -108,7 +110,7 @@ function InputEmoji(props, ref) { const { addEventListener, listeners } = useEventListeners(); - const { addSanitizeFn, sanitize, sanitizedTextRef } = useSanitize(props.shouldReturn); + const { addSanitizeFn, sanitize, sanitizedTextRef } = useSanitize(shouldReturn, shouldConvertEmojiToImage); const { addPolluteFn, pollute } = usePollute(); @@ -139,7 +141,8 @@ function InputEmoji(props, ref) { ref, setValue, textInputRef, - emitChange + emitChange, + shouldConvertEmojiToImage }); useEffect(() => { @@ -360,6 +363,7 @@ InputEmojiWithRef.defaultProps = { background: "white", tabIndex: 0, shouldReturn: false, + shouldConvertEmojiToImage: false, customEmojis: [], language: undefined, };