import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CommentSortType, CreatePostLikeResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { msgOp, hotRank,mdToHtml } from '../utils'; import { MomentTime } from './moment-time'; import { PostListing } from './post-listing'; import { Sidebar } from './sidebar'; import * as autosize from 'autosize'; interface CommentNodeI { comment: Comment; children?: Array; }; interface PostState { post: PostI; comments: Array; commentSort: CommentSortType; community: Community; } export class Post extends Component { private subscription: Subscription; private emptyState: PostState = { post: null, comments: [], commentSort: CommentSortType.Hot, community: null, } constructor(props, context) { super(props, context); this.state = this.emptyState; let postId = Number(this.props.match.params.id); this.subscription = WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) .subscribe( (msg) => this.parseMessage(msg), (err) => console.error(err), () => console.log('complete') ); WebSocketService.Instance.getPost(postId); } componentWillUnmount() { this.subscription.unsubscribe(); } componentDidMount() { autosize(document.querySelectorAll('textarea')); } render() { return (
{this.state.post &&
{this.sortRadios()} {this.commentsTree()}
{this.state.comments.length > 0 && this.newComments()}
{this.sidebar()}
}
) } sortRadios() { return (
) } newComments() { return (
New Comments
{this.state.comments.map(comment => )}
) } sidebar() { return (
); } handleCommentSortChange(i: Post, event) { i.state.commentSort = Number(event.target.value); i.setState(i.state); } private buildCommentsTree(): Array { let map = new Map(); for (let comment of this.state.comments) { let node: CommentNodeI = { comment: comment, children: [] }; map.set(comment.id, { ...node }); } let tree: Array = []; for (let comment of this.state.comments) { if( comment.parent_id ) { map.get(comment.parent_id).children.push(map.get(comment.id)); } else { tree.push(map.get(comment.id)); } } this.sortTree(tree); return tree; } sortTree(tree: Array) { if (this.state.commentSort == CommentSortType.Top) { tree.sort((a, b) => b.comment.score - a.comment.score); } else if (this.state.commentSort == CommentSortType.New) { tree.sort((a, b) => b.comment.published.localeCompare(a.comment.published)); } else if (this.state.commentSort == CommentSortType.Hot) { tree.sort((a, b) => hotRank(b.comment) - hotRank(a.comment)); } for (let node of tree) { this.sortTree(node.children); } } commentsTree() { let nodes = this.buildCommentsTree(); return (
); } parseMessage(msg: any) { console.log(msg); let op: UserOperation = msgOp(msg); if (msg.error) { alert(msg.error); return; } else if (op == UserOperation.GetPost) { let res: GetPostResponse = msg; this.state.post = res.post; this.state.comments = res.comments; this.state.community = res.community; this.setState(this.state); } else if (op == UserOperation.CreateComment) { let res: CommentResponse = msg; this.state.comments.unshift(res.comment); this.setState(this.state); } else if (op == UserOperation.EditComment) { let res: CommentResponse = msg; let found = this.state.comments.find(c => c.id == res.comment.id); found.content = res.comment.content; found.updated = res.comment.updated; this.setState(this.state); } else if (op == UserOperation.CreateCommentLike) { let res: CommentResponse = msg; let found: Comment = this.state.comments.find(c => c.id === res.comment.id); found.score = res.comment.score; found.upvotes = res.comment.upvotes; found.downvotes = res.comment.downvotes; if (res.comment.my_vote !== null) found.my_vote = res.comment.my_vote; this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { let res: CreatePostLikeResponse = msg; this.state.post.my_vote = res.post.my_vote; this.state.post.score = res.post.score; this.state.post.upvotes = res.post.upvotes; this.state.post.downvotes = res.post.downvotes; this.setState(this.state); } else if (op == UserOperation.EditPost) { let res: PostResponse = msg; this.state.post = res.post; this.setState(this.state); } } } interface CommentNodesState { } interface CommentNodesProps { nodes: Array; noIndent?: boolean; } export class CommentNodes extends Component { constructor(props, context) { super(props, context); } render() { return (
{this.props.nodes.map(node => )}
) } } interface CommentNodeState { showReply: boolean; showEdit: boolean; } interface CommentNodeProps { node: CommentNodeI; noIndent?: boolean; } export class CommentNode extends Component { private emptyState: CommentNodeState = { showReply: false, showEdit: false } constructor(props, context) { super(props, context); this.state = this.emptyState; this.handleReplyCancel = this.handleReplyCancel.bind(this); this.handleCommentLike = this.handleCommentLike.bind(this); this.handleCommentDisLike = this.handleCommentDisLike.bind(this); } render() { let node = this.props.node; return (
{node.comment.score}
  • {node.comment.creator_name}
  • ( +{node.comment.upvotes} | -{node.comment.downvotes} )
{this.state.showEdit && } {!this.state.showEdit &&
  • reply
  • {this.myComment &&
  • edit
  • } {this.myComment &&
  • delete
  • }
  • link
}
{this.state.showReply && } {this.props.node.children && }
) } private get myComment(): boolean { return UserService.Instance.loggedIn && this.props.node.comment.creator_id == UserService.Instance.user.id; } handleReplyClick(i: CommentNode, event) { i.state.showReply = true; i.setState(i.state); } handleEditClick(i: CommentNode, event) { i.state.showEdit = true; i.setState(i.state); } handleDeleteClick(i: CommentNode, event) { let deleteForm: CommentFormI = { content: "*deleted*", edit_id: i.props.node.comment.id, post_id: i.props.node.comment.post_id, parent_id: i.props.node.comment.parent_id, auth: null }; WebSocketService.Instance.editComment(deleteForm); } handleReplyCancel(): any { this.state.showReply = false; this.state.showEdit = false; this.setState(this.state); } handleCommentLike(i: CommentNodeI, event) { let form: CommentLikeForm = { comment_id: i.comment.id, post_id: i.comment.post_id, score: (i.comment.my_vote == 1) ? 0 : 1 }; WebSocketService.Instance.likeComment(form); } handleCommentDisLike(i: CommentNodeI, event) { let form: CommentLikeForm = { comment_id: i.comment.id, post_id: i.comment.post_id, score: (i.comment.my_vote == -1) ? 0 : -1 }; WebSocketService.Instance.likeComment(form); } } interface CommentFormProps { postId?: number; node?: CommentNodeI; onReplyCancel?(); edit?: boolean; } interface CommentFormState { commentForm: CommentFormI; buttonTitle: string; } export class CommentForm extends Component { private emptyState: CommentFormState = { commentForm: { auth: null, content: null, post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId }, buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply" } constructor(props, context) { super(props, context); this.state = this.emptyState; if (this.props.node) { if (this.props.edit) { this.state.commentForm.edit_id = this.props.node.comment.id; this.state.commentForm.parent_id = this.props.node.comment.parent_id; this.state.commentForm.content = this.props.node.comment.content; } else { // A reply gets a new parent id this.state.commentForm.parent_id = this.props.node.comment.id; } } } componentDidMount() { autosize(document.querySelectorAll('textarea')); } render() { return (