This repository has been archived on 2025-09-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
resolver/app/components/Post.jsx
2017-10-10 16:39:02 +07:00

505 lines
15 KiB
JavaScript

// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import {NavLink} from 'react-router-dom'
import { push } from 'react-router-redux'
import PropTypes from 'prop-types'
import moment from 'moment'
import { createAction as action } from 'redux-actions'
import Linkify from 'react-linkify'
// - Material UI
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card'
import FloatingActionButton from 'material-ui/FloatingActionButton'
import SvgShare from 'material-ui/svg-icons/social/share'
import SvgLink from 'material-ui/svg-icons/content/link'
import SvgComment from 'material-ui/svg-icons/Communication/comment'
import SvgFavorite from 'material-ui/svg-icons/action/favorite'
import SvgFavoriteBorder from 'material-ui/svg-icons/action/favorite-border'
import Checkbox from 'material-ui/Checkbox'
import FlatButton from 'material-ui/FlatButton'
import Divider from 'material-ui/Divider'
import { grey200, grey400, grey600 } from 'material-ui/styles/colors'
import Paper from 'material-ui/Paper'
import Menu from 'material-ui/Menu'
import MenuItem from 'material-ui/MenuItem'
import TextField from 'material-ui/TextField'
import Dialog from 'material-ui/Dialog'
import IconButton from 'material-ui/IconButton'
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
import IconMenu from 'material-ui/IconMenu'
import reactStringReplace from 'react-string-replace'
// - Import app components
import CommentGroup from 'CommentGroup'
import PostWrite from 'PostWrite'
import Img from 'Img'
import IconButtonElement from 'IconButtonElement'
import UserAvatar from 'UserAvatar'
// - Import actions
import * as voteActions from 'voteActions'
import * as postActions from 'postActions'
import * as globalActions from 'globalActions'
// - Create component class
export class Post extends Component {
static propTypes = {
/**
* The context of a post
*/
body: PropTypes.string,
/**
* The number of comment on a post
*/
commentCounter:PropTypes.number,
/**
* Creation post date
*/
creationDate:PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
/**
* Post identifier
*/
id:PropTypes.string,
/**
* Post image address
*/
image:PropTypes.string,
/**
* The last time date when post has was edited
*/
lastEditDate:PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
/**
* The name of the user who created the post
*/
ownerDisplayName:PropTypes.string,
/**
* The identifier of the user who created the post
*/
ownerUserId:PropTypes.string,
/**
* The avatar address of the user who created the post
*/
ownerAvatar:PropTypes.string,
/**
* If post is only [0]text, [1]whith picture, ...
*/
postTypeId:PropTypes.number,
/**
* The number votes on a post
*/
score:PropTypes.number,
/**
* Array of tags on a post
*/
tags:PropTypes.array,
/**
* The video address of a post
*/
video:PropTypes.string,
/**
* If it's true comment will be disabled on a post
*/
disableComments:PropTypes.bool,
/**
* If it's true sharing will be disabled on a post
*/
disableSharing:PropTypes.bool,
/**
* The number of users who has visited the post
*/
viewCount:PropTypes.number
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
this.state = {
/**
* Post text
*/
text: this.props.body,
/**
* It's true if whole the text post is visible
*/
readMoreState: false,
/**
* Handle open comment from parent component
*/
openComments: false,
/**
* If it's true, share dialog will be open
*/
shareOpen: false,
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.disableComments,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.disableSharing,
/**
* Title of share post
*/
shareTitle: 'Share On',
/**
* If it's true, post link will be visible in share post dialog
*/
openCopyLink: false,
/**
* If it's true, post write will be open
*/
openPostWrite: false
}
// Binding functions to this
this.handleReadMore = this.handleReadMore.bind(this)
this.getOpenCommentGroup = this.getOpenCommentGroup.bind(this)
this.handleVote = this.handleVote.bind(this)
this.handleOpenShare = this.handleOpenShare.bind(this)
this.handleCloseShare = this.handleCloseShare.bind(this)
this.handleCopyLink = this.handleCopyLink.bind(this)
this.handleDelete = this.handleDelete.bind(this)
this.handleOpenPostWrite = this.handleOpenPostWrite.bind(this)
this.handleClosePostWrite = this.handleClosePostWrite.bind(this)
this.handleOpenComments = this.handleOpenComments.bind(this)
}
/**
* Toggle on show/hide comment
* @param {event} evt passed by clicking on comment slide show
*/
handleOpenComments = (evt) => {
this.setState({
openComments: !this.state.openComments
})
}
/**
* Open post write
*
*
* @memberof Blog
*/
handleOpenPostWrite = () => {
this.setState({
openPostWrite: true
})
}
/**
* Close post write
*
*
* @memberof Blog
*/
handleClosePostWrite = () => {
this.setState({
openPostWrite: false
})
}
/**
* Delete a post
*
*
* @memberof Post
*/
handleDelete = () => {
this.props.delete(this.props.id)
}
/**
* Show copy link
*
*
* @memberof Post
*/
handleCopyLink = () => {
this.setState({
openCopyLink: true,
shareTitle: 'Copy Link'
})
}
/**
* Open share post
*
*
* @memberof Post
*/
handleOpenShare = () => {
this.setState({
shareOpen: true
})
}
/**
* Close share post
*
*
* @memberof Post
*/
handleCloseShare = () => {
this.setState({
shareOpen: false,
shareTitle: 'Share On',
openCopyLink: false
})
}
/**
* Handle vote on a post
*
*
* @memberof Post
*/
handleVote = () => {
if (this.props.userVoteStatus) {
this.props.unvote()
}
else {
this.props.vote()
}
}
/**
* Set open comment group function on state which passed by CommentGroup component
* @param {function} open the function to open comment list
*/
getOpenCommentGroup = (open) => {
this.setState({
openCommentGroup: open
})
}
/**
* Handle read more event
* @param {event} evt is the event passed by click on read more
*/
handleReadMore(evt) {
this.setState({
readMoreState: !this.state.readMoreState
})
}
componentDidMount() {
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
/**
* DOM styles
*
*
* @memberof Post
*/
const styles = {
counter: {
lineHeight: '36px',
color: '#777',
fontSize: '12px',
marginRight: '6px'
},
postBody:{
wordWrap: "break-word"
},
dialog: {
width: '',
maxWidth: '530px',
borderRadius: "4px"
},
rightIconMenu: {
position: 'absolute',
right: 18,
top: 8,
},
iconButton: {
width: 24,
height: 24
}
}
const RightIconMenu = () => (
<IconMenu iconButtonElement={IconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "4px" }}>
<MenuItem primaryText="Edit" onClick={this.handleOpenPostWrite} />
<MenuItem primaryText="Delete" onClick={this.handleDelete} />
<MenuItem primaryText={this.props.disableComments ? "Enable comments" : "Disable comments"} onClick={() => this.props.toggleDisableComments(!this.props.disableComments)} />
<MenuItem primaryText={this.props.disableSharing ? "Enable sharing" : "Disable sharing"} onClick={() => this.props.toggleSharingComments(!this.props.disableSharing)} />
</IconMenu>
)
const {ownerUserId,setHomeTitle, goTo, ownerDisplayName,creationDate, avatar, fullName, isPostOwner,image, body} = this.props
// Define variables
return (
<Card>
<CardHeader
title={<NavLink to={`/${ownerUserId}`}>{ownerDisplayName}</NavLink>}
subtitle={moment.unix(creationDate).fromNow() + " | public"}
avatar={<NavLink to={`/${ownerUserId}`}><UserAvatar fullName={fullName} fileName={avatar} size={36} /></NavLink>}
>
{isPostOwner ? ( <div style={styles.rightIconMenu}><RightIconMenu /></div>) : ''}
</CardHeader>
{image ? (
<CardMedia>
<Img fileName={image} />
</CardMedia>) : ''}
<CardText style={styles.postBody}>
<Linkify properties={{target: '_blank', style: {color: 'blue'}}}>
{reactStringReplace(body,/#(\w+)/g, (match, i) => (
<NavLink
style={{color:'green'}}
key={match + i}
to={`/tag/${match}`}
onClick ={evt => {
evt.preventDefault()
goTo(`/tag/${match}`)
setHomeTitle(`#${match}`)
}}
>
#{match}
</NavLink>
))}
</Linkify>
</CardText>
<CardActions>
<div style={{ margin: "16px 8px", display: 'flex', justifyContent: 'space-between' }}>
<div style={{ display: 'flex' }}>
{/*<FloatingActionButton style={{ margin: "0 8px" }} zDepth={1} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: "36px", width: "36px" }} zDepth={1} secondary={false}>*/}
<div className='g__circle' onClick={this.handleVote}>
<Checkbox
checkedIcon={<SvgFavorite style={{fill:'#4CAF50'}}/>}
uncheckedIcon={<SvgFavoriteBorder style={{fill:'#757575'}} />}
defaultChecked={this.props.userVoteStatus}
style={{transform: 'translate(6px, 6px)'}}
/>
</div>
<div style={styles.counter}> {this.props.voteCount > 0 ? this.props.voteCount : ''} </div>
</div>
<div style={{ display: 'flex' }}>
{!this.props.disableComments ? (<div style={{display: 'inherit'}}><FloatingActionButton onClick={this.handleOpenComments} style={{ margin: "0 8px" }} zDepth={1} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: "36px", width: "36px" }} zDepth={1} secondary={false}>
<SvgComment viewBox="0 -9 24 34" style={{ height: "30px", width: "30px" }} /> 3
</FloatingActionButton>
<div style={styles.counter}>{this.props.commentCount > 0 ? this.props.commentCount : ''} </div></div>) : ''}
{!this.props.disableSharing ? (<FloatingActionButton onClick={this.handleOpenShare} style={{ margin: "0 8px" }} zDepth={1} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: "36px", width: "36px" }} zDepth={1} secondary={false}>
<SvgShare viewBox="0 -9 24 34" style={{ height: "30px", width: "30px" }} />
</FloatingActionButton>) : ''}
</div>
</div>
</CardActions>
<CommentGroup open={this.state.openComments} ownerPostUserId={this.props.ownerUserId} onToggleRequest={this.handleOpenComments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments} postId={this.props.id} />
{/* Copy link dialog*/}
<Dialog
title="Share On"
modal={false}
open={this.state.shareOpen}
onRequestClose={this.handleCloseShare}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
contentStyle={styles.dialog}
autoDetectWindowHeight={false}
actionsContainerStyle={{ borderTop: "1px solid rgb(224, 224, 224)" }}
>
{!this.state.openCopyLink
? (<Paper >
<Menu>
<MenuItem primaryText="Copy Link" leftIcon={<SvgLink />} onClick={this.handleCopyLink} />
</Menu>
</Paper>)
: <TextField fullWidth={true} id="text-field-default" defaultValue={`${location.origin}/${this.props.ownerUserId}/posts/${this.props.id}`} />
}
</Dialog>
<PostWrite
open={this.state.openPostWrite}
onRequestClose={this.handleClosePostWrite}
edit={true}
text= {this.props.body}
image= {this.props.image ? this.props.image : ''}
id= {this.props.id}
disableComments= {this.props.disableComments}
disableSharing= {this.props.disableSharing}
/>
</Card>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
vote: () => dispatch(voteActions.dbAddVote(ownProps.id,ownProps.ownerUserId)),
unvote: () => dispatch(voteActions.dbDeleteVote(ownProps.id)) ,
delete: (id) => dispatch(postActions.dbDeletePost(id)),
toggleDisableComments: (status) => dispatch(postActions.dbUpdatePost({id:ownProps.id, disableComments:status},_ =>_)),
toggleSharingComments: (status) => dispatch(postActions.dbUpdatePost({id:ownProps.id, disableSharing:status},_ => _)),
goTo: (url) => dispatch(push(url)),
setHomeTitle: (title) => dispatch(globalActions.setHeaderTitle(title|| ''))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const {uid} = state.authorize
let votes = state.vote.postVotes[ownProps.id]
const post = (state.post.userPosts[uid] ? Object.keys(state.post.userPosts[uid]).filter((key)=>{ return ownProps.id === key}).length : 0)
return {
avatar: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].avatar || '' : '',
fullName: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].fullName || '' : '',
commentCount: state.comment.postComments[ownProps.id] ? Object.keys(state.comment.postComments[ownProps.id]).length : 0,
voteCount: state.vote.postVotes[ownProps.id] ? Object.keys(state.vote.postVotes[ownProps.id]).length : 0,
userVoteStatus: votes && Object.keys(votes).filter((key) => votes[key].userId === state.authorize.uid)[0] ? true : false,
isPostOwner: post > 0
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Post)