about summary refs log tree commit diff
path: root/src/view/com/composer/text-input
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/composer/text-input')
-rw-r--r--src/view/com/composer/text-input/TextInput.tsx2
-rw-r--r--src/view/com/composer/text-input/TextInput.web.tsx33
-rw-r--r--src/view/com/composer/text-input/hooks/useGrapheme.tsx2
-rw-r--r--src/view/com/composer/text-input/mobile/Autocomplete.tsx57
-rw-r--r--src/view/com/composer/text-input/web/Autocomplete.tsx15
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>
               )