diff options
author | Ansh <anshnanda10@gmail.com> | 2023-08-23 16:29:23 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-23 16:29:23 -0700 |
commit | 6487a875426d098432fa708dc493ce150385ce91 (patch) | |
tree | 41f5366b1781a43c70d1bef92ad22afb92fe43b1 /src | |
parent | 8ab5eb6583b6ddd4ed03ef2b1a55ef83fa0c0625 (diff) | |
download | voidsky-6487a875426d098432fa708dc493ce150385ce91.tar.zst |
[APP-836] Emoji picker for web (#1254)
* add emoji-mart package for emoji dropdown picker * remove emoji picker modal * load emoji mart data not as part of the main bundle * remove @emoji-mart/data * setup emoji insertion with events * get emoji data from local static assets * close emoji picker after one emoji has been inserted * Switch emoji picker trigger to an icon * Update emoji-mart-data.js * make grabbing emoji data work on more browsers --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/platform/polyfills.web.ts | 5 | ||||
-rw-r--r-- | src/view/com/composer/Composer.tsx | 2 | ||||
-rw-r--r-- | src/view/com/composer/text-input/TextInput.web.tsx | 37 | ||||
-rw-r--r-- | src/view/com/composer/text-input/web/EmojiPicker.web.tsx | 81 | ||||
-rw-r--r-- | src/view/index.ts | 2 |
5 files changed, 115 insertions, 12 deletions
diff --git a/src/platform/polyfills.web.ts b/src/platform/polyfills.web.ts index e46963a6f..1f9a3fa5e 100644 --- a/src/platform/polyfills.web.ts +++ b/src/platform/polyfills.web.ts @@ -9,4 +9,9 @@ if (!globalThis.Intl?.Segmenter) { const script = document.createElement('script') script.setAttribute('src', '/static/js/intl-segmenter-polyfill.min.js') document.head.appendChild(script) + + // loading emoji mart data + const emojiMartScript = document.createElement('script') + emojiMartScript.setAttribute('src', '/static/js/emoji-mart-data.js') + document.head.appendChild(emojiMartScript) } diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index f6308c394..c801c47bf 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -45,6 +45,7 @@ import {Gallery} from './photos/Gallery' import {MAX_GRAPHEME_LENGTH} from 'lib/constants' import {LabelsBtn} from './labels/LabelsBtn' import {SelectLangBtn} from './select-language/SelectLangBtn' +import {EmojiPickerButton} from './text-input/web/EmojiPicker.web' import {insertMentionAt} from 'lib/strings/mention-manip' type Props = ComposerOpts & { @@ -394,6 +395,7 @@ export const ComposePost = observer(function ComposePost({ <OpenCameraBtn gallery={gallery} /> </> ) : null} + {isDesktopWeb ? <EmojiPickerButton /> : null} <View style={s.flex1} /> <SelectLangBtn /> <CharProgress count={graphemeLength} /> diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index b7ebdd989..dfe1e26a1 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -16,6 +16,7 @@ import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete' import {createSuggestion} from './web/Autocomplete' import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' import {isUriImage, blobToDataUri} from 'lib/media/util' +import {Emoji} from './web/EmojiPicker.web' export interface TextInputRef { focus: () => void @@ -34,6 +35,8 @@ interface TextInputProps { onError: (err: string) => void } +export const textInputWebEmitter = new EventEmitter() + export const TextInput = React.forwardRef( ( { @@ -54,21 +57,18 @@ export const TextInput = React.forwardRef( 'ProseMirror-dark', ) - // we use a memoized emitter to propagate events out of tiptap - // without triggering re-runs of the useEditor hook - const emitter = React.useMemo(() => new EventEmitter(), []) React.useEffect(() => { - emitter.addListener('publish', onPressPublish) + textInputWebEmitter.addListener('publish', onPressPublish) return () => { - emitter.removeListener('publish', onPressPublish) + textInputWebEmitter.removeListener('publish', onPressPublish) } - }, [emitter, onPressPublish]) + }, [onPressPublish]) React.useEffect(() => { - emitter.addListener('photo-pasted', onPhotoPasted) + textInputWebEmitter.addListener('photo-pasted', onPhotoPasted) return () => { - emitter.removeListener('photo-pasted', onPhotoPasted) + textInputWebEmitter.removeListener('photo-pasted', onPhotoPasted) } - }, [emitter, onPhotoPasted]) + }, [onPhotoPasted]) const editor = useEditor( { @@ -105,12 +105,12 @@ export const TextInput = React.forwardRef( } getImageFromUri(items, (uri: string) => { - emitter.emit('photo-pasted', uri) + textInputWebEmitter.emit('photo-pasted', uri) }) }, handleKeyDown: (_, event) => { if ((event.metaKey || event.ctrlKey) && event.code === 'Enter') { - emitter.emit('publish') + textInputWebEmitter.emit('publish') } }, }, @@ -134,8 +134,21 @@ export const TextInput = React.forwardRef( } }, }, - [modeClass, emitter], + [modeClass], + ) + + const onEmojiInserted = React.useCallback( + (emoji: Emoji) => { + editor?.chain().focus('end').insertContent(emoji.native).run() + }, + [editor], ) + React.useEffect(() => { + textInputWebEmitter.addListener('emoji-inserted', onEmojiInserted) + return () => { + textInputWebEmitter.removeListener('emoji-inserted', onEmojiInserted) + } + }, [onEmojiInserted]) React.useImperativeHandle(ref, () => ({ focus: () => {}, // TODO diff --git a/src/view/com/composer/text-input/web/EmojiPicker.web.tsx b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx new file mode 100644 index 000000000..6c7d890a4 --- /dev/null +++ b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx @@ -0,0 +1,81 @@ +import React from 'react' +import Picker from '@emoji-mart/react' +import {StyleSheet, View} from 'react-native' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import {textInputWebEmitter} from '../TextInput.web' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {usePalette} from 'lib/hooks/usePalette' + +export type Emoji = { + aliases?: string[] + emoticons: string[] + id: string + keywords: string[] + name: string + native: string + shortcodes?: string + unified: string +} + +export function EmojiPickerButton() { + const pal = usePalette('default') + const [open, setOpen] = React.useState(false) + const onOpenChange = (o: boolean) => { + setOpen(o) + } + + return ( + <DropdownMenu.Root open={open} onOpenChange={onOpenChange}> + <DropdownMenu.Trigger style={styles.trigger}> + <FontAwesomeIcon + icon={['far', 'face-smile']} + color={pal.colors.link} + size={22} + /> + </DropdownMenu.Trigger> + + <DropdownMenu.Portal> + <DropdownMenu.Content> + <EmojiPicker + close={() => { + setOpen(false) + }} + /> + </DropdownMenu.Content> + </DropdownMenu.Portal> + </DropdownMenu.Root> + ) +} + +export function EmojiPicker({close}: {close: () => void}) { + const onInsert = (emoji: Emoji) => { + textInputWebEmitter.emit('emoji-inserted', emoji) + close() + } + return ( + <View style={styles.mask}> + <Picker + // @ts-ignore we set emojiMartData in `emoji-mart-data.js` file + data={window.emojiMartData} + onEmojiSelect={onInsert} + autoFocus={false} + /> + </View> + ) +} + +const styles = StyleSheet.create({ + mask: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + }, + trigger: { + backgroundColor: 'transparent', + border: 'none', + paddingTop: 4, + paddingHorizontal: 10, + cursor: 'pointer', + }, +}) diff --git a/src/view/index.ts b/src/view/index.ts index 27655e900..1c3dc3937 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -40,6 +40,7 @@ import {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope' import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation' import {faEye} from '@fortawesome/free-solid-svg-icons/faEye' import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash' +import {faFaceSmile} from '@fortawesome/free-regular-svg-icons/faFaceSmile' import {faFloppyDisk} from '@fortawesome/free-regular-svg-icons/faFloppyDisk' import {faGear} from '@fortawesome/free-solid-svg-icons/faGear' import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe' @@ -134,6 +135,7 @@ export function setup() { faEye, faExclamation, farEyeSlash, + faFaceSmile, faFloppyDisk, faGear, faGlobe, |