tooot/src/screens/Compose/formatText.tsx

165 lines
4.5 KiB
TypeScript
Raw Normal View History

2022-09-20 22:23:01 +02:00
import LinkifyIt from 'linkify-it'
2020-12-04 01:17:10 +01:00
import { debounce, differenceWith, isEqual } from 'lodash'
2022-09-20 22:23:01 +02:00
import React, { Dispatch } from 'react'
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 { useTheme } from '@utils/styles/ThemeManager'
2020-12-30 00:56:25 +01:00
import { ComposeAction, ComposeState } from './utils/types'
2021-11-15 22:34:43 +01:00
import { instanceConfigurationStatusCharsURL } from './Root'
import CustomText from '@components/Text'
2022-09-20 22:23:01 +02:00
import { emojis } from '@components/Emojis'
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 }) => {
2022-02-12 14:51:01 +01:00
const { colors } = useTheme()
2020-12-06 22:32:36 +01:00
return <CustomText style={{ color: colors.blue }}>{text}</CustomText>
2020-12-06 22:32:36 +01:00
}
2022-09-20 22:23:01 +02:00
const linkify = new LinkifyIt()
linkify
.set({ fuzzyLink: false, fuzzyEmail: false })
.add('@', {
validate: function (text, pos, self) {
var tail = text.slice(pos)
if (!self.re.mention) {
self.re.mention = new RegExp('^\\S+')
}
if (self.re.mention.test(tail)) {
return tail.match(self.re.mention)![0].length
}
return 0
}
})
.add('#', {
validate: function (text, pos, self) {
var tail = text.slice(pos)
if (!self.re.hashtag) {
self.re.hashtag = new RegExp('^[A-Za-z0-9_]+')
}
if (self.re.hashtag.test(tail)) {
return tail.match(self.re.hashtag)![0].length
}
return 0
}
})
.add(':', {
validate: function (text, pos, self) {
var tail = text.slice(pos)
if (!self.re.emoji) {
self.re.emoji = new RegExp('^(?:([^:]+):)')
}
if (self.re.emoji.test(tail)) {
return tail.match(self.re.emoji)![0].length
}
return 0
}
})
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,
2022-09-20 22:23:01 +02:00
{ trailing: true }
2020-12-04 19:04:23 +01:00
)
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
2022-09-20 22:23:01 +02:00
const formatText = ({ textInput, composeDispatch, content, disableDebounce = false }: Params) => {
const tags = linkify.match(content)
if (!tags) {
composeDispatch({
type: textInput,
payload: {
count: content.length,
raw: content,
formatted: <CustomText children={content} />
2020-12-04 01:17:10 +01:00
}
2022-09-20 22:23:01 +02:00
})
return
}
2020-12-04 01:17:10 +01:00
2022-09-20 22:23:01 +02:00
const changedTag: LinkifyIt.Match[] = differenceWith(tags, prevTags, isEqual)
2021-01-22 01:34:20 +01:00
if (changedTag.length > 0 && !disableDebounce) {
2022-09-20 22:23:01 +02:00
if (changedTag[0]?.schema === '@' || changedTag[0]?.schema === '#') {
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) => {
2022-09-20 22:23:01 +02:00
const prev = _content.substring(0, tag.index - pointer)
const main = _content.substring(tag.index - pointer, tag.lastIndex - pointer)
const next = _content.substring(tag.lastIndex - pointer)
children.push(prev)
contentLength = contentLength + prev.length
if (tag.schema === ':') {
if (emojis.current?.length) {
const matchedEmoji = emojis.current.filter(
emojisSection =>
emojisSection.data.filter(
emojisGroup => emojisGroup.filter(emoji => `:${emoji.shortcode}:` === main).length
).length
).length
if (matchedEmoji) {
children.push(<TagText key={index} text={main} />)
} else {
children.push(main)
}
}
} else {
2021-01-22 01:34:20 +01:00
children.push(<TagText key={index} text={main} />)
2022-09-20 22:23:01 +02:00
}
switch (tag.schema) {
case '@':
const theMatch = main.match(/@/g)
if (theMatch && theMatch.length > 1) {
contentLength = contentLength + main.split(new RegExp('(@.*?)@'))[1].length
} else {
2021-01-01 16:48:16 +01:00
contentLength = contentLength + main.length
2022-09-20 22:23:01 +02:00
}
break
case '#':
case ':':
contentLength = contentLength + main.length
break
default:
contentLength = contentLength + instanceConfigurationStatusCharsURL
break
2020-12-04 01:17:10 +01:00
}
2022-09-20 22:23:01 +02:00
_content = next
pointer = pointer + prev.length + tag.lastIndex - tag.index
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,
2022-09-20 22:23:01 +02:00
formatted: <CustomText children={children} />
2020-12-04 01:17:10 +01:00
}
})
}
export default formatText