MainPostsFeed.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import { Card, Col, Row, Carousel, Empty, Button } from 'antd'
  2. import React, { createRef, useEffect, useState } from 'react'
  3. import { connect } from 'react-redux'
  4. import { Link } from 'react-router-dom'
  5. import { backURL } from '../../helpers'
  6. import { UserAvatar } from '../header/Header'
  7. import nodata from '../../images/nodata.png'
  8. import { HeartFilled, HeartOutlined, LeftCircleOutlined, RightCircleOutlined, SendOutlined, } from '@ant-design/icons'
  9. import Paragraph from 'antd/lib/typography/Paragraph'
  10. import Text from 'antd/lib/typography/Text'
  11. import TextArea from 'antd/lib/input/TextArea'
  12. import { actionAddPostsFeed, actionFullAddComment, actionFullAddLikePost, actionFullRemoveLikePost } from '../../redux/redux-thunk'
  13. import { actionRemovePostsFeedAC } from '../../actions'
  14. const PostTitle = ({ owner }) =>
  15. <Link to={`/profile/${owner?._id}`} className='owner'>
  16. <Row justify="start" align='middle'>
  17. <Col >
  18. <UserAvatar avatar={owner?.avatar} login={owner?.login} avatarSize={'45px'} nick={owner?.nick} />
  19. </Col>
  20. <Col offset={1}>
  21. <span>{owner?.nick ? owner.nick : owner?.login ? owner.login : 'Null'}</span>
  22. </Col>
  23. </Row>
  24. </Link >
  25. class PostImage extends React.Component {
  26. constructor(props) {
  27. super(props);
  28. this.carouselRef = createRef();
  29. this.state = {
  30. movePrev: false,
  31. moveNext: false
  32. }
  33. }
  34. handleNext = () => this.carouselRef.current.next(this);
  35. handlePrev = () => this.carouselRef.current.prev(this);
  36. moveOnDivArray = (length, index) => {
  37. if (length === 1) {
  38. this.setState({ movePrev: false, moveNext: false })
  39. } else if (index === 0) {
  40. this.setState({ movePrev: false, moveNext: true })
  41. } else if (index === length - 1 && length > 1) {
  42. this.setState({ movePrev: true, moveNext: false })
  43. } else {
  44. this.setState({ movePrev: true, moveNext: true })
  45. }
  46. }
  47. downOnDivArray = () => this.setState({ movePrev: false, moveNext: false })
  48. render() {
  49. const { images } = this.props
  50. return (
  51. <Carousel ref={this.carouselRef}
  52. effect="fade"
  53. infinite={false}
  54. dots={{ className: 'Post__dots' }
  55. }>
  56. {!!images ?
  57. images.map((i, index) => i?.url ? <div key={i._id}
  58. onMouseEnter={() => this.moveOnDivArray(images.length, index)}
  59. onMouseLeave={this.downOnDivArray}>
  60. <button onClick={() => this.handlePrev()}
  61. className={`Post__prev Post__btn ${this.state.movePrev ? '--active' : ''}`}><LeftCircleOutlined /></button>
  62. <button onClick={() => this.handleNext()}
  63. className={`Post__next Post__btn ${this.state.moveNext ? '--active' : ''}`}><RightCircleOutlined /></button>
  64. <img src={backURL + '/' + i.url} />
  65. </div> :
  66. <Empty key={i._id} image={nodata} description={false} />) :
  67. <Empty image={nodata} description={false} />
  68. }
  69. </Carousel >
  70. );
  71. }
  72. }
  73. const HeartLike = ({ styleFontSize, likeStatus, changeLike }) =>
  74. <Button
  75. onClick={() => changeLike()}
  76. type="none"
  77. shape="circle"
  78. icon={
  79. likeStatus ?
  80. <HeartFilled style={{ color: '#ff6969', fontSize: `${styleFontSize}` }} /> :
  81. <HeartOutlined style={{ color: '#1890ff', fontSize: `${styleFontSize}` }} />}
  82. />
  83. const PostUserPanel = ({ myID, postId, likes = [], addLikePost, removeLikePost }) => {
  84. let likeStatus
  85. let likeId
  86. likes.find(l => {
  87. if (l.owner._id === myID) {
  88. likeStatus = true
  89. likeId = l._id
  90. } else {
  91. likeStatus = false
  92. }
  93. })
  94. const changeLike = () => likeStatus ? removeLikePost(likeId, postId) : addLikePost(postId)
  95. const styleFontSize = '1.7em'
  96. return (
  97. <>
  98. <Row className="Post__panel-btn">
  99. <Col className='Post__heart'>
  100. <HeartLike
  101. changeLike={changeLike}
  102. likeStatus={likeStatus}
  103. styleFontSize={styleFontSize} />
  104. </Col>
  105. <Col>
  106. </Col>
  107. </Row>
  108. {!!likes.length && <strong>Likes: {likes.length}</strong>}
  109. </>
  110. )
  111. }
  112. const CPostUserPanel = connect(state => ({
  113. myID: state.auth.payload.sub.id || '',
  114. myLikes: state?.promise?.myLikes?.payload || [],
  115. }), {
  116. addLikePost: actionFullAddLikePost,
  117. removeLikePost: actionFullRemoveLikePost,
  118. })(PostUserPanel)
  119. const PostDescription = ({ title, description, date }) =>
  120. <>
  121. <Row justify='space-between'>
  122. <Col >
  123. {!!title && <Text level={3} strong>{title}</Text>}
  124. </Col>
  125. <Col >
  126. <Text type='secondary'>{date}</Text>
  127. </Col>
  128. </Row>
  129. <Paragraph ellipsis={true ? { rows: 1, expandable: true, symbol: 'more' } : false}>
  130. {description}
  131. </Paragraph>
  132. </>
  133. const Comments = ({ comments }) =>
  134. <>
  135. {comments && comments.length > 2 &&
  136. <Link to={`/#`}>
  137. <Text type={'secondary'} level={3}>{`Посмотреть все ${comments.length} комментария`}</Text>
  138. </Link>}
  139. {comments && <div>
  140. <div className='Post__comments'>
  141. <Link to={`/#`}>{comments[comments?.length - 2]?.owner?.nick || comments[comments?.length - 2]?.owner?.login}: </Link>
  142. <span>{comments[comments?.length - 2]?.text}</span>
  143. </div>
  144. <div className='Post__comments'>
  145. <Link to={`/#`}>{comments[comments?.length - 1]?.owner?.login || comments[comments?.length - 1]?.owner?.login}: </Link>
  146. <span>{comments[comments?.length - 1]?.text}</span>
  147. </div>
  148. </div>}
  149. </>
  150. const FieldCommentSend = ({ postId, sentComment }) => {
  151. const [commentValue, setCommentValue] = useState('')
  152. const [error, setError] = useState(false)
  153. const changeComentTextarea = (e) => {
  154. setCommentValue(e.currentTarget.value)
  155. setError(false)
  156. }
  157. const sendCommentValid = (value) => {
  158. if (value.trim() !== '') {
  159. sentComment(postId, value.trim())
  160. setCommentValue('')
  161. } else {
  162. setError(true)
  163. }
  164. }
  165. return (
  166. <>
  167. {error && <Text type='danger'>Field is required</Text>}
  168. <Row align='middle' className='Post__send-comment'>
  169. <Col flex='auto' offset={1}>
  170. <TextArea value={commentValue}
  171. placeholder="Add a comment ..."
  172. autoSize={{ minRows: 1, maxRows: 2 }}
  173. onChange={changeComentTextarea}
  174. bordered={false}
  175. />
  176. </Col>
  177. <Col span={2}>
  178. <Button
  179. onClick={() => sendCommentValid(commentValue)}
  180. icon={< SendOutlined
  181. style={{ fontSize: '1.2em', opacity: .6 }} />} />
  182. </Col>
  183. </Row>
  184. </>
  185. )
  186. }
  187. const CFieldCommentSend = connect(null, { sentComment: actionFullAddComment })(FieldCommentSend)
  188. const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes } }) => {
  189. const date = new Date(createdAt * 1)
  190. const resultDate = new Intl.DateTimeFormat('default').format(date)
  191. return (
  192. <div className='Post'>
  193. <Card
  194. title={<PostTitle owner={owner} />}
  195. cover={<PostImage images={images} />}
  196. actions={[<CFieldCommentSend postId={_id} />]}
  197. >
  198. <CPostUserPanel postId={_id} likes={likes} />
  199. <PostDescription title={title} description={text} date={resultDate} />
  200. <Comments comments={comments} />
  201. </Card>
  202. </div>
  203. )
  204. }
  205. const MainPostsFeed = ({ posts, countPosts, postsFollowing, postsFollowingRemove, following }) => {
  206. const [checkScroll, setCheckScroll] = useState(true)
  207. useEffect(async () => {
  208. if (checkScroll && following.length !== 0 && posts.length < countPosts) {
  209. await postsFollowing(posts.length, following)
  210. setCheckScroll(false)
  211. }
  212. }, [checkScroll, following])
  213. useEffect(() => {
  214. document.addEventListener('scroll', scrollHandler)
  215. return () => {
  216. document.removeEventListener('scroll', scrollHandler)
  217. postsFollowingRemove()
  218. }
  219. }, [])
  220. const scrollHandler = (e) => {
  221. if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
  222. setCheckScroll(true)
  223. }
  224. }
  225. return (
  226. <>
  227. {posts.map(p => <Post key={p._id} postData={p} />)}
  228. </>
  229. )
  230. }
  231. export const CMainPostsFeed = connect(state => ({
  232. countPosts: state?.postsFeed?.count || 1,
  233. posts: state?.postsFeed?.posts || [],
  234. following: state?.myData.following || []
  235. }), {
  236. postsFollowing: actionAddPostsFeed,
  237. postsFollowingRemove: actionRemovePostsFeedAC,
  238. })(MainPostsFeed)