135 lines
3.3 KiB
TypeScript
135 lines
3.3 KiB
TypeScript
import React from 'react'
|
|
import {StyleSheet, View} from 'react-native'
|
|
import {useEditor, EditorContent, JSONContent} from '@tiptap/react'
|
|
import {Document} from '@tiptap/extension-document'
|
|
import {Link} from '@tiptap/extension-link'
|
|
import {Mention} from '@tiptap/extension-mention'
|
|
import {Paragraph} from '@tiptap/extension-paragraph'
|
|
import {Placeholder} from '@tiptap/extension-placeholder'
|
|
import {Text} from '@tiptap/extension-text'
|
|
import isEqual from 'lodash.isequal'
|
|
import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
|
|
import {createSuggestion} from './web/Autocomplete'
|
|
|
|
export interface TextInputRef {
|
|
focus: () => void
|
|
blur: () => void
|
|
}
|
|
|
|
interface TextInputProps {
|
|
text: string
|
|
placeholder: string
|
|
suggestedLinks: Set<string>
|
|
autocompleteView: UserAutocompleteViewModel
|
|
onTextChanged: (v: string) => void
|
|
onPhotoPasted: (uri: string) => void
|
|
onSuggestedLinksChanged: (uris: Set<string>) => void
|
|
onError: (err: string) => void
|
|
}
|
|
|
|
export const TextInput = React.forwardRef(
|
|
(
|
|
{
|
|
text,
|
|
placeholder,
|
|
suggestedLinks,
|
|
autocompleteView,
|
|
onTextChanged,
|
|
// onPhotoPasted, TODO
|
|
onSuggestedLinksChanged,
|
|
}: // onError, TODO
|
|
TextInputProps,
|
|
ref,
|
|
) => {
|
|
const editor = useEditor({
|
|
extensions: [
|
|
Document,
|
|
Link.configure({
|
|
protocols: ['http', 'https'],
|
|
autolink: true,
|
|
}),
|
|
Mention.configure({
|
|
HTMLAttributes: {
|
|
class: 'mention',
|
|
},
|
|
suggestion: createSuggestion({autocompleteView}),
|
|
}),
|
|
Paragraph,
|
|
Placeholder.configure({
|
|
placeholder,
|
|
}),
|
|
Text,
|
|
],
|
|
content: text,
|
|
autofocus: true,
|
|
editable: true,
|
|
injectCSS: true,
|
|
onUpdate({editor: editorProp}) {
|
|
const json = editorProp.getJSON()
|
|
|
|
const newText = editorJsonToText(json).trim()
|
|
onTextChanged(newText)
|
|
|
|
const newSuggestedLinks = new Set(editorJsonToLinks(json))
|
|
if (!isEqual(newSuggestedLinks, suggestedLinks)) {
|
|
onSuggestedLinksChanged(newSuggestedLinks)
|
|
}
|
|
},
|
|
})
|
|
|
|
React.useImperativeHandle(ref, () => ({
|
|
focus: () => {}, // TODO
|
|
blur: () => {}, // TODO
|
|
}))
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<EditorContent editor={editor} />
|
|
</View>
|
|
)
|
|
},
|
|
)
|
|
|
|
function editorJsonToText(json: JSONContent): string {
|
|
let text = ''
|
|
if (json.type === 'doc' || json.type === 'paragraph') {
|
|
if (json.content?.length) {
|
|
for (const node of json.content) {
|
|
text += editorJsonToText(node)
|
|
}
|
|
}
|
|
text += '\n'
|
|
} else if (json.type === 'text') {
|
|
text += json.text || ''
|
|
} else if (json.type === 'mention') {
|
|
text += `@${json.attrs?.id || ''}`
|
|
}
|
|
return text
|
|
}
|
|
|
|
function editorJsonToLinks(json: JSONContent): string[] {
|
|
let links: string[] = []
|
|
if (json.content?.length) {
|
|
for (const node of json.content) {
|
|
links = links.concat(editorJsonToLinks(node))
|
|
}
|
|
}
|
|
|
|
const link = json.marks?.find(m => m.type === 'link')
|
|
if (link?.attrs?.href) {
|
|
links.push(link.attrs.href)
|
|
}
|
|
|
|
return links
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
alignSelf: 'flex-start',
|
|
padding: 5,
|
|
marginLeft: 8,
|
|
marginBottom: 10,
|
|
},
|
|
})
|