Files
EMI-ExpoAPP/components/Post.js

372 lines
14 KiB
JavaScript

import React, { useMemo, useRef, useState } from 'react';
import { Text, Pressable, StyleSheet, View, Share, Alert, Linking, Animated, PanResponder } from 'react-native';
import { Button, Card, Chip } from 'react-native-paper';
import API from './../API.js';
import UserName from './UserName.js';
import Media from './Media.js';
import Comment from "./Comment";
import NewComment from './NewComment.js';
import Moment from 'moment';
import { useSnapshot } from 'valtio';
import GlobalState from '../contexts/GlobalState.js';
import i18n from "../i18nMessages.js";
import ProfilePhotoCircle from './ProfilePhotoCircle.js';
import { posthog } from './../PostHog.js';
import { useNavigation } from '@react-navigation/native';
import ParsedText from 'react-native-parsed-text';
import BibleEmbeddedView from './BibleEmbeddedView.js';
import { stripBibleTokens } from '../utils/bibleReferences.js';
let Post = (props) => {
const gState = useSnapshot(GlobalState);
const viewer = gState.me;
let [showCommentsB, changeshowCommentsB] = useState(props.showComments || false);
let [post, changePost] = useState(props.post);
const [deleted, setDeleted] = useState(false);
let [likes, changeLikes] = useState(Object.keys(post.reactions).length);
let [bookmarked, changeBookmarked] = useState(post.bookmarks && post.bookmarks.includes(viewer._id));
const isOwner = String(post.profileid || '') === String(viewer?._id || '');
const swipeX = useRef(new Animated.Value(0)).current;
const mediaGestureActiveRef = useRef(false);
const SWIPE_WIDTH = 86;
let toProfileText = post.toProfile && post.toProfile !== post.profileid ?
<ProfilePhotoCircle profileid={post.toProfile} small={true} /> : undefined;
let cleanContent = stripInlineTags(stripBibleTokens(post.content));
const navigation = useNavigation();
//cleanContent = convertLinks(cleanContent);
const newComentAdded = (commentData) => {
let newPostObj = { ...post };
newPostObj.comments.push(commentData);
changePost(newPostObj);
};
const newPostReaction = () => {
let newPostObj = { ...post };
if (!newPostObj.reactions[viewer._id]) {
changeLikes(likes + 1);
newPostObj.reactions[viewer._id] = { type: "like" };
API.newPostReaction(post._id);
posthog.capture(
'post_clicked',
{
post_id: post._id,
}
);
} else {
changeLikes(likes - 1);
delete newPostObj.reactions[viewer._id];
API.removePostReaction(viewer._id);
}
changePost(newPostObj);
}
const newPostBookmark = () => {
if (!post.bookmarks || !post.bookmarks.includes(viewer._id)) {
changeBookmarked(true);
if (!post.bookmarks) post.bookmarks = [];
post.bookmarks.push(viewer._id);
API.newPostBookmark(post._id);
} else {
changeBookmarked(false);
post.bookmarks = post.bookmarks.filter(id => id != viewer._id);
API.removePostBookmark(post._id)
}
}
const handleTagPress = (tag) => {
// Alert.alert("tag pressed", `You pressed the tag: ${tag}`);
// You can navigate to another screen or perform any other action here
//remove hastag from tag
tag = tag.replace("#", "");
navigation.navigate("Tags", { tag: tag });
};
const handleLinkPress = (url) => {
Linking.canOpenURL(url)
.then((supported) => {
if (supported) {
Linking.openURL(url);
} else {
Alert.alert('Error', 'Unable to open the link.');
}
})
.catch((err) => console.error('An error occurred', err));
};
const handleLinkLongPress = (url) => {
Share.share({
url: url
});
};
const closeSwipe = () => {
Animated.spring(swipeX, { toValue: 0, useNativeDriver: true }).start();
};
const deletePost = async () => {
const result = await API.deletePost(post._id);
if (result?.status !== "ok") {
Alert.alert("Could not delete post", result?.status || "Please try again.");
return closeSwipe();
}
setDeleted(true);
};
const confirmDelete = () => {
Alert.alert(
"Delete post?",
"This action cannot be undone.",
[
{ text: "Cancel", style: "cancel", onPress: closeSwipe },
{ text: "Delete", style: "destructive", onPress: deletePost },
]
);
};
const panResponder = useMemo(() => PanResponder.create({
onMoveShouldSetPanResponder: (_, gestureState) =>
isOwner &&
!mediaGestureActiveRef.current &&
Math.abs(gestureState.dx) > 8 &&
Math.abs(gestureState.dx) > Math.abs(gestureState.dy),
onPanResponderMove: (_, gestureState) => {
if (gestureState.dx > 0) {
swipeX.setValue(0);
return;
}
swipeX.setValue(Math.max(gestureState.dx, -SWIPE_WIDTH));
},
onPanResponderRelease: (_, gestureState) => {
const open = gestureState.dx < -SWIPE_WIDTH / 2;
Animated.spring(swipeX, {
toValue: open ? -SWIPE_WIDTH : 0,
useNativeDriver: true,
}).start();
},
onPanResponderTerminate: closeSwipe,
}), [isOwner, swipeX]);
if (deleted) return null;
const postCard = (
<Card style={styles.card}>
<Card.Content style={{
padding: 0,
margin: 0,
marginBottom: 0
}}>
{!post.nonOrganicType ?
<View>
<ProfilePhotoCircle profileid={post.profileid} />
<View style={{ flexDirection: 'row', alignItems: 'center', margin: 0, marginLeft: 37, marginTop: -14, paddingBottom: 2 }}>
{toProfileText}
</View>
<Pressable onLongPress={() => {
if (cleanContent.length > 10) {
Share.share({
message: cleanContent
});
}
}}>
<ParsedText
style={styles.text}
parse={[
{ pattern: /#(\w+)/, style: styles.tag, onPress: handleTagPress },
{ pattern: /(https?:\/\/[^\s]+)/, style: styles.link, onPress: handleLinkPress, onLongPress: handleLinkLongPress },
]}
>
{cleanContent}
</ParsedText>
</Pressable>
<View style={{ paddingLeft: 40, paddingRight: 8 }}>
<BibleEmbeddedView content={post.content} openChapterOnPress />
</View>
<View
onStartShouldSetResponderCapture={() => {
mediaGestureActiveRef.current = true;
return false;
}}
onMoveShouldSetResponderCapture={() => {
mediaGestureActiveRef.current = true;
return false;
}}
onResponderRelease={() => {
mediaGestureActiveRef.current = false;
}}
onResponderTerminate={() => {
mediaGestureActiveRef.current = false;
}}
onTouchEnd={() => {
mediaGestureActiveRef.current = false;
}}
onTouchCancel={() => {
mediaGestureActiveRef.current = false;
}}
>
<Media content={post.content} postId={post._id} post={post} style={{ paddingTop: 2 }} />
</View>
</View> :
<View>
<Chip icon="new-releases" style={{ width: 100 }} >{i18n.t("message.news")}</Chip>
<Text style={{ fontSize: 18 }}>{cleanContent}</Text>
<View
onStartShouldSetResponderCapture={() => {
mediaGestureActiveRef.current = true;
return false;
}}
onMoveShouldSetResponderCapture={() => {
mediaGestureActiveRef.current = true;
return false;
}}
onResponderRelease={() => {
mediaGestureActiveRef.current = false;
}}
onResponderTerminate={() => {
mediaGestureActiveRef.current = false;
}}
onTouchEnd={() => {
mediaGestureActiveRef.current = false;
}}
onTouchCancel={() => {
mediaGestureActiveRef.current = false;
}}
>
<Media content={post.content} />
</View>
</View>
}
</Card.Content>
<Card.Actions style={{ flexDirection: "row", flow: 4, fontSize: 16, marginLeft: 36, marginTop: -10 }}>
<Button
icon={post.reactions[viewer._id] ? "favorite" : "favorite-border"}
labelStyle={{ fontSize: 17 }}
style={{ flow: 1 }}
onPress={newPostReaction}
color="#555"
>
{likes}
</Button>
<Button icon="forum" labelStyle={{ fontSize: 17 }} style={{ flow: 1 }}
onPress={() => {
// changeshowCommentsB(!showCommentsB) // Show comments
// Change view to single post
navigation.navigate("SinglePost", {
postid: post._id,
});
}}
color="#555"
>
{post.comments.length}
</Button>
<Button icon="ios-share" style={{ flow: 1 }} labelStyle={{ fontSize: 17 }}
color="#555"
onPress={() => {
Share.share({
//message: "https://social.emmint.com/post/" + props.post._id,
url: "https://social.emmint.com/feed/post/" + props.post._id
});
}}></Button>
<Button
icon={!bookmarked ? "bookmark-outline" : "bookmark"}
style={{ flow: 1 }}
labelStyle={{ fontSize: 17 }}
onPress={newPostBookmark}
color="#555"
>
</Button>
<Text style={{ fontWeight: 'normal', fontSize: 12 }}>
{" " + Moment(post.createdAt).fromNow()}
</Text>
</Card.Actions>
{showCommentsB && <NewComment postid={post._id} newComentAdded={newComentAdded} />}
{
showCommentsB &&
<View>
{post.comments.map((comment, index) => (
<Comment
key={`${comment?.createdAt || "comment"}-${index}`}
comment={comment}
postid={post._id}
/>
))}
</View>
}
</Card>
);
if (!isOwner) return postCard;
return (
<View style={styles.swipeWrap}>
<View style={styles.deleteActionWrap}>
<Pressable style={styles.deleteActionBtn} onPress={confirmDelete}>
<Text style={styles.deleteActionText}>{i18n.t("message.delete")}</Text>
</Pressable>
</View>
<Animated.View style={{ transform: [{ translateX: swipeX }] }} {...panResponder.panHandlers}>
{postCard}
</Animated.View>
</View>
);
}
export default React.memo(Post);
const styles = StyleSheet.create({
userName: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 5,
fontSize: 17,
},
card: {
margin: 0,
backgroundColor: "#FAFAFA",
borderRadius: 0,
marginBottom: 2,
padding: 0
},
swipeWrap: {
position: "relative",
backgroundColor: "#edf2f7",
},
deleteActionWrap: {
position: "absolute",
right: 0,
top: 0,
bottom: 2,
width: 86,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#b3261e",
},
deleteActionBtn: {
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center",
},
deleteActionText: {
color: "#fff",
fontWeight: "700",
},
comment: {
margin: 8,
marginTop: 0,
padding: 8
},
text: {
fontSize: 16,
padding: 3,
paddingLeft: 40
},
tag: {
color: '#77B5FE',
textDecorationLine: 'underline',
},
link: {
color: '#77B5FE',
textDecorationLine: 'underline',
},
});
const stripInlineTags = (content = "") => {
return String(content || "")
.replace(/@[A-Za-z]+:[^\s]+/g, "")
.replace(/[ \t]{2,}/g, " ")
.replace(/[ \t]+\n/g, "\n")
.trim();
};