tooot/src/screens/Shared/Compose/formatText.tsx

128 lines
3.4 KiB
TypeScript
Raw Normal View History

2020-12-04 01:17:10 +01:00
import { debounce, differenceWith, isEqual } from 'lodash'
import React, { createElement, Dispatch } from 'react'
import { Text } from 'react-native'
2020-12-18 23:58:53 +01:00
import { FetchOptions } from 'react-query/types/core/query'
2020-12-13 14:04:25 +01:00
import Autolinker from '@root/modules/autolinker'
import { useTheme } from '@utils/styles/ThemeManager'
2020-12-30 00:56:25 +01:00
import { ComposeAction, ComposeState } from './utils/types'
2020-12-04 01:17:10 +01:00
export interface Params {
textInput: ComposeState['textInputFocus']['current']
2020-12-18 23:58:53 +01:00
composeDispatch: Dispatch<ComposeAction>
2020-12-04 01:17:10 +01:00
content: string
2020-12-18 23:58:53 +01:00
refetch?: (options?: FetchOptions | undefined) => Promise<any>
2020-12-04 01:17:10 +01:00
disableDebounce?: boolean
}
2020-12-06 22:32:36 +01:00
const TagText = ({ text }: { text: string }) => {
const { theme } = useTheme()
2020-12-26 00:40:27 +01:00
return <Text style={{ color: theme.blue }}>{text}</Text>
2020-12-06 22:32:36 +01:00
}
2020-12-04 19:04:23 +01:00
const debouncedSuggestions = debounce(
2020-12-07 12:31:40 +01:00
(composeDispatch, tag) => {
composeDispatch({ type: 'tag', payload: tag })
2020-12-04 19:04:23 +01:00
},
500,
{
trailing: true
}
)
2020-12-04 01:17:10 +01:00
2020-12-07 12:31:40 +01:00
let prevTags: ComposeState['tag'][] = []
2020-12-04 01:17:10 +01:00
const formatText = ({
textInput,
2020-12-07 12:31:40 +01:00
composeDispatch,
2020-12-04 01:17:10 +01:00
content,
disableDebounce = false
}: Params) => {
2020-12-07 12:31:40 +01:00
const tags: ComposeState['tag'][] = []
2020-12-04 01:17:10 +01:00
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(),
length: props.getMatchedText().length
2020-12-04 01:17:10 +01:00
})
return
}
})
2020-12-04 19:04:23 +01:00
const changedTag = differenceWith(tags, prevTags, isEqual)
2021-01-22 01:34:20 +01:00
if (changedTag.length > 0 && !disableDebounce) {
if (changedTag[0]?.type !== 'url') {
2020-12-07 12:31:40 +01:00
debouncedSuggestions(composeDispatch, changedTag[0])
2020-12-04 01:17:10 +01:00
}
} else {
2020-12-04 19:04:23 +01:00
debouncedSuggestions.cancel()
2020-12-07 12:31:40 +01:00
composeDispatch({ type: 'tag', payload: undefined })
2020-12-04 01:17:10 +01:00
}
prevTags = tags
let _content = content
let pointer = 0
2020-12-04 01:17:10 +01:00
let contentLength: number = 0
const children = []
tags.forEach((tag, index) => {
2021-01-22 01:34:20 +01:00
if (tag) {
const prev = _content.substr(0, tag.offset - pointer)
const main = _content.substr(tag.offset - pointer, tag.length)
const next = _content.substr(tag.offset - pointer + tag.length)
children.push(prev)
contentLength = contentLength + prev.length
children.push(<TagText key={index} text={main} />)
switch (tag.type) {
case 'url':
contentLength = contentLength + 23
break
case 'accounts':
const theMatch = main.match(/@/g)
if (theMatch && theMatch.length > 1) {
contentLength =
contentLength + main.split(new RegExp('(@.*?)@'))[1].length
} else {
contentLength = contentLength + main.length
}
break
case 'hashtags':
2021-01-01 16:48:16 +01:00
contentLength = contentLength + main.length
2021-01-22 01:34:20 +01:00
break
}
_content = next
pointer = pointer + prev.length + tag.length
2020-12-04 01:17:10 +01:00
}
})
children.push(_content)
contentLength = contentLength + _content.length
2020-12-07 12:31:40 +01:00
composeDispatch({
type: textInput,
2020-12-04 01:17:10 +01:00
payload: {
2020-12-06 23:51:13 +01:00
count: contentLength,
2020-12-04 01:17:10 +01:00
raw: content,
formatted: createElement(Text, null, children)
}
})
}
export default formatText