fastcomments-react-native-sdk
Version:
React Native FastComments Components. Add live commenting to any React Native application.
205 lines (204 loc) • 10.7 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { FastCommentsImageAsset } from "../types";
import { Text, View, ActivityIndicator, ScrollView, TouchableOpacity, Image } from "react-native";
import { useEffect, useState } from "react";
import { RichText, useEditorBridge, useEditorContent, DEFAULT_TOOLBAR_ITEMS } from '@10play/tentap-editor';
export function CommentTextArea10Tap({ emoticonBarConfig, focusObserver, state, styles, output, onFocus: _onFocus, pickImage, pickGIF, value, toolbarButtons, }) {
const maxLength = state.config.maxCommentCharacterLength || 2000;
const hasDarkBackground = state.config.hasDarkBackground;
const [imageUploadProgress, setImageUploadProgress] = useState(null);
// Default toolbar button configuration - code disabled by default
const defaultButtons = {
bold: true,
italic: true,
underline: true,
strikethrough: true,
code: false,
image: true,
gif: true,
};
const buttons = { ...defaultButtons, ...toolbarButtons };
const editor = useEditorBridge({
autofocus: false,
avoidIosKeyboard: true,
initialContent: value || '<p></p>',
});
// Handle focus/blur events
useEffect(() => {
// 10tap-editor handles focus/blur internally
// We'll manage focus state through the editor bridge if needed
return () => { };
}, [_onFocus]);
const content = useEditorContent(editor, { type: 'html' });
useEffect(() => {
if (value !== undefined && value !== content) {
editor.setContent(value);
}
}, [value]);
useEffect(() => {
if (focusObserver) {
focusObserver.setFocused = (focused) => {
if (focused) {
editor.focus();
}
else {
editor.blur();
}
};
}
}, [editor, focusObserver]);
output.getValue = () => {
return content?.substring(0, maxLength) || '';
};
const handleImageUpload = async () => {
if (!pickImage)
return;
try {
const photoData = await pickImage();
if (!photoData)
return;
if (typeof photoData === 'string' && photoData.startsWith('http')) {
editor.setImage(photoData);
}
else {
setImageUploadProgress(0);
const formData = new FormData();
formData.append('file', photoData);
const xhr = new XMLHttpRequest();
xhr.open('POST', state.apiHost + '/upload-image/' + state.config.tenantId);
xhr.upload.onprogress = (progressEvent) => {
if (progressEvent.lengthComputable) {
const progress = progressEvent.loaded / progressEvent.total;
setImageUploadProgress(progress);
}
};
const url = await new Promise((resolve, reject) => {
xhr.onload = () => {
setImageUploadProgress(null);
if (xhr.status === 200) {
const url = JSON.parse(xhr.response).url;
resolve(url);
}
else {
reject(new Error(xhr.response));
}
};
xhr.onerror = () => {
setImageUploadProgress(null);
reject(new Error('Upload failed'));
};
xhr.send(formData);
});
editor.setImage(url);
}
}
catch (error) {
console.error('Image upload failed:', error);
setImageUploadProgress(null);
}
};
const handleGIFPick = async () => {
if (!pickGIF)
return;
try {
const gifUrl = await pickGIF();
if (gifUrl) {
editor.setImage(gifUrl);
}
}
catch (error) {
console.error('GIF pick failed:', error);
}
};
const customToolbarItems = [...DEFAULT_TOOLBAR_ITEMS];
if (pickImage) {
customToolbarItems.push({
onPress: () => {
handleImageUpload();
return () => { };
},
disabled: () => false,
active: () => false,
image: () => state.imageAssets[hasDarkBackground
? FastCommentsImageAsset.ICON_IMAGE_UPLOAD_WHITE
: FastCommentsImageAsset.ICON_IMAGE_UPLOAD],
});
}
if (pickGIF) {
customToolbarItems.push({
onPress: () => {
handleGIFPick();
return () => { };
},
disabled: () => false,
active: () => false,
image: () => state.imageAssets[hasDarkBackground
? FastCommentsImageAsset.ICON_GIF
: FastCommentsImageAsset.ICON_GIF],
});
}
// Setup emoticon bar config
if (emoticonBarConfig) {
emoticonBarConfig.addEmoticon = (src) => {
editor.setImage(src);
};
}
// Common button style for toolbar buttons - using app's existing colors
const toolbarButtonStyle = {
backgroundColor: hasDarkBackground ? '#444' : 'white',
paddingHorizontal: 8,
paddingVertical: 6,
borderRadius: 4,
borderWidth: 1,
borderColor: styles.commentTextArea?.textarea?.borderColor || (hasDarkBackground ? '#555' : '#a2a2a2'),
minWidth: 28,
alignItems: 'center',
justifyContent: 'center',
marginRight: 6,
};
return (_jsxs(View, { style: { width: '100%', flex: 1 }, children: [_jsx(View, { style: [
styles.commentTextArea?.textarea,
{
minHeight: state.config.useSingleLineCommentInput ? 40 : 100,
borderRadius: styles.commentTextArea?.textarea?.borderRadius || 11,
overflow: 'hidden',
paddingHorizontal: 8,
paddingVertical: 4, // Minimal vertical padding
// backgroundColor will come from styles.commentTextArea?.textarea?.backgroundColor if set, otherwise transparent
}
], children: _jsx(RichText, { editor: editor, style: {
minHeight: state.config.useSingleLineCommentInput ? 32 : 92,
flex: 1,
backgroundColor: 'transparent', // Ensure RichText itself is transparent
} }) }), emoticonBarConfig?.emoticons && (_jsx(ScrollView, { horizontal: true, style: styles.commentTextAreaEmoticonBar?.root, children: emoticonBarConfig.emoticons.map(([src, element], index) => (_jsx(TouchableOpacity, { onPress: () => emoticonBarConfig.addEmoticon?.(src), style: styles.commentTextAreaEmoticonBar?.button, children: element }, src + index))) })), _jsxs(View, { style: {
backgroundColor: hasDarkBackground ? '#2c2c2c' : '#f8f8f8',
paddingHorizontal: 12,
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
flexWrap: 'wrap',
}, children: [buttons.bold && (_jsx(TouchableOpacity, { style: toolbarButtonStyle, onPress: () => editor.toggleBold(), activeOpacity: 0.7, children: _jsx(Text, { style: {
fontWeight: 'bold',
fontSize: 14,
color: hasDarkBackground ? '#fff' : '#333'
}, children: "B" }) })), buttons.italic && (_jsx(TouchableOpacity, { style: toolbarButtonStyle, onPress: () => editor.toggleItalic(), activeOpacity: 0.7, children: _jsx(Text, { style: {
fontStyle: 'italic',
fontSize: 14,
color: hasDarkBackground ? '#fff' : '#333'
}, children: "I" }) })), buttons.underline && (_jsx(TouchableOpacity, { style: toolbarButtonStyle, onPress: () => editor.toggleUnderline(), activeOpacity: 0.7, children: _jsx(Text, { style: {
textDecorationLine: 'underline',
fontSize: 14,
color: hasDarkBackground ? '#fff' : '#333'
}, children: "U" }) })), buttons.strikethrough && (_jsx(TouchableOpacity, { style: toolbarButtonStyle, onPress: () => editor.toggleStrike(), activeOpacity: 0.7, children: _jsx(Text, { style: {
textDecorationLine: 'line-through',
fontSize: 14,
color: hasDarkBackground ? '#fff' : '#333'
}, children: "S" }) })), buttons.code && (_jsx(TouchableOpacity, { style: toolbarButtonStyle, onPress: () => editor.toggleCode(), activeOpacity: 0.7, children: _jsx(Text, { style: {
fontFamily: 'monospace',
fontSize: 12,
color: hasDarkBackground ? '#fff' : '#333'
}, children: "<>" }) })), buttons.image && pickImage && (_jsx(TouchableOpacity, { style: toolbarButtonStyle, onPress: handleImageUpload, activeOpacity: 0.7, children: _jsx(Image, { source: state.imageAssets[hasDarkBackground
? FastCommentsImageAsset.ICON_IMAGE_UPLOAD_WHITE
: FastCommentsImageAsset.ICON_IMAGE_UPLOAD], style: { width: 16, height: 16 } }) })), buttons.gif && pickGIF && (_jsx(TouchableOpacity, { style: toolbarButtonStyle, onPress: handleGIFPick, activeOpacity: 0.7, children: _jsx(Image, { source: state.imageAssets[FastCommentsImageAsset.ICON_GIF], style: { width: 16, height: 16 } }) }))] }), imageUploadProgress !== null && (_jsx(View, { style: styles.commentTextArea?.imageUploadModalCenteredView, children: _jsxs(View, { style: styles.commentTextArea?.imageUploadModalContent, children: [_jsx(ActivityIndicator, { size: styles.commentTextArea?.imageUploadModalProgressSpinnerSize }), _jsxs(Text, { style: styles.commentTextArea?.imageUploadModalProgressText, children: [Math.round(imageUploadProgress * 100), "%"] })] }) }))] }));
}