diff options
Diffstat (limited to 'src/view/com/composer/text-input')
5 files changed, 53 insertions, 56 deletions
diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx index 10cf1a931..96cecb37c 100644 --- a/src/view/com/composer/text-input/TextInput.tsx +++ b/src/view/com/composer/text-input/TextInput.tsx @@ -31,7 +31,7 @@ import { suggestLinkCardUri, } from '#/view/com/composer/text-input/text-input-util' import {atoms as a, useAlf} from '#/alf' -import {normalizeTextStyles} from '#/components/Typography' +import {normalizeTextStyles} from '#/alf/typography' import {Autocomplete} from './mobile/Autocomplete' export interface TextInputRef { diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index fa742d258..8ec4fefa8 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -11,6 +11,7 @@ import {Paragraph} from '@tiptap/extension-paragraph' import {Placeholder} from '@tiptap/extension-placeholder' import {Text as TiptapText} from '@tiptap/extension-text' import {generateJSON} from '@tiptap/html' +import {Fragment, Node, Slice} from '@tiptap/pm/model' import {EditorContent, JSONContent, useEditor} from '@tiptap/react' import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' @@ -23,8 +24,8 @@ import { } from '#/view/com/composer/text-input/text-input-util' import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' import {atoms as a, useAlf} from '#/alf' +import {normalizeTextStyles} from '#/alf/typography' import {Portal} from '#/components/Portal' -import {normalizeTextStyles} from '#/components/Typography' import {Text} from '../../util/text/Text' import {createSuggestion} from './web/Autocomplete' import {Emoji} from './web/EmojiPicker.web' @@ -166,6 +167,11 @@ export const TextInput = React.forwardRef(function TextInputImpl( const editor = useEditor( { extensions, + coreExtensionOptions: { + clipboardTextSerializer: { + blockSeparator: '\n', + }, + }, onFocus() { onFocus?.() }, @@ -173,6 +179,20 @@ export const TextInput = React.forwardRef(function TextInputImpl( attributes: { class: modeClass, }, + clipboardTextParser: (text, context) => { + const blocks = text.split(/(?:\r\n?|\n)/) + const nodes: Node[] = blocks.map(line => { + return Node.fromJSON( + context.doc.type.schema, + line.length > 0 + ? {type: 'paragraph', content: [{type: 'text', text: line}]} + : {type: 'paragraph', content: []}, + ) + }) + + const fragment = Fragment.fromArray(nodes) + return Slice.maxOpen(fragment) + }, handlePaste: (view, event) => { const clipboardData = event.clipboardData let preventDefault = false @@ -205,6 +225,7 @@ export const TextInput = React.forwardRef(function TextInputImpl( autofocus: 'end', editable: true, injectCSS: true, + shouldRerenderOnTransaction: false, onCreate({editor: editorProp}) { // HACK // the 'enter' animation sometimes causes autofocus to fail @@ -297,15 +318,9 @@ export const TextInput = React.forwardRef(function TextInputImpl( style.lineHeight = style.lineHeight ? ((style.lineHeight + 'px') as unknown as number) : undefined + style.minHeight = webForceMinHeight ? 140 : undefined return style - }, [t, fonts]) - - React.useLayoutEffect(() => { - let node = editor?.view.dom - if (node) { - node.style.minHeight = webForceMinHeight ? '140px' : '' - } - }, [editor, webForceMinHeight]) + }, [t, fonts, webForceMinHeight]) return ( <> diff --git a/src/view/com/composer/text-input/hooks/useGrapheme.tsx b/src/view/com/composer/text-input/hooks/useGrapheme.tsx index 01b5b9698..aa375ff47 100644 --- a/src/view/com/composer/text-input/hooks/useGrapheme.tsx +++ b/src/view/com/composer/text-input/hooks/useGrapheme.tsx @@ -13,7 +13,7 @@ export const useGrapheme = () => { if (graphemes.length > length) { remainingCharacters = 0 - name = `${graphemes.slice(0, length).join('')}...` + name = `${graphemes.slice(0, length).join('')}…` } else { remainingCharacters = length - graphemes.length name = graphemes.join('') diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx index 3d2bcfa61..0fda6843b 100644 --- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx @@ -1,7 +1,5 @@ -import React, {useRef} from 'react' import {View} from 'react-native' import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' -import {AppBskyActorDefs} from '@atproto/api' import {Trans} from '@lingui/macro' import {PressableScale} from '#/lib/custom-animations/PressableScale' @@ -11,7 +9,6 @@ import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import {Text} from '#/components/Typography' -import {useGrapheme} from '../hooks/useGrapheme' export function Autocomplete({ prefix, @@ -22,15 +19,11 @@ export function Autocomplete({ }) { const t = useTheme() - const {getGraphemeString} = useGrapheme() const isActive = !!prefix - const {data: suggestions, isFetching} = useActorAutocompleteQuery(prefix) - const suggestionsRef = useRef< - AppBskyActorDefs.ProfileViewBasic[] | undefined - >(undefined) - if (suggestions) { - suggestionsRef.current = suggestions - } + const {data: suggestions, isFetching} = useActorAutocompleteQuery( + prefix, + true, + ) if (!isActive) return null @@ -46,26 +39,8 @@ export function Autocomplete({ t.atoms.border_contrast_high, {marginLeft: -62}, ]}> - {suggestionsRef.current?.length ? ( - suggestionsRef.current.slice(0, 5).map((item, index, arr) => { - // Eventually use an average length - const MAX_CHARS = 40 - const MAX_HANDLE_CHARS = 20 - - // Using this approach because styling is not respecting - // bounding box wrapping (before converting to ellipsis) - const {name: displayHandle, remainingCharacters} = getGraphemeString( - item.handle, - MAX_HANDLE_CHARS, - ) - - const {name: displayName} = getGraphemeString( - item.displayName || item.handle, - MAX_CHARS - - MAX_HANDLE_CHARS + - (remainingCharacters > 0 ? remainingCharacters : 0), - ) - + {suggestions?.length ? ( + suggestions.slice(0, 5).map((item, index, arr) => { return ( <View style={[ @@ -93,15 +68,23 @@ export function Autocomplete({ type={item.associated?.labeler ? 'labeler' : 'user'} /> <Text - style={[a.text_md, a.font_bold]} - emoji={true} + style={[a.flex_1, a.text_md, a.font_bold]} + emoji + numberOfLines={1}> + {sanitizeDisplayName( + item.displayName || sanitizeHandle(item.handle), + )} + </Text> + <Text + style={[ + t.atoms.text_contrast_medium, + a.text_right, + {maxWidth: '50%'}, + ]} numberOfLines={1}> - {sanitizeDisplayName(displayName)} + {sanitizeHandle(item.handle, '@')} </Text> </View> - <Text style={[t.atoms.text_contrast_medium]} numberOfLines={1}> - {sanitizeHandle(displayHandle, '@')} - </Text> </PressableScale> </View> ) diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index a43e67c04..f40c2ee8d 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -1,9 +1,4 @@ -import React, { - forwardRef, - useEffect, - useImperativeHandle, - useState, -} from 'react' +import {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import {Pressable, StyleSheet, View} from 'react-native' import {Trans} from '@lingui/macro' import {ReactRenderer} from '@tiptap/react' @@ -15,6 +10,8 @@ import { import tippy, {Instance as TippyInstance} from 'tippy.js' import {usePalette} from '#/lib/hooks/usePalette' +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {sanitizeHandle} from '#/lib/strings/handles' import {ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {Text} from '#/view/com/util/text/Text' import {UserAvatar} from '#/view/com/util/UserAvatar' @@ -153,7 +150,9 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>( {items.length > 0 ? ( items.map((item, index) => { const {name: displayName} = getGraphemeString( - item.displayName ?? item.handle, + sanitizeDisplayName( + item.displayName || sanitizeHandle(item.handle), + ), 30, // Heuristic value; can be modified ) const isSelected = selectedIndex === index @@ -186,7 +185,7 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>( </Text> </View> <Text type="xs" style={pal.textLight} numberOfLines={1}> - @{item.handle} + {sanitizeHandle(item.handle, '@')} </Text> </Pressable> ) |