import { Component, linkEvent } from 'inferno'; import { HtmlTags } from './html-tags'; import { Subscription } from 'rxjs'; import { UserOperation, PostView, GetPostResponse, PostResponse, MarkCommentAsRead, CommentResponse, CommunityResponse, BanFromCommunityResponse, BanUserResponse, AddModToCommunityResponse, AddAdminResponse, SearchType, SortType, Search, GetPost, SearchResponse, GetSiteResponse, GetCommunityResponse, ListCategoriesResponse, Category, } from 'lemmy-js-client'; import { CommentSortType, CommentViewType, InitialFetchRequest, CommentNode as CommentNodeI, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { wsJsonToRes, toast, editCommentRes, saveCommentRes, createCommentLikeRes, createPostLikeRes, commentsToFlatNodes, setupTippy, setIsoData, getIdFromProps, getCommentIdFromProps, wsSubscribe, isBrowser, previewLines, isImage, wsUserOp, wsClient, authField, setOptionalAuth, } from '../utils'; import { PostListing } from './post-listing'; import { Sidebar } from './sidebar'; import { CommentForm } from './comment-form'; import { CommentNodes } from './comment-nodes'; import autosize from 'autosize'; import { i18n } from '../i18next'; interface PostState { postRes: GetPostResponse; postId: number; commentId?: number; commentSort: CommentSortType; commentViewType: CommentViewType; scrolled?: boolean; loading: boolean; crossPosts: PostView[]; siteRes: GetSiteResponse; categories: Category[]; } export class Post extends Component { private subscription: Subscription; private isoData = setIsoData(this.context); private emptyState: PostState = { postRes: null, postId: getIdFromProps(this.props), commentId: getCommentIdFromProps(this.props), commentSort: CommentSortType.Hot, commentViewType: CommentViewType.Tree, scrolled: false, loading: true, crossPosts: [], siteRes: this.isoData.site_res, categories: [], }; constructor(props: any, context: any) { super(props, context); this.state = this.emptyState; this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); // Only fetch the data if coming from another route if (this.isoData.path == this.context.router.route.match.url) { this.state.postRes = this.isoData.routeData[0]; this.state.categories = this.isoData.routeData[1].categories; this.state.loading = false; if (isBrowser()) { this.fetchCrossPosts(); if (this.state.commentId) { this.scrollCommentIntoView(); } } } else { this.fetchPost(); WebSocketService.Instance.send(wsClient.listCategories()); } } fetchPost() { let form: GetPost = { id: this.state.postId, auth: authField(false), }; WebSocketService.Instance.send(wsClient.getPost(form)); } fetchCrossPosts() { if (this.state.postRes.post_view.post.url) { let form: Search = { q: this.state.postRes.post_view.post.url, type_: SearchType.Url, sort: SortType.TopAll, page: 1, limit: 6, auth: authField(false), }; WebSocketService.Instance.send(wsClient.search(form)); } } static fetchInitialData(req: InitialFetchRequest): Promise[] { let pathSplit = req.path.split('/'); let promises: Promise[] = []; let id = Number(pathSplit[2]); let postForm: GetPost = { id, }; setOptionalAuth(postForm, req.auth); promises.push(req.client.getPost(postForm)); promises.push(req.client.listCategories()); return promises; } componentWillUnmount() { this.subscription.unsubscribe(); window.isoData.path = undefined; } componentDidMount() { WebSocketService.Instance.send( wsClient.postJoin({ post_id: this.state.postId }) ); autosize(document.querySelectorAll('textarea')); } componentDidUpdate(_lastProps: any, lastState: PostState, _snapshot: any) { if ( this.state.commentId && !this.state.scrolled && lastState.postRes && lastState.postRes.comments.length > 0 ) { this.scrollCommentIntoView(); } // Necessary if you are on a post and you click another post (same route) if (_lastProps.location.pathname !== _lastProps.history.location.pathname) { // TODO Couldnt get a refresh working. This does for now. location.reload(); // let currentId = this.props.match.params.id; // WebSocketService.Instance.getPost(currentId); // this.context.refresh(); // this.context.router.history.push(_lastProps.location.pathname); } } scrollCommentIntoView() { var elmnt = document.getElementById(`comment-${this.state.commentId}`); elmnt.scrollIntoView(); elmnt.classList.add('mark'); this.state.scrolled = true; this.markScrolledAsRead(this.state.commentId); } // TODO this needs some re-work markScrolledAsRead(commentId: number) { let found = this.state.postRes.comments.find( c => c.comment.id == commentId ); let parent = this.state.postRes.comments.find( c => found.comment.parent_id == c.comment.id ); let parent_user_id = parent ? parent.creator.id : this.state.postRes.post_view.creator.id; if ( UserService.Instance.user && UserService.Instance.user.id == parent_user_id ) { let form: MarkCommentAsRead = { comment_id: found.comment.id, read: true, auth: authField(), }; WebSocketService.Instance.send(wsClient.markCommentAsRead(form)); UserService.Instance.unreadCountSub.next( UserService.Instance.unreadCountSub.value - 1 ); } } get documentTitle(): string { return `${this.state.postRes.post_view.post.name} - ${this.state.siteRes.site_view.site.name}`; } get imageTag(): string { let post = this.state.postRes.post_view.post; return ( post.thumbnail_url || (post.url ? (isImage(post.url) ? post.url : undefined) : undefined) ); } get descriptionTag(): string { let body = this.state.postRes.post_view.post.body; return body ? previewLines(body) : undefined; } render() { let pv = this.state.postRes?.post_view; return (
{this.state.loading ? (
) : (
{this.state.postRes.comments.length > 0 && this.sortRadios()} {this.state.commentViewType == CommentViewType.Tree && this.commentsTree()} {this.state.commentViewType == CommentViewType.Chat && this.commentsFlat()}
{this.sidebar()}
)}
); } sortRadios() { return ( <>
); } commentsFlat() { return (
); } sidebar() { return (
); } handleCommentSortChange(i: Post, event: any) { i.state.commentSort = Number(event.target.value); i.state.commentViewType = CommentViewType.Tree; i.setState(i.state); } handleCommentViewTypeChange(i: Post, event: any) { i.state.commentViewType = Number(event.target.value); i.state.commentSort = CommentSortType.New; i.setState(i.state); } buildCommentsTree(): CommentNodeI[] { let map = new Map(); for (let comment_view of this.state.postRes.comments) { let node: CommentNodeI = { comment_view: comment_view, children: [], }; map.set(comment_view.comment.id, { ...node }); } let tree: CommentNodeI[] = []; for (let comment_view of this.state.postRes.comments) { let child = map.get(comment_view.comment.id); if (comment_view.comment.parent_id) { let parent_ = map.get(comment_view.comment.parent_id); parent_.children.push(child); } else { tree.push(child); } this.setDepth(child); } return tree; } setDepth(node: CommentNodeI, i: number = 0): void { for (let child of node.children) { child.depth = i; this.setDepth(child, i + 1); } } commentsTree() { let nodes = this.buildCommentsTree(); return (
); } parseMessage(msg: any) { let op = wsUserOp(msg); console.log(msg); if (msg.error) { toast(i18n.t(msg.error), 'danger'); return; } else if (msg.reconnect) { let postId = Number(this.props.match.params.id); WebSocketService.Instance.send(wsClient.postJoin({ post_id: postId })); WebSocketService.Instance.send( wsClient.getPost({ id: postId, auth: authField(false), }) ); } else if (op == UserOperation.GetPost) { let data = wsJsonToRes(msg).data; this.state.postRes = data; this.state.loading = false; // Get cross-posts this.fetchCrossPosts(); this.setState(this.state); setupTippy(); } else if (op == UserOperation.CreateComment) { let data = wsJsonToRes(msg).data; // Necessary since it might be a user reply, which has the recipients, to avoid double if (data.recipient_ids.length == 0) { this.state.postRes.comments.unshift(data.comment_view); this.state.postRes.post_view.counts.comments++; this.setState(this.state); setupTippy(); } } else if ( op == UserOperation.EditComment || op == UserOperation.DeleteComment || op == UserOperation.RemoveComment ) { let data = wsJsonToRes(msg).data; editCommentRes(data.comment_view, this.state.postRes.comments); this.setState(this.state); } else if (op == UserOperation.SaveComment) { let data = wsJsonToRes(msg).data; saveCommentRes(data.comment_view, this.state.postRes.comments); this.setState(this.state); setupTippy(); } else if (op == UserOperation.CreateCommentLike) { let data = wsJsonToRes(msg).data; createCommentLikeRes(data.comment_view, this.state.postRes.comments); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { let data = wsJsonToRes(msg).data; createPostLikeRes(data.post_view, this.state.postRes.post_view); this.setState(this.state); } else if ( op == UserOperation.EditPost || op == UserOperation.DeletePost || op == UserOperation.RemovePost || op == UserOperation.LockPost || op == UserOperation.StickyPost || op == UserOperation.SavePost ) { let data = wsJsonToRes(msg).data; this.state.postRes.post_view = data.post_view; this.setState(this.state); setupTippy(); } else if ( op == UserOperation.EditCommunity || op == UserOperation.DeleteCommunity || op == UserOperation.RemoveCommunity || op == UserOperation.FollowCommunity ) { let data = wsJsonToRes(msg).data; this.state.postRes.community_view = data.community_view; this.state.postRes.post_view.community = data.community_view.community; this.setState(this.state); this.setState(this.state); } else if (op == UserOperation.BanFromCommunity) { let data = wsJsonToRes(msg).data; this.state.postRes.comments .filter(c => c.creator.id == data.user_view.user.id) .forEach(c => (c.creator_banned_from_community = data.banned)); if (this.state.postRes.post_view.creator.id == data.user_view.user.id) { this.state.postRes.post_view.creator_banned_from_community = data.banned; } this.setState(this.state); } else if (op == UserOperation.AddModToCommunity) { let data = wsJsonToRes(msg).data; this.state.postRes.moderators = data.moderators; this.setState(this.state); } else if (op == UserOperation.BanUser) { let data = wsJsonToRes(msg).data; this.state.postRes.comments .filter(c => c.creator.id == data.user_view.user.id) .forEach(c => (c.creator.banned = data.banned)); if (this.state.postRes.post_view.creator.id == data.user_view.user.id) { this.state.postRes.post_view.creator.banned = data.banned; } this.setState(this.state); } else if (op == UserOperation.AddAdmin) { let data = wsJsonToRes(msg).data; this.state.siteRes.admins = data.admins; this.setState(this.state); } else if (op == UserOperation.Search) { let data = wsJsonToRes(msg).data; this.state.crossPosts = data.posts.filter( p => p.post.id != Number(this.props.match.params.id) ); this.setState(this.state); } else if (op == UserOperation.TransferSite) { let data = wsJsonToRes(msg).data; this.state.siteRes = data; this.setState(this.state); } else if (op == UserOperation.TransferCommunity) { let data = wsJsonToRes(msg).data; this.state.postRes.community_view = data.community_view; this.state.postRes.post_view.community = data.community_view.community; this.state.postRes.moderators = data.moderators; this.setState(this.state); } else if (op == UserOperation.ListCategories) { let data = wsJsonToRes(msg).data; this.state.categories = data.categories; this.setState(this.state); } } }