1
0
mirror of https://github.com/tooot-app/app synced 2025-02-14 19:00:50 +01:00

Hashtag done

This commit is contained in:
Zhiyuan Zheng 2020-12-04 01:17:10 +01:00
parent 5866d016bc
commit fcaea5b8d9
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
9 changed files with 424 additions and 334 deletions

View File

@ -19,7 +19,6 @@ export type PostState = {
formatted: ReactNode formatted: ReactNode
} }
selection: { start: number; end: number } selection: { start: number; end: number }
overlay: null | 'suggestions' | 'emojis'
tag: tag:
| { | {
type: 'url' | 'accounts' | 'hashtags' type: 'url' | 'accounts' | 'hashtags'
@ -27,7 +26,10 @@ export type PostState = {
offset: number offset: number
} }
| undefined | undefined
emojis: Mastodon.Emoji[] | undefined emoji: {
active: boolean
emojis: { title: string; data: Mastodon.Emoji[] }[] | undefined
}
poll: { poll: {
active: boolean active: boolean
total: number total: number
@ -67,17 +69,13 @@ export type PostAction =
type: 'selection' type: 'selection'
payload: PostState['selection'] payload: PostState['selection']
} }
| {
type: 'overlay'
payload: PostState['overlay']
}
| { | {
type: 'tag' type: 'tag'
payload: PostState['tag'] payload: PostState['tag']
} }
| { | {
type: 'emojis' type: 'emoji'
payload: PostState['emojis'] payload: PostState['emoji']
} }
| { | {
type: 'poll' type: 'poll'
@ -110,9 +108,8 @@ const postInitialState: PostState = {
formatted: undefined formatted: undefined
}, },
selection: { start: 0, end: 0 }, selection: { start: 0, end: 0 },
overlay: null,
tag: undefined, tag: undefined,
emojis: undefined, emoji: { active: false, emojis: undefined },
poll: { poll: {
active: false, active: false,
total: 2, total: 2,
@ -137,12 +134,10 @@ const postReducer = (state: PostState, action: PostAction): PostState => {
return { ...state, text: { ...state.text, ...action.payload } } return { ...state, text: { ...state.text, ...action.payload } }
case 'selection': case 'selection':
return { ...state, selection: action.payload } return { ...state, selection: action.payload }
case 'overlay':
return { ...state, overlay: action.payload }
case 'tag': case 'tag':
return { ...state, tag: action.payload } return { ...state, tag: action.payload }
case 'emojis': case 'emoji':
return { ...state, emojis: action.payload } return { ...state, emoji: action.payload }
case 'poll': case 'poll':
return { ...state, poll: action.payload } return { ...state, poll: action.payload }
case 'attachments/add': case 'attachments/add':

View File

@ -106,16 +106,23 @@ const ComposeActions: React.FC<Props> = ({
<Feather <Feather
name='smile' name='smile'
size={24} size={24}
color={postState.emojis?.length ? theme.primary : theme.secondary} color={postState.emoji.emojis?.length ? theme.primary : theme.secondary}
onPress={() => { {...(postState.emoji.emojis && {
if (postState.emojis?.length && postState.overlay === null) { onPress: () => {
Keyboard.dismiss() if (postState.emoji.active) {
postDispatch({ type: 'overlay', payload: 'emojis' }) postDispatch({
type: 'emoji',
payload: { ...postState.emoji, active: false }
})
} else {
Keyboard.dismiss()
postDispatch({
type: 'emoji',
payload: { ...postState.emoji, active: true }
})
}
} }
if (postState.overlay === 'emojis') { })}
postDispatch({ type: 'overlay', payload: null })
}
}}
/> />
<Text <Text
style={[ style={[

View File

@ -17,30 +17,22 @@ import updateText from './updateText'
export interface Props { export interface Props {
textInputRef: React.RefObject<TextInput> textInputRef: React.RefObject<TextInput>
onChangeText: any
postState: PostState postState: PostState
postDispatch: Dispatch<PostAction> postDispatch: Dispatch<PostAction>
} }
const ComposeEmojis: React.FC<Props> = ({ const ComposeEmojis: React.FC<Props> = ({
textInputRef, textInputRef,
onChangeText,
postState, postState,
postDispatch postDispatch
}) => { }) => {
const { theme } = useTheme() const { theme } = useTheme()
let sortedEmojis: { title: string; data: Mastodon.Emoji[] }[] = []
forEach(
groupBy(sortBy(postState.emojis, ['category', 'shortcode']), 'category'),
(value, key) => sortedEmojis.push({ title: key, data: value })
)
return ( return (
<View style={styles.base}> <View style={styles.base}>
<SectionList <SectionList
horizontal horizontal
sections={sortedEmojis} sections={postState.emoji.emojis!}
keyExtractor={item => item.shortcode} keyExtractor={item => item.shortcode}
renderSectionHeader={({ section: { title } }) => ( renderSectionHeader={({ section: { title } }) => (
<Text style={[styles.group, { color: theme.secondary }]}> <Text style={[styles.group, { color: theme.secondary }]}>
@ -56,12 +48,16 @@ const ComposeEmojis: React.FC<Props> = ({
key={emoji.shortcode} key={emoji.shortcode}
onPress={() => { onPress={() => {
updateText({ updateText({
onChangeText,
postState, postState,
newText: `:${emoji.shortcode}:` postDispatch,
newText: `:${emoji.shortcode}:`,
type: 'emoji'
}) })
textInputRef.current?.focus() textInputRef.current?.focus()
postDispatch({ type: 'overlay', payload: null }) postDispatch({
type: 'emoji',
payload: { ...postState.emoji, active: false }
})
}} }}
> >
<Image source={{ uri: emoji.url }} style={styles.emoji} /> <Image source={{ uri: emoji.url }} style={styles.emoji} />

View File

@ -1,37 +1,29 @@
import React, { import ImagePicker from 'expo-image-picker'
createElement, import { forEach, groupBy, sortBy } from 'lodash'
Dispatch, import React, { Dispatch, useEffect, useMemo, useRef } from 'react'
useCallback,
useEffect,
useRef,
useState
} from 'react'
import { import {
ActionSheetIOS, View,
Keyboard, ActivityIndicator,
FlatList,
Pressable, Pressable,
ScrollView,
StyleSheet, StyleSheet,
Text, Text,
TextInput, TextInput,
View Image
} from 'react-native' } from 'react-native'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { Feather } from '@expo/vector-icons' import Emojis from 'src/components/Timelines/Timeline/Shared/Emojis'
import * as ImagePicker from 'expo-image-picker' import { emojisFetch } from 'src/utils/fetches/emojisFetch'
import { debounce, differenceWith, isEqual } from 'lodash' import { searchFetch } from 'src/utils/fetches/searchFetch'
import { StyleConstants } from 'src/utils/styles/constants'
import Autolinker from 'src/modules/autolinker' import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, PostState } from '../Compose'
import ComposeActions from './Actions'
import ComposeAttachments from './Attachments'
import ComposeEmojis from './Emojis' import ComposeEmojis from './Emojis'
import ComposePoll from './Poll' import ComposePoll from './Poll'
import ComposeSuggestions from './Suggestions' import ComposeTextInput from './TextInput'
import { emojisFetch } from 'src/utils/fetches/emojisFetch' import updateText from './updateText'
import { PostAction, PostState } from 'src/screens/Shared/Compose'
import addAttachments from './addAttachments'
import ComposeAttachments from './Attachments'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { StyleConstants } from 'src/utils/styles/constants'
import ComposeActions from './Actions'
export interface Props { export interface Props {
postState: PostState postState: PostState
@ -40,6 +32,19 @@ export interface Props {
const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => { const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
const { theme } = useTheme() const { theme } = useTheme()
const { isFetching, isSuccess, data, refetch } = useQuery(
[
'Search',
{ type: postState.tag?.type, term: postState.tag?.text.substring(1) }
],
searchFetch,
{ enabled: false }
)
useEffect(() => {
refetch()
}, [postState.tag?.text])
useEffect(() => { useEffect(() => {
;(async () => { ;(async () => {
const { status } = await ImagePicker.requestCameraRollPermissionsAsync() const { status } = await ImagePicker.requestCameraRollPermissionsAsync()
@ -52,176 +57,32 @@ const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
const { data: emojisData } = useQuery(['Emojis'], emojisFetch) const { data: emojisData } = useQuery(['Emojis'], emojisFetch)
useEffect(() => { useEffect(() => {
if (emojisData && emojisData.length) { if (emojisData && emojisData.length) {
postDispatch({ type: 'emojis', payload: emojisData }) let sortedEmojis: { title: string; data: Mastodon.Emoji[] }[] = []
forEach(
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
(value, key) => sortedEmojis.push({ title: key, data: value })
)
postDispatch({
type: 'emoji',
payload: { ...postState.emoji, emojis: sortedEmojis }
})
} }
}, [emojisData]) }, [emojisData])
const debouncedSuggestions = useCallback(
debounce(tag => {
postDispatch({ type: 'overlay', payload: 'suggestions' })
postDispatch({ type: 'tag', payload: tag })
}, 500),
[]
)
let prevTags: PostState['tag'][] = []
const onChangeText = useCallback(({ content, disableDebounce }) => {
const tags: PostState['tag'][] = []
Autolinker.link(content, {
email: false,
phone: false,
mention: 'mastodon',
hashtag: 'twitter',
replaceFn: props => {
const type = props.getType()
let newType: 'url' | 'accounts' | 'hashtags'
switch (type) {
case 'mention':
newType = 'accounts'
break
case 'hashtag':
newType = 'hashtags'
break
default:
newType = 'url'
break
}
tags.push({
type: newType,
text: props.getMatchedText(),
offset: props.getOffset()
})
return
}
})
const changedTag = differenceWith(prevTags, tags, isEqual)
// quick delete causes flicking of suggestion box
if (
changedTag.length > 0 &&
tags.length > 0 &&
content.length > 0 &&
!disableDebounce
) {
console.log('changedTag length')
console.log(changedTag.length)
console.log('tags length')
console.log(tags.length)
console.log('changed Tag')
console.log(changedTag)
if (changedTag[0]!.type !== 'url') {
debouncedSuggestions(changedTag[0])
}
} else {
postDispatch({ type: 'overlay', payload: null })
postDispatch({ type: 'tag', payload: undefined })
}
prevTags = tags
let _content = content
let contentLength: number = 0
const children = []
tags.forEach(tag => {
const parts = _content.split(tag!.text)
const prevPart = parts.shift()
children.push(prevPart)
contentLength = contentLength + prevPart.length
children.push(
<Text style={{ color: 'red' }} key={Math.random()}>
{tag!.text}
</Text>
)
switch (tag!.type) {
case 'url':
contentLength = contentLength + 23
break
case 'accounts':
contentLength =
contentLength + tag!.text.split(new RegExp('(@.*)@?'))[1].length
break
case 'hashtags':
contentLength = contentLength + tag!.text.length
break
}
_content = parts.join()
})
children.push(_content)
contentLength = contentLength + _content.length
postDispatch({
type: 'text',
payload: {
count: 500 - contentLength,
raw: content,
formatted: createElement(Text, null, children)
}
})
}, [])
const textInputRef = useRef<TextInput>(null) const textInputRef = useRef<TextInput>(null)
const renderOverlay = (overlay: PostState['overlay']) => { const listFooter = useMemo(() => {
switch (overlay) { return (
case 'emojis': <>
return ( {postState.emoji.active && (
<View style={styles.emojis}> <View style={styles.emojis}>
<ComposeEmojis <ComposeEmojis
textInputRef={textInputRef} textInputRef={textInputRef}
onChangeText={onChangeText}
postState={postState} postState={postState}
postDispatch={postDispatch} postDispatch={postDispatch}
/> />
</View> </View>
) )}
case 'suggestions':
return (
<View style={styles.suggestions}>
<ComposeSuggestions
onChangeText={onChangeText}
postState={postState}
postDispatch={postDispatch}
/>
</View>
)
}
}
return (
<View style={styles.base}>
<ScrollView
style={[styles.contentView]}
alwaysBounceVertical={false}
keyboardDismissMode='interactive'
// child touch event not picked up
keyboardShouldPersistTaps='always'
>
<TextInput
style={[
styles.textInput,
{
color: theme.primary
}
]}
autoCapitalize='none'
autoCorrect={false}
autoFocus
enablesReturnKeyAutomatically
multiline
placeholder='想说点什么'
placeholderTextColor={theme.secondary}
onChangeText={content => onChangeText({ content })}
onSelectionChange={({
nativeEvent: {
selection: { start, end }
}
}) => {
postDispatch({ type: 'selection', payload: { start, end } })
}}
ref={textInputRef}
scrollEnabled
>
<Text>{postState.text.formatted}</Text>
</TextInput>
{renderOverlay(postState.overlay)}
{postState.attachments.length > 0 && ( {postState.attachments.length > 0 && (
<View style={styles.attachments}> <View style={styles.attachments}>
@ -236,7 +97,106 @@ const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
<ComposePoll postState={postState} postDispatch={postDispatch} /> <ComposePoll postState={postState} postDispatch={postDispatch} />
</View> </View>
)} )}
</ScrollView> </>
)
}, [
postState.emoji.active,
postState.attachments.length,
postState.poll.active
])
const listEmpty = useMemo(() => {
if (isFetching) {
return <ActivityIndicator />
}
}, [isFetching])
return (
<View style={styles.base}>
<FlatList
ListHeaderComponent={
<ComposeTextInput
postState={postState}
postDispatch={postDispatch}
textInputRef={textInputRef}
/>
}
ListFooterComponent={listFooter}
ListEmptyComponent={listEmpty}
data={postState.tag && isSuccess ? data[postState.tag.type] : []}
renderItem={({ item, index }) => (
<Pressable
key={index}
onPress={() => {
updateText({
postState: {
...postState,
selection: {
start: postState.tag!.offset,
end: postState.tag!.offset + postState.tag!.text.length + 1
}
},
postDispatch,
newText: item.acct ? `@${item.acct}` : `#${item.name}`,
type: 'suggestion'
})
textInputRef.current?.focus()
}}
style={styles.suggestion}
>
{item.acct ? (
<View
style={[
styles.account,
{ borderBottomColor: theme.border },
index === 0 && {
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: theme.border
}
]}
>
<Image
source={{ uri: item.avatar }}
style={styles.accountAvatar}
/>
<View>
<Text style={[styles.accountName, { color: theme.primary }]}>
{item.emojis.length ? (
<Emojis
content={item.display_name || item.username}
emojis={item.emojis}
size={StyleConstants.Font.Size.S}
/>
) : (
item.display_name || item.username
)}
</Text>
<Text
style={[styles.accountAccount, { color: theme.primary }]}
>
@{item.acct}
</Text>
</View>
</View>
) : (
<View
style={[
styles.hashtag,
{ borderBottomColor: theme.border },
index === 0 && {
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: theme.border
}
]}
>
<Text style={[styles.hashtagText, { color: theme.primary }]}>
#{item.name}
</Text>
</View>
)}
</Pressable>
)}
/>
<ComposeActions <ComposeActions
textInputRef={textInputRef} textInputRef={textInputRef}
postState={postState} postState={postState}
@ -251,13 +211,7 @@ const styles = StyleSheet.create({
flex: 1 flex: 1
}, },
contentView: { flex: 1 }, contentView: { flex: 1 },
textInput: {
fontSize: StyleConstants.Font.Size.M,
marginTop: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.M,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding
},
attachments: { attachments: {
flex: 1, flex: 1,
height: 100 height: 100
@ -266,9 +220,45 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
padding: StyleConstants.Spacing.Global.PagePadding padding: StyleConstants.Spacing.Global.PagePadding
}, },
suggestions: { suggestion: {
flex: 1
},
account: {
flex: 1, flex: 1,
backgroundColor: 'lightyellow' flexDirection: 'row',
alignItems: 'center',
paddingTop: StyleConstants.Spacing.S,
paddingBottom: StyleConstants.Spacing.S,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding,
borderBottomWidth: StyleSheet.hairlineWidth
},
accountAvatar: {
width: StyleConstants.Font.LineHeight.M * 2,
height: StyleConstants.Font.LineHeight.M * 2,
marginRight: StyleConstants.Spacing.S,
borderRadius: StyleConstants.Avatar.S
},
accountName: {
fontSize: StyleConstants.Font.Size.S,
fontWeight: StyleConstants.Font.Weight.Bold,
marginBottom: StyleConstants.Spacing.XS
},
accountAccount: {
fontSize: StyleConstants.Font.Size.S
},
hashtag: {
flex: 1,
paddingTop: StyleConstants.Spacing.S,
paddingBottom: StyleConstants.Spacing.S,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding,
borderBottomWidth: StyleSheet.hairlineWidth
},
hashtagText: {
fontSize: StyleConstants.Font.Size.S,
fontWeight: StyleConstants.Font.Weight.Bold,
marginBottom: StyleConstants.Spacing.XS
}, },
emojis: { emojis: {
flex: 1 flex: 1

View File

@ -1,97 +0,0 @@
import React, { Dispatch } from 'react'
import { ActivityIndicator, Pressable, Text } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import { useQuery } from 'react-query'
import { searchFetch } from 'src/utils/fetches/searchFetch'
import { PostAction, PostState } from '../Compose'
import updateText from './updateText'
declare module 'react' {
function memo<A, B> (
Component: (props: A) => B
): (props: A) => ReactElement | null
}
const Suggestion = React.memo(
({ onChangeText, postState, postDispatch, item, index }) => {
return (
<Pressable
key={index}
onPress={() => {
updateText({
onChangeText,
postState: {
...postState,
selection: {
start: postState.tag.offset,
end: postState.tag.offset + postState.tag.text.length + 1
}
},
newText: `@${item.acct ? item.acct : item.name} `
})
postDispatch({ type: 'overlay', payload: null })
}}
>
<Text>{item.acct ? item.acct : item.name}</Text>
</Pressable>
)
}
)
export interface Props {
onChangeText: any
postState: PostState
postDispatch: Dispatch<PostAction>
}
const ComposeSuggestions: React.FC<Props> = ({
onChangeText,
postState,
postDispatch
}) => {
if (!postState.tag) {
return <></>
}
const { status, data } = useQuery(
['Search', { type: postState.tag.type, term: postState.tag.text }],
searchFetch,
{ retry: false }
)
let content
switch (status) {
case 'success':
content = data[postState.tag.type].length ? (
<FlatList
data={data[postState.tag.type]}
renderItem={({ item, index, separators }) => (
<Suggestion
onChangeText={onChangeText}
postState={postState}
postDispatch={postDispatch}
item={item}
index={index}
/>
)}
/>
) : (
<Text></Text>
)
break
case 'loading':
content = <ActivityIndicator />
break
case 'error':
content = <Text></Text>
break
default:
content = <></>
}
return content
}
export default ComposeSuggestions

View File

@ -0,0 +1,71 @@
import React, { Dispatch, RefObject } from 'react'
import { StyleSheet, Text, TextInput } from 'react-native'
import { StyleConstants } from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
import { PostAction, PostState } from '../Compose'
import formatText from './formatText'
export interface Props {
postState: PostState
postDispatch: Dispatch<PostAction>
textInputRef: RefObject<TextInput>
}
const ComposeTextInput: React.FC<Props> = ({
postState,
postDispatch,
textInputRef
}) => {
const { theme } = useTheme()
return (
<TextInput
style={[
styles.textInput,
{
color: theme.primary
}
]}
autoCapitalize='none'
autoCorrect={false}
autoFocus
enablesReturnKeyAutomatically
multiline
placeholder='想说点什么'
placeholderTextColor={theme.secondary}
onChangeText={content =>
formatText({
postDispatch,
content
})
}
onSelectionChange={({
nativeEvent: {
selection: { start, end }
}
}) => {
postDispatch({ type: 'selection', payload: { start, end } })
}}
ref={textInputRef}
scrollEnabled
>
<Text>{postState.text.formatted}</Text>
</TextInput>
)
}
const styles = StyleSheet.create({
textInput: {
fontSize: StyleConstants.Font.Size.M,
marginTop: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.M,
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
paddingRight: StyleConstants.Spacing.Global.PagePadding
}
})
export default React.memo(
ComposeTextInput,
(prev, next) =>
prev.postState.text.formatted === next.postState.text.formatted
)

View File

@ -0,0 +1,118 @@
import { debounce, differenceWith, isEqual } from 'lodash'
import React, { createElement, Dispatch } from 'react'
import { Text } from 'react-native'
import { RefetchOptions } from 'react-query/types/core/query'
import Autolinker from 'src/modules/autolinker'
import { PostAction, PostState } from '../Compose'
export interface Params {
postDispatch: Dispatch<PostAction>
content: string
refetch?: (options?: RefetchOptions | undefined) => Promise<any>
disableDebounce?: boolean
}
const debouncedSuggestions = debounce((postDispatch, tag) => {
console.log('debounced!!!')
postDispatch({ type: 'tag', payload: tag })
}, 500)
let prevTags: PostState['tag'][] = []
const formatText = ({
postDispatch,
content,
refetch,
disableDebounce = false
}: Params) => {
const tags: PostState['tag'][] = []
Autolinker.link(content, {
email: false,
phone: false,
mention: 'mastodon',
hashtag: 'twitter',
replaceFn: props => {
const type = props.getType()
let newType: 'url' | 'accounts' | 'hashtags'
switch (type) {
case 'mention':
newType = 'accounts'
break
case 'hashtag':
newType = 'hashtags'
break
default:
newType = 'url'
break
}
tags.push({
type: newType,
text: props.getMatchedText(),
offset: props.getOffset()
})
return
}
})
const changedTag = differenceWith(prevTags, tags, isEqual)
// quick delete causes flicking of suggestion box
if (
changedTag.length > 0 &&
tags.length > 0 &&
content.length > 0 &&
!disableDebounce
) {
// console.log('changedTag length')
// console.log(changedTag.length)
// console.log('tags length')
// console.log(tags.length)
// console.log('changed Tag')
// console.log(changedTag)
if (changedTag[0]!.type !== 'url') {
debouncedSuggestions(postDispatch, changedTag[0])
}
} else {
postDispatch({ type: 'tag', payload: undefined })
}
prevTags = tags
let _content = content
let contentLength: number = 0
const children = []
tags.forEach(tag => {
const parts = _content.split(tag!.text)
const prevPart = parts.shift()
children.push(prevPart)
contentLength = contentLength + (prevPart ? prevPart.length : 0)
children.push(
<Text style={{ color: 'red' }} key={Math.random()}>
{tag!.text}
</Text>
)
switch (tag!.type) {
case 'url':
contentLength = contentLength + 23
break
case 'accounts':
contentLength =
contentLength + tag!.text.split(new RegExp('(@.*)@?'))[1].length
break
case 'hashtags':
contentLength = contentLength + tag!.text.length
break
}
_content = parts.join()
})
children.push(_content)
contentLength = contentLength + _content.length
postDispatch({
type: 'text',
payload: {
count: 500 - contentLength,
raw: content,
formatted: createElement(Text, null, children)
}
})
}
export default formatText

View File

@ -1,13 +1,17 @@
import { PostState } from '../Compose' import { Dispatch } from 'react'
import { PostAction, PostState } from '../Compose'
import formatText from './formatText'
const updateText = ({ const updateText = ({
onChangeText,
postState, postState,
newText postDispatch,
newText,
type
}: { }: {
onChangeText: any
postState: PostState postState: PostState
postDispatch: Dispatch<PostAction>
newText: string newText: string
type: 'emoji' | 'suggestion'
}) => { }) => {
if (postState.text.raw.length) { if (postState.text.raw.length) {
const contentFront = postState.text.raw.slice(0, postState.selection.start) const contentFront = postState.text.raw.slice(0, postState.selection.start)
@ -16,16 +20,18 @@ const updateText = ({
const whiteSpaceFront = /\s/g.test(contentFront.slice(-1)) const whiteSpaceFront = /\s/g.test(contentFront.slice(-1))
const whiteSpaceRear = /\s/g.test(contentRear.slice(-1)) const whiteSpaceRear = /\s/g.test(contentRear.slice(-1))
const newTextWithSpace = `${whiteSpaceFront ? '' : ' '}${newText}${ const newTextWithSpace = `${
whiteSpaceRear ? '' : ' ' whiteSpaceFront || type === 'suggestion' ? '' : ' '
}` }${newText}${whiteSpaceRear ? '' : ' '}`
onChangeText({ formatText({
postDispatch,
content: [contentFront, newTextWithSpace, contentRear].join(''), content: [contentFront, newTextWithSpace, contentRear].join(''),
disableDebounce: true disableDebounce: true
}) })
} else { } else {
onChangeText({ formatText({
postDispatch,
content: `${newText} `, content: `${newText} `,
disableDebounce: true disableDebounce: true
}) })

View File

@ -19,5 +19,9 @@ export const searchFetch = async (
endpoint: 'search', endpoint: 'search',
query: { type, q: term, limit } query: { type, q: term, limit }
}) })
console.log('search query')
console.log({ type, q: term, limit })
console.log('search result')
console.log(res.body)
return Promise.resolve(res.body) return Promise.resolve(res.body)
} }