]> Untitled Git - lemmy.git/blob - ui/src/components/post.tsx
Adding emoji support
[lemmy.git] / ui / src / components / post.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Subscription } from "rxjs";
3 import { retryWhen, delay, take } from 'rxjs/operators';
4 import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse, CommentNode as CommentNodeI, BanFromCommunityResponse, BanUserResponse, AddModToCommunityResponse, AddAdminResponse, UserView } from '../interfaces';
5 import { WebSocketService, UserService } from '../services';
6 import { msgOp, hotRank } from '../utils';
7 import { PostListing } from './post-listing';
8 import { Sidebar } from './sidebar';
9 import { CommentForm } from './comment-form';
10 import { CommentNodes } from './comment-nodes';
11 import * as autosize from 'autosize';
12
13 interface PostState {
14   post: PostI;
15   comments: Array<Comment>;
16   commentSort: CommentSortType;
17   community: Community;
18   moderators: Array<CommunityUser>;
19   admins: Array<UserView>;
20   scrolled?: boolean;
21   scrolled_comment_id?: number;
22   loading: boolean;
23 }
24
25 export class Post extends Component<any, PostState> {
26
27   private subscription: Subscription;
28   private emptyState: PostState = {
29     post: null,
30     comments: [],
31     commentSort: CommentSortType.Hot,
32     community: null,
33     moderators: [],
34     admins: [],
35     scrolled: false, 
36     loading: true
37   }
38
39   constructor(props: any, context: any) {
40     super(props, context);
41
42     this.state = this.emptyState;
43
44     let postId = Number(this.props.match.params.id);
45     if (this.props.match.params.comment_id) {
46       this.state.scrolled_comment_id = this.props.match.params.comment_id;
47     }
48
49     this.subscription = WebSocketService.Instance.subject
50       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
51       .subscribe(
52         (msg) => this.parseMessage(msg),
53         (err) => console.error(err),
54         () => console.log('complete')
55       );
56
57     WebSocketService.Instance.getPost(postId);
58   }
59
60   componentWillUnmount() {
61     this.subscription.unsubscribe();
62   }
63
64   componentDidMount() {
65     autosize(document.querySelectorAll('textarea'));
66   }
67
68   componentDidUpdate(_lastProps: any, lastState: PostState, _snapshot: any) {
69     if (this.state.scrolled_comment_id && !this.state.scrolled && lastState.comments.length > 0) {
70       var elmnt = document.getElementById(`comment-${this.state.scrolled_comment_id}`);
71       elmnt.scrollIntoView(); 
72       elmnt.classList.add("mark-two");
73       this.state.scrolled = true;
74       this.markScrolledAsRead(this.state.scrolled_comment_id);
75     }
76   }
77
78   markScrolledAsRead(commentId: number) {
79     let found = this.state.comments.find(c => c.id == commentId);
80     let parent = this.state.comments.find(c => found.parent_id == c.id);
81     let parent_user_id = parent ? parent.creator_id : this.state.post.creator_id;
82
83     if (UserService.Instance.user && UserService.Instance.user.id == parent_user_id) {
84
85       let form: CommentFormI = {
86         content: found.content,
87         edit_id: found.id,
88         creator_id: found.creator_id,
89         post_id: found.post_id,
90         parent_id: found.parent_id,
91         read: true,
92         auth: null
93       };
94       WebSocketService.Instance.editComment(form);
95     }
96   }
97
98   render() {
99     return (
100       <div class="container">
101         {this.state.loading ? 
102         <h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> : 
103         <div class="row">
104             <div class="col-12 col-md-8 col-lg-6 mb-3">
105               <PostListing 
106                 post={this.state.post} 
107                 showBody 
108                 showCommunity 
109                 editable 
110                 moderators={this.state.moderators} 
111                 admins={this.state.admins}
112               />
113               <div className="mb-2" />
114               <CommentForm postId={this.state.post.id} disabled={this.state.post.locked} />
115               {this.sortRadios()}
116               {this.commentsTree()}
117             </div>
118             <div class="col-12 col-md-4 col-lg-3 mb-3 d-none d-md-block px-0">
119               {this.state.comments.length > 0 && this.newComments()}
120             </div>
121             <div class="col-12 col-sm-12 col-lg-3">
122               {this.sidebar()}
123             </div>
124           </div>
125         }
126       </div>
127     )
128   }
129
130   sortRadios() {
131     return (
132       <div class="btn-group btn-group-toggle mb-3">
133         <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Hot && 'active'}`}>Hot
134           <input type="radio" value={CommentSortType.Hot}
135           checked={this.state.commentSort === CommentSortType.Hot} 
136           onChange={linkEvent(this, this.handleCommentSortChange)}  />
137         </label>
138         <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Top && 'active'}`}>Top
139           <input type="radio" value={CommentSortType.Top}
140           checked={this.state.commentSort === CommentSortType.Top} 
141           onChange={linkEvent(this, this.handleCommentSortChange)}  />
142         </label>
143         <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.New && 'active'}`}>New
144           <input type="radio" value={CommentSortType.New}
145           checked={this.state.commentSort === CommentSortType.New} 
146           onChange={linkEvent(this, this.handleCommentSortChange)}  />
147         </label>
148       </div>
149     )
150   }
151
152   newComments() {
153     return (
154       <div class="container-fluid sticky-top new-comments">
155         <h5>Chat</h5>
156         <CommentForm postId={this.state.post.id} disabled={this.state.post.locked} />
157         {this.state.comments.map(comment => 
158           <CommentNodes 
159             nodes={[{comment: comment}]} 
160             noIndent 
161             locked={this.state.post.locked} 
162             moderators={this.state.moderators} 
163             admins={this.state.admins}
164           />
165         )}
166       </div>
167     )
168   }
169
170   sidebar() {
171     return ( 
172       <div class="">
173         <Sidebar 
174           community={this.state.community} 
175           moderators={this.state.moderators} 
176           admins={this.state.admins}
177         />
178       </div>
179     );
180   }
181   
182   handleCommentSortChange(i: Post, event: any) {
183     i.state.commentSort = Number(event.target.value);
184     i.setState(i.state);
185   }
186
187   private buildCommentsTree(): Array<CommentNodeI> {
188     let map = new Map<number, CommentNodeI>();
189     for (let comment of this.state.comments) {
190       let node: CommentNodeI = {
191         comment: comment,
192         children: []
193       };
194       map.set(comment.id, { ...node });
195     }
196     let tree: Array<CommentNodeI> = [];
197     for (let comment of this.state.comments) {
198       if( comment.parent_id ) {
199         map.get(comment.parent_id).children.push(map.get(comment.id));
200       } 
201       else {
202         tree.push(map.get(comment.id));
203       }
204     }
205
206     this.sortTree(tree);
207
208     return tree;
209   }
210
211   sortTree(tree: Array<CommentNodeI>) {
212
213     if (this.state.commentSort == CommentSortType.Top) {
214       tree.sort((a, b) => b.comment.score - a.comment.score);
215     } else if (this.state.commentSort == CommentSortType.New) {
216       tree.sort((a, b) => b.comment.published.localeCompare(a.comment.published));
217     } else if (this.state.commentSort == CommentSortType.Hot) {
218       tree.sort((a, b) => hotRank(b.comment) - hotRank(a.comment));
219     }
220
221     for (let node of tree) {
222       this.sortTree(node.children);
223     }
224
225   }
226
227   commentsTree() {
228     let nodes = this.buildCommentsTree();
229     return (
230       <div>
231         <CommentNodes 
232           nodes={nodes} 
233           locked={this.state.post.locked} 
234           moderators={this.state.moderators} 
235           admins={this.state.admins}
236         />
237       </div>
238     );
239   }
240
241   parseMessage(msg: any) {
242     console.log(msg);
243     let op: UserOperation = msgOp(msg);
244     if (msg.error) {
245       alert(msg.error);
246       return;
247     } else if (op == UserOperation.GetPost) {
248       let res: GetPostResponse = msg;
249       this.state.post = res.post;
250       this.state.post = res.post;
251       this.state.comments = res.comments;
252       this.state.community = res.community;
253       this.state.moderators = res.moderators;
254       this.state.admins = res.admins;
255       this.state.loading = false;
256       document.title = `${this.state.post.name} - Lemmy`;
257       this.setState(this.state);
258     } else if (op == UserOperation.CreateComment) {
259       let res: CommentResponse = msg;
260       this.state.comments.unshift(res.comment);
261       this.setState(this.state);
262     } else if (op == UserOperation.EditComment) {
263       let res: CommentResponse = msg;
264       let found = this.state.comments.find(c => c.id == res.comment.id);
265       found.content = res.comment.content;
266       found.updated = res.comment.updated;
267       found.removed = res.comment.removed;
268       found.deleted = res.comment.deleted;
269       found.upvotes = res.comment.upvotes;
270       found.downvotes = res.comment.downvotes;
271       found.score = res.comment.score;
272       found.read = res.comment.read;
273
274       this.setState(this.state);
275     } else if (op == UserOperation.SaveComment) {
276       let res: CommentResponse = msg;
277       let found = this.state.comments.find(c => c.id == res.comment.id);
278       found.saved = res.comment.saved;
279       this.setState(this.state);
280     } else if (op == UserOperation.CreateCommentLike) {
281       let res: CommentResponse = msg;
282       let found: Comment = this.state.comments.find(c => c.id === res.comment.id);
283       found.score = res.comment.score;
284       found.upvotes = res.comment.upvotes;
285       found.downvotes = res.comment.downvotes;
286       if (res.comment.my_vote !== null) 
287         found.my_vote = res.comment.my_vote;
288       this.setState(this.state);
289     } else if (op == UserOperation.CreatePostLike) {
290       let res: CreatePostLikeResponse = msg;
291       this.state.post.my_vote = res.post.my_vote;
292       this.state.post.score = res.post.score;
293       this.state.post.upvotes = res.post.upvotes;
294       this.state.post.downvotes = res.post.downvotes;
295       this.setState(this.state);
296     } else if (op == UserOperation.EditPost) {
297       let res: PostResponse = msg;
298       this.state.post = res.post;
299       this.setState(this.state);
300     } else if (op == UserOperation.SavePost) {
301       let res: PostResponse = msg;
302       this.state.post = res.post;
303       this.setState(this.state);
304     } else if (op == UserOperation.EditCommunity) {
305       let res: CommunityResponse = msg;
306       this.state.community = res.community;
307       this.state.post.community_id = res.community.id;
308       this.state.post.community_name = res.community.name;
309       this.setState(this.state);
310     } else if (op == UserOperation.FollowCommunity) {
311       let res: CommunityResponse = msg;
312       this.state.community.subscribed = res.community.subscribed;
313       this.state.community.number_of_subscribers = res.community.number_of_subscribers;
314       this.setState(this.state);
315     } else if (op == UserOperation.BanFromCommunity) {
316       let res: BanFromCommunityResponse = msg;
317       this.state.comments.filter(c => c.creator_id == res.user.id)
318       .forEach(c => c.banned_from_community = res.banned);
319       this.setState(this.state);
320     } else if (op == UserOperation.AddModToCommunity) {
321       let res: AddModToCommunityResponse = msg;
322       this.state.moderators = res.moderators;
323       this.setState(this.state);
324     } else if (op == UserOperation.BanUser) {
325       let res: BanUserResponse = msg;
326       this.state.comments.filter(c => c.creator_id == res.user.id)
327       .forEach(c => c.banned = res.banned);
328       this.setState(this.state);
329     } else if (op == UserOperation.AddAdmin) {
330       let res: AddAdminResponse = msg;
331       this.state.admins = res.admins;
332       this.setState(this.state);
333     }
334
335   }
336 }
337
338
339