mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-14 21:14:15 +00:00
246 lines
6.8 KiB
JavaScript
246 lines
6.8 KiB
JavaScript
import React from "react"
|
|
import * as antd from "antd"
|
|
import { Icons } from "components/Icons"
|
|
import { LikeButton } from "components"
|
|
import moment from "moment"
|
|
import classnames from "classnames"
|
|
|
|
import { User } from "models"
|
|
|
|
import CSSMotion from "rc-animate/lib/CSSMotion"
|
|
import useLayoutEffect from "rc-util/lib/hooks/useLayoutEffect"
|
|
|
|
import "./index.less"
|
|
|
|
const getCurrentHeight = (node) => ({ height: node.offsetHeight })
|
|
|
|
const getMaxHeight = (node) => {
|
|
return { height: node.scrollHeight }
|
|
}
|
|
|
|
const getCollapsedHeight = () => ({ height: 0, opacity: 0 })
|
|
|
|
function PostHeader(props) {
|
|
const [timeAgo, setTimeAgo] = React.useState(0)
|
|
|
|
const goToProfile = () => {
|
|
window.app.goToAccount(props.postData.user?.username)
|
|
}
|
|
|
|
const updateTimeAgo = () => {
|
|
setTimeAgo(moment(props.postData.created_at ?? "").fromNow())
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
updateTimeAgo()
|
|
|
|
const interval = setInterval(() => {
|
|
updateTimeAgo()
|
|
}, 10000)
|
|
|
|
return () => {
|
|
clearInterval(interval)
|
|
}
|
|
}, [props.postData.created_at])
|
|
|
|
return <div className="postHeader">
|
|
<div className="userInfo">
|
|
<div className="avatar">
|
|
<antd.Avatar shape="square" src={props.postData.user?.avatar} />
|
|
</div>
|
|
<div className="info">
|
|
<div>
|
|
<h1 onClick={goToProfile}>
|
|
{props.postData.user?.fullName ?? `@${props.postData.user?.username}`}
|
|
{props.postData.user?.verified && <Icons.verifiedBadge />}
|
|
</h1>
|
|
</div>
|
|
|
|
<div>
|
|
{timeAgo}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="postStadistics">
|
|
<div className="item">
|
|
<Icons.Heart className={classnames("icon", { ["filled"]: props.isLiked })} />
|
|
<div className="value">
|
|
{props.likes}
|
|
</div>
|
|
</div>
|
|
<div className="item">
|
|
<Icons.MessageSquare />
|
|
<div className="value">
|
|
{props.comments}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
function PostContent({ message }) {
|
|
return <div className="content">
|
|
{message}
|
|
</div>
|
|
}
|
|
|
|
function PostActions(props) {
|
|
return <div className="actions">
|
|
<div className="action" id="likes">
|
|
<div className="icon">
|
|
<LikeButton defaultLiked={props.defaultLiked} onClick={props.onClickLike} />
|
|
</div>
|
|
</div>
|
|
<div className="action" id="comments" onClick={props.onClickComments}>
|
|
<div className="icon">
|
|
<Icons.MessageSquare className="icon" />
|
|
</div>
|
|
</div>
|
|
<div className="action" id="save" onClick={props.onClickSave}>
|
|
<div className="icon">
|
|
<Icons.Bookmark />
|
|
</div>
|
|
</div>
|
|
{props.isSelf && <div className="action" id="selfMenu" onClick={props.onClickSelfMenu}>
|
|
<div className="icon">
|
|
<Icons.MoreVertical />
|
|
</div>
|
|
</div>}
|
|
</div>
|
|
}
|
|
|
|
export class PostCard extends React.Component {
|
|
state = {
|
|
loading: true,
|
|
selfId: null,
|
|
data: this.props.data,
|
|
}
|
|
|
|
api = window.app.request
|
|
|
|
componentDidMount = async () => {
|
|
const selfId = await User.selfUserId()
|
|
|
|
window.app.ws.listen(`like.post.${this.props.data._id}`, async (data) => {
|
|
await this.setState({ data })
|
|
})
|
|
window.app.ws.listen(`unlike.post.${this.props.data._id}`, async (data) => {
|
|
await this.setState({ data })
|
|
})
|
|
|
|
await this.setState({
|
|
selfId,
|
|
loading: false
|
|
})
|
|
}
|
|
|
|
onClickLike = async (to) => {
|
|
let result = false
|
|
|
|
if (to) {
|
|
const apiResult = await await this.api.put.like({ post_id: this.props.data._id })
|
|
result = apiResult.success
|
|
} else {
|
|
const apiResult = await await this.api.put.unlike({ post_id: this.props.data._id })
|
|
result = apiResult.success
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
onClickSave = async () => {
|
|
// TODO: save post
|
|
}
|
|
|
|
hasLiked = () => {
|
|
return this.state.data.likes.some(user_id => user_id === this.state.selfId)
|
|
}
|
|
|
|
isSelf = () => {
|
|
return this.state.selfId === this.state.data.user._id
|
|
}
|
|
|
|
render() {
|
|
const hasLiked = this.hasLiked()
|
|
|
|
if (this.state.loading) {
|
|
return <antd.Skeleton active />
|
|
}
|
|
|
|
return <div
|
|
id={this.props.data._id}
|
|
key={this.props.data._id}
|
|
className={classnames("postCard", { ["liked"]: hasLiked })}
|
|
>
|
|
<div className="wrapper">
|
|
<PostHeader
|
|
postData={this.props.data}
|
|
isLiked={hasLiked}
|
|
onClickLike={() => this.onClickLike(false)}
|
|
onClickSave={this.onClickSave}
|
|
likes={this.state.data.likes.length}
|
|
comments={this.state.data.comments.length}
|
|
/>
|
|
<PostContent
|
|
message={this.props.data.message}
|
|
/>
|
|
</div>
|
|
<div className="actionsIndicatorWrapper">
|
|
<div className="actionsIndicator">
|
|
<Icons.MoreHorizontal />
|
|
</div>
|
|
</div>
|
|
<div className="actionsWrapper">
|
|
<PostActions
|
|
onClickLike={this.onClickLike}
|
|
defaultLiked={hasLiked}
|
|
isSelf={this.isSelf()}
|
|
/>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
export const PostCardAnimated = ({
|
|
data,
|
|
onAppear,
|
|
motionAppear,
|
|
}, ref,) => {
|
|
const motionRef = React.useRef(false)
|
|
|
|
useLayoutEffect(() => {
|
|
return () => {
|
|
if (motionRef.current) {
|
|
onAppear()
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
return <CSSMotion
|
|
ref={ref}
|
|
motionName="motion"
|
|
motionAppear={motionAppear}
|
|
onAppearStart={getCollapsedHeight}
|
|
onAppearActive={node => {
|
|
motionRef.current = true;
|
|
return getMaxHeight(node);
|
|
}}
|
|
onAppearEnd={onAppear}
|
|
onLeaveStart={getCurrentHeight}
|
|
onLeaveActive={getCollapsedHeight}
|
|
onLeaveEnd={() => {
|
|
onLeave(id)
|
|
}}
|
|
>
|
|
{(props, passedMotionRef) => {
|
|
return <PostCard
|
|
ref={passedMotionRef}
|
|
data={data}
|
|
/>
|
|
}}
|
|
</CSSMotion>
|
|
}
|
|
|
|
export const ForwardedPostCardAnimated = React.forwardRef(PostCardAnimated)
|
|
|
|
export default ForwardedPostCardAnimated |