diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index ec3a042a..8898ab97 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -9,7 +9,7 @@ import Hardbreak from '@tiptap/extension-hard-break' import {Mention} from '@tiptap/extension-mention' import {Paragraph} from '@tiptap/extension-paragraph' import {Placeholder} from '@tiptap/extension-placeholder' -import {Text} from '@tiptap/extension-text' +import {Text as TiptapText} from '@tiptap/extension-text' import isEqual from 'lodash.isequal' import {createSuggestion} from './web/Autocomplete' import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' @@ -18,6 +18,11 @@ import {Emoji} from './web/EmojiPicker.web' import {LinkDecorator} from './web/LinkDecorator' import {generateJSON} from '@tiptap/html' import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' +import {usePalette} from '#/lib/hooks/usePalette' +import {Portal} from '#/components/Portal' +import {Text} from '../../util/text/Text' +import {Trans} from '@lingui/macro' +import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' export interface TextInputRef { focus: () => void @@ -53,7 +58,11 @@ export const TextInput = React.forwardRef(function TextInputImpl( ) { const autocomplete = useActorAutocompleteFn() + const pal = usePalette('default') const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark') + + const [isDropping, setIsDropping] = React.useState(false) + const extensions = React.useMemo( () => [ Document, @@ -68,7 +77,7 @@ export const TextInput = React.forwardRef(function TextInputImpl( Placeholder.configure({ placeholder, }), - Text, + TiptapText, History, Hardbreak, ], @@ -88,6 +97,46 @@ export const TextInput = React.forwardRef(function TextInputImpl( } }, [onPhotoPasted]) + React.useEffect(() => { + const handleDrop = (event: DragEvent) => { + const transfer = event.dataTransfer + if (transfer) { + const items = transfer.items + + getImageFromUri(items, (uri: string) => { + textInputWebEmitter.emit('photo-pasted', uri) + }) + } + + event.preventDefault() + setIsDropping(false) + } + const handleDragEnter = (event: DragEvent) => { + const transfer = event.dataTransfer + + event.preventDefault() + if (transfer && transfer.types.includes('Files')) { + setIsDropping(true) + } + } + const handleDragLeave = (event: DragEvent) => { + event.preventDefault() + setIsDropping(false) + } + + document.body.addEventListener('drop', handleDrop) + document.body.addEventListener('dragenter', handleDragEnter) + document.body.addEventListener('dragover', handleDragEnter) + document.body.addEventListener('dragleave', handleDragLeave) + + return () => { + document.body.removeEventListener('drop', handleDrop) + document.body.removeEventListener('dragenter', handleDragEnter) + document.body.removeEventListener('dragover', handleDragEnter) + document.body.removeEventListener('dragleave', handleDragLeave) + } + }, [setIsDropping]) + const editor = useEditor( { extensions, @@ -177,9 +226,28 @@ export const TextInput = React.forwardRef(function TextInputImpl( })) return ( - - - + <> + + + + + {isDropping && ( + + + + + Drop to add images + + + + + )} + ) }) @@ -210,6 +278,32 @@ const styles = StyleSheet.create({ marginLeft: 8, marginBottom: 10, }, + dropContainer: { + backgroundColor: '#0007', + pointerEvents: 'none', + alignItems: 'center', + justifyContent: 'center', + position: 'absolute', + padding: 16, + top: 0, + bottom: 0, + left: 0, + right: 0, + }, + dropModal: { + // @ts-ignore web only + boxShadow: 'rgba(0, 0, 0, 0.3) 0px 5px 20px', + padding: 8, + borderWidth: 1, + borderRadius: 16, + }, + dropText: { + paddingVertical: 44, + paddingHorizontal: 36, + borderStyle: 'dashed', + borderRadius: 8, + borderWidth: 2, + }, }) function getImageFromUri( @@ -218,25 +312,24 @@ function getImageFromUri( ) { for (let index = 0; index < items.length; index++) { const item = items[index] - const {kind, type} = item + const type = item.type if (type === 'text/plain') { item.getAsString(async itemString => { if (isUriImage(itemString)) { const response = await fetch(itemString) const blob = await response.blob() - blobToDataUri(blob).then(callback, err => console.error(err)) + + if (blob.type.startsWith('image/')) { + blobToDataUri(blob).then(callback, err => console.error(err)) + } } }) - } - - if (kind === 'file') { + } else if (type.startsWith('image/')) { const file = item.getAsFile() - if (file instanceof Blob) { - blobToDataUri(new Blob([file], {type: item.type})).then(callback, err => - console.error(err), - ) + if (file) { + blobToDataUri(file).then(callback, err => console.error(err)) } } }