1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
12 GetUserMentionsResponse,
15 WebSocketJsonResponse,
16 PrivateMessage as PrivateMessageI,
17 GetPrivateMessagesForm,
18 PrivateMessagesResponse,
19 PrivateMessageResponse,
20 } from '../interfaces';
21 import { WebSocketService, UserService } from '../services';
32 import { CommentNodes } from './comment-nodes';
33 import { PrivateMessage } from './private-message';
34 import { SortSelect } from './sort-select';
35 import { i18n } from '../i18next';
36 import { T } from 'inferno-i18next';
50 type ReplyType = Comment | PrivateMessageI;
52 interface InboxState {
53 unreadOrAll: UnreadOrAll;
54 unreadType: UnreadType;
55 replies: Array<Comment>;
56 mentions: Array<Comment>;
57 messages: Array<PrivateMessageI>;
62 export class Inbox extends Component<any, InboxState> {
63 private subscription: Subscription;
64 private emptyState: InboxState = {
65 unreadOrAll: UnreadOrAll.Unread,
66 unreadType: UnreadType.All,
74 constructor(props: any, context: any) {
75 super(props, context);
77 this.state = this.emptyState;
78 this.handleSortChange = this.handleSortChange.bind(this);
80 this.subscription = WebSocketService.Instance.subject
81 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
83 msg => this.parseMessage(msg),
84 err => console.error(err),
85 () => console.log('complete')
91 componentWillUnmount() {
92 this.subscription.unsubscribe();
96 document.title = `/u/${UserService.Instance.user.username} ${i18n.t(
98 )} - ${WebSocketService.Instance.site.name}`;
102 let user = UserService.Instance.user;
104 <div class="container">
111 interpolation={{ user: user.username }}
113 #<Link to={`/u/${user.username}`}>#</Link>
117 href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
120 <svg class="icon mx-2 text-muted small">
121 <use xlinkHref="#icon-rss">#</use>
126 {this.state.replies.length +
127 this.state.mentions.length +
128 this.state.messages.length >
130 this.state.unreadOrAll == UnreadOrAll.Unread && (
131 <ul class="list-inline mb-1 text-muted small font-weight-bold">
132 <li className="list-inline-item">
133 <span class="pointer" onClick={this.markAllAsRead}>
134 {i18n.t('mark_all_as_read')}
140 {this.state.unreadType == UnreadType.All && this.all()}
141 {this.state.unreadType == UnreadType.Replies && this.replies()}
142 {this.state.unreadType == UnreadType.Mentions && this.mentions()}
143 {this.state.unreadType == UnreadType.Messages && this.messages()}
153 <div className="mb-2">
155 value={this.state.unreadOrAll}
156 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
157 class="custom-select custom-select-sm w-auto mr-2"
159 <option disabled>{i18n.t('type')}</option>
160 <option value={UnreadOrAll.Unread}>{i18n.t('unread')}</option>
161 <option value={UnreadOrAll.All}>{i18n.t('all')}</option>
164 value={this.state.unreadType}
165 onChange={linkEvent(this, this.handleUnreadTypeChange)}
166 class="custom-select custom-select-sm w-auto mr-2"
168 <option disabled>{i18n.t('type')}</option>
169 <option value={UnreadType.All}>{i18n.t('all')}</option>
170 <option value={UnreadType.Replies}>{i18n.t('replies')}</option>
171 <option value={UnreadType.Mentions}>{i18n.t('mentions')}</option>
172 <option value={UnreadType.Messages}>{i18n.t('messages')}</option>
175 sort={this.state.sort}
176 onChange={this.handleSortChange}
184 let combined: Array<ReplyType> = [];
186 combined.push(...this.state.replies);
187 combined.push(...this.state.mentions);
188 combined.push(...this.state.messages);
191 combined.sort((a, b) => b.published.localeCompare(a.published));
197 <CommentNodes nodes={[{ comment: i }]} noIndent markable />
199 <PrivateMessage privateMessage={i} />
210 nodes={commentsToFlatNodes(this.state.replies)}
221 {this.state.mentions.map(mention => (
222 <CommentNodes nodes={[{ comment: mention }]} noIndent markable />
231 {this.state.messages.map(message => (
232 <PrivateMessage privateMessage={message} />
241 {this.state.page > 1 && (
243 class="btn btn-sm btn-secondary mr-1"
244 onClick={linkEvent(this, this.prevPage)}
250 class="btn btn-sm btn-secondary"
251 onClick={linkEvent(this, this.nextPage)}
271 handleUnreadOrAllChange(i: Inbox, event: any) {
272 i.state.unreadOrAll = Number(event.target.value);
278 handleUnreadTypeChange(i: Inbox, event: any) {
279 i.state.unreadType = Number(event.target.value);
286 let repliesForm: GetRepliesForm = {
287 sort: SortType[this.state.sort],
288 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
289 page: this.state.page,
292 WebSocketService.Instance.getReplies(repliesForm);
294 let userMentionsForm: GetUserMentionsForm = {
295 sort: SortType[this.state.sort],
296 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
297 page: this.state.page,
300 WebSocketService.Instance.getUserMentions(userMentionsForm);
302 let privateMessagesForm: GetPrivateMessagesForm = {
303 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
304 page: this.state.page,
307 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
310 handleSortChange(val: SortType) {
311 this.state.sort = val;
313 this.setState(this.state);
318 WebSocketService.Instance.markAllAsRead();
321 parseMessage(msg: WebSocketJsonResponse) {
323 let res = wsJsonToRes(msg);
325 toast(i18n.t(msg.error), 'danger');
327 } else if (msg.reconnect) {
329 } else if (res.op == UserOperation.GetReplies) {
330 let data = res.data as GetRepliesResponse;
331 this.state.replies = data.replies;
332 this.sendUnreadCount();
333 window.scrollTo(0, 0);
334 this.setState(this.state);
335 } else if (res.op == UserOperation.GetUserMentions) {
336 let data = res.data as GetUserMentionsResponse;
337 this.state.mentions = data.mentions;
338 this.sendUnreadCount();
339 window.scrollTo(0, 0);
340 this.setState(this.state);
341 } else if (res.op == UserOperation.GetPrivateMessages) {
342 let data = res.data as PrivateMessagesResponse;
343 this.state.messages = data.messages;
344 this.sendUnreadCount();
345 window.scrollTo(0, 0);
346 this.setState(this.state);
347 } else if (res.op == UserOperation.EditPrivateMessage) {
348 let data = res.data as PrivateMessageResponse;
349 let found: PrivateMessageI = this.state.messages.find(
350 m => m.id === data.message.id
352 found.content = data.message.content;
353 found.updated = data.message.updated;
354 found.deleted = data.message.deleted;
355 // If youre in the unread view, just remove it from the list
356 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
357 this.state.messages = this.state.messages.filter(
358 r => r.id !== data.message.id
361 let found = this.state.messages.find(c => c.id == data.message.id);
362 found.read = data.message.read;
364 this.sendUnreadCount();
365 window.scrollTo(0, 0);
366 this.setState(this.state);
367 } else if (res.op == UserOperation.MarkAllAsRead) {
368 this.state.replies = [];
369 this.state.mentions = [];
370 this.state.messages = [];
371 this.sendUnreadCount();
372 window.scrollTo(0, 0);
373 this.setState(this.state);
374 } else if (res.op == UserOperation.EditComment) {
375 let data = res.data as CommentResponse;
376 editCommentRes(data, this.state.replies);
378 // If youre in the unread view, just remove it from the list
379 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
380 this.state.replies = this.state.replies.filter(
381 r => r.id !== data.comment.id
384 let found = this.state.replies.find(c => c.id == data.comment.id);
385 found.read = data.comment.read;
387 this.sendUnreadCount();
388 this.setState(this.state);
389 } else if (res.op == UserOperation.EditUserMention) {
390 let data = res.data as UserMentionResponse;
392 let found = this.state.mentions.find(c => c.id == data.mention.id);
393 found.content = data.mention.content;
394 found.updated = data.mention.updated;
395 found.removed = data.mention.removed;
396 found.deleted = data.mention.deleted;
397 found.upvotes = data.mention.upvotes;
398 found.downvotes = data.mention.downvotes;
399 found.score = data.mention.score;
401 // If youre in the unread view, just remove it from the list
402 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) {
403 this.state.mentions = this.state.mentions.filter(
404 r => r.id !== data.mention.id
407 let found = this.state.mentions.find(c => c.id == data.mention.id);
408 found.read = data.mention.read;
410 this.sendUnreadCount();
411 this.setState(this.state);
412 } else if (res.op == UserOperation.CreateComment) {
413 let data = res.data as CommentResponse;
415 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
416 this.state.replies.unshift(data.comment);
417 this.setState(this.state);
418 } else if (data.comment.creator_id == UserService.Instance.user.id) {
419 toast(i18n.t('reply_sent'));
421 this.setState(this.state);
422 } else if (res.op == UserOperation.CreatePrivateMessage) {
423 let data = res.data as PrivateMessageResponse;
424 if (data.message.recipient_id == UserService.Instance.user.id) {
425 this.state.messages.unshift(data.message);
426 this.setState(this.state);
428 } else if (res.op == UserOperation.SaveComment) {
429 let data = res.data as CommentResponse;
430 saveCommentRes(data, this.state.replies);
431 this.setState(this.state);
432 } else if (res.op == UserOperation.CreateCommentLike) {
433 let data = res.data as CommentResponse;
434 createCommentLikeRes(data, this.state.replies);
435 this.setState(this.state);
441 this.state.replies.filter(r => !r.read).length +
442 this.state.mentions.filter(r => !r.read).length +
443 this.state.messages.filter(
444 r => !r.read && r.creator_id !== UserService.Instance.user.id
446 UserService.Instance.sub.next({
447 user: UserService.Instance.user,