diff --git a/src/style.css b/src/style.css index bf2ffed698..e299fdea52 100644 --- a/src/style.css +++ b/src/style.css @@ -74,6 +74,9 @@ a[role='link'][data-no-underline='1']:hover { height: 0; pointer-events: none; } +.composer-fallback-input::placeholder { + color: #8d8e96; +} .ProseMirror .mention { color: #0085ff; } diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 4f2c8086cc..f859b7b17d 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -1,13 +1,24 @@ -import {lazy, Suspense, useEffect, useState} from 'react' +import { + lazy, + Suspense, + useCallback, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from 'react' import {StyleSheet, View} from 'react-native' import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' +import {RichText} from '@atproto/api' import {Trans} from '@lingui/macro' import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, useAlf, useTheme} from '#/alf' +import {normalizeTextStyles} from '#/alf/typography' import {Portal} from '#/components/Portal' import {Text} from '#/components/Typography' -import {type TextInputProps} from './TextInput.types' +import {type TextInputProps, type TextInputRef} from './TextInput.types' import {getImageOrVideoFromUri} from './web/uri-to-media' const RichTextInput = lazy(async () => { @@ -16,6 +27,101 @@ const RichTextInput = lazy(async () => { return await import('./web/RichTextInputWeb') }) +function FallbackTextInput({ + ref, + richtext, + placeholder, + webForceMinHeight, + setRichText, + onFocus, +}: Pick< + TextInputProps, + | 'ref' + | 'richtext' + | 'placeholder' + | 'webForceMinHeight' + | 'setRichText' + | 'onFocus' +>) { + const {theme: t, fonts} = useAlf() + const textareaRef = useRef(null) + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + const newText = e.target.value + const newRt = new RichText({text: newText}) + newRt.detectFacetsWithoutResolution() + setRichText(newRt) + }, + [setRichText], + ) + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { + textInputWebEmitter.emit('publish') + e.preventDefault() + } + }, []) + + useImperativeHandle( + ref, + (): TextInputRef => ({ + focus: () => textareaRef.current?.focus(), + blur: () => textareaRef.current?.blur(), + getCursorPosition: () => undefined, + maybeClosePopup: () => false, + }), + ) + + useEffect(() => { + // Move cursor to end after autofocus + const textarea = textareaRef.current + if (textarea) { + const len = textarea.value.length + textarea.setSelectionRange(len, len) + } + }, []) + + const inputStyle = useMemo((): React.CSSProperties => { + const style = normalizeTextStyles( + [a.text_lg, a.leading_snug, t.atoms.text], + { + fontScale: fonts.scaleMultiplier, + fontFamily: fonts.family, + flags: {}, + }, + ) + style.lineHeight = style.lineHeight + ? ((style.lineHeight + 'px') as unknown as number) + : undefined + style.minHeight = webForceMinHeight ? 140 : undefined + return { + ...style, + width: '100%', + border: 'none', + outline: 'none', + resize: 'none', + background: 'transparent', + padding: 0, + margin: 0, + } as React.CSSProperties + }, [t, fonts, webForceMinHeight]) + + return ( +