1 import { Component, linkEvent } from 'inferno';
2 import { Helmet } from 'inferno-helmet';
3 import { Subscription } from 'rxjs';
11 GetUserMentionsResponse,
14 WebSocketJsonResponse,
15 PrivateMessage as PrivateMessageI,
16 GetPrivateMessagesForm,
17 PrivateMessagesResponse,
18 PrivateMessageResponse,
20 } from 'lemmy-js-client';
21 import { WebSocketService, UserService } from '../services';
38 import { CommentNodes } from './comment-nodes';
39 import { PrivateMessage } from './private-message';
40 import { SortSelect } from './sort-select';
41 import { i18n } from '../i18next';
55 type ReplyType = Comment | PrivateMessageI;
57 interface InboxState {
58 unreadOrAll: UnreadOrAll;
59 messageType: MessageType;
62 messages: PrivateMessageI[];
69 export class Inbox extends Component<any, InboxState> {
70 private isoData = setIsoData(this.context);
71 private subscription: Subscription;
72 private emptyState: InboxState = {
73 unreadOrAll: UnreadOrAll.Unread,
74 messageType: MessageType.All,
80 site: this.isoData.site.site,
84 constructor(props: any, context: any) {
85 super(props, context);
87 this.state = this.emptyState;
88 this.handleSortChange = this.handleSortChange.bind(this);
90 this.parseMessage = this.parseMessage.bind(this);
91 this.subscription = wsSubscribe(this.parseMessage);
93 // Only fetch the data if coming from another route
94 if (this.isoData.path == this.context.router.route.match.url) {
95 this.state.replies = this.isoData.routeData[0].replies;
96 this.state.mentions = this.isoData.routeData[1].mentions;
97 this.state.messages = this.isoData.routeData[2].messages;
98 this.sendUnreadCount();
99 this.state.loading = false;
105 componentWillUnmount() {
107 this.subscription.unsubscribe();
111 get documentTitle(): string {
112 return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${
119 <div class="container">
120 <Helmet title={this.documentTitle} />
121 {this.state.loading ? (
123 <svg class="icon icon-spinner spin">
124 <use xlinkHref="#icon-spinner"></use>
134 href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
139 <svg class="icon ml-2 text-muted small">
140 <use xlinkHref="#icon-rss">#</use>
145 {this.state.replies.length +
146 this.state.mentions.length +
147 this.state.messages.length >
149 this.state.unreadOrAll == UnreadOrAll.Unread && (
150 <ul class="list-inline mb-1 text-muted small font-weight-bold">
151 <li className="list-inline-item">
154 onClick={linkEvent(this, this.markAllAsRead)}
156 {i18n.t('mark_all_as_read')}
162 {this.state.messageType == MessageType.All && this.all()}
163 {this.state.messageType == MessageType.Replies && this.replies()}
164 {this.state.messageType == MessageType.Mentions &&
166 {this.state.messageType == MessageType.Messages &&
176 unreadOrAllRadios() {
178 <div class="btn-group btn-group-toggle flex-wrap mb-2">
180 className={`btn btn-outline-secondary pointer
181 ${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
186 value={UnreadOrAll.Unread}
187 checked={this.state.unreadOrAll == UnreadOrAll.Unread}
188 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
193 className={`btn btn-outline-secondary pointer
194 ${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
199 value={UnreadOrAll.All}
200 checked={this.state.unreadOrAll == UnreadOrAll.All}
201 onChange={linkEvent(this, this.handleUnreadOrAllChange)}
209 messageTypeRadios() {
211 <div class="btn-group btn-group-toggle flex-wrap mb-2">
213 className={`btn btn-outline-secondary pointer
214 ${this.state.messageType == MessageType.All && 'active'}
219 value={MessageType.All}
220 checked={this.state.messageType == MessageType.All}
221 onChange={linkEvent(this, this.handleMessageTypeChange)}
226 className={`btn btn-outline-secondary pointer
227 ${this.state.messageType == MessageType.Replies && 'active'}
232 value={MessageType.Replies}
233 checked={this.state.messageType == MessageType.Replies}
234 onChange={linkEvent(this, this.handleMessageTypeChange)}
239 className={`btn btn-outline-secondary pointer
240 ${this.state.messageType == MessageType.Mentions && 'active'}
245 value={MessageType.Mentions}
246 checked={this.state.messageType == MessageType.Mentions}
247 onChange={linkEvent(this, this.handleMessageTypeChange)}
252 className={`btn btn-outline-secondary pointer
253 ${this.state.messageType == MessageType.Messages && 'active'}
258 value={MessageType.Messages}
259 checked={this.state.messageType == MessageType.Messages}
260 onChange={linkEvent(this, this.handleMessageTypeChange)}
270 <div className="mb-2">
271 <span class="mr-3">{this.unreadOrAllRadios()}</span>
272 <span class="mr-3">{this.messageTypeRadios()}</span>
274 sort={this.state.sort}
275 onChange={this.handleSortChange}
282 combined(): ReplyType[] {
284 ...this.state.replies,
285 ...this.state.mentions,
286 ...this.state.messages,
287 ].sort((a, b) => b.published.localeCompare(a.published));
293 {this.combined().map(i =>
297 nodes={[{ comment: i }]}
302 enableDownvotes={this.state.site.enable_downvotes}
305 <PrivateMessage key={i.id} privateMessage={i} />
316 nodes={commentsToFlatNodes(this.state.replies)}
321 enableDownvotes={this.state.site.enable_downvotes}
330 {this.state.mentions.map(mention => (
333 nodes={[{ comment: mention }]}
338 enableDownvotes={this.state.site.enable_downvotes}
348 {this.state.messages.map(message => (
349 <PrivateMessage key={message.id} privateMessage={message} />
358 {this.state.page > 1 && (
360 class="btn btn-secondary mr-1"
361 onClick={linkEvent(this, this.prevPage)}
366 {this.unreadCount() > 0 && (
368 class="btn btn-secondary"
369 onClick={linkEvent(this, this.nextPage)}
390 handleUnreadOrAllChange(i: Inbox, event: any) {
391 i.state.unreadOrAll = Number(event.target.value);
397 handleMessageTypeChange(i: Inbox, event: any) {
398 i.state.messageType = Number(event.target.value);
404 static fetchInitialData(auth: string, _path: string): Promise<any>[] {
405 let promises: Promise<any>[] = [];
407 // It can be /u/me, or /username/1
408 let repliesForm: GetRepliesForm = {
414 setAuth(repliesForm, auth);
415 promises.push(lemmyHttp.getReplies(repliesForm));
417 let userMentionsForm: GetUserMentionsForm = {
423 setAuth(userMentionsForm, auth);
424 promises.push(lemmyHttp.getUserMentions(userMentionsForm));
426 let privateMessagesForm: GetPrivateMessagesForm = {
431 setAuth(privateMessagesForm, auth);
432 promises.push(lemmyHttp.getPrivateMessages(privateMessagesForm));
438 let repliesForm: GetRepliesForm = {
439 sort: this.state.sort,
440 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
441 page: this.state.page,
444 WebSocketService.Instance.getReplies(repliesForm);
446 let userMentionsForm: GetUserMentionsForm = {
447 sort: this.state.sort,
448 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
449 page: this.state.page,
452 WebSocketService.Instance.getUserMentions(userMentionsForm);
454 let privateMessagesForm: GetPrivateMessagesForm = {
455 unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
456 page: this.state.page,
459 WebSocketService.Instance.getPrivateMessages(privateMessagesForm);
462 handleSortChange(val: SortType) {
463 this.state.sort = val;
465 this.setState(this.state);
469 markAllAsRead(i: Inbox) {
470 WebSocketService.Instance.markAllAsRead();
471 i.state.replies = [];
472 i.state.mentions = [];
473 i.state.messages = [];
475 window.scrollTo(0, 0);
479 parseMessage(msg: WebSocketJsonResponse) {
481 let res = wsJsonToRes(msg);
483 toast(i18n.t(msg.error), 'danger');
485 } else if (msg.reconnect) {
487 } else if (res.op == UserOperation.GetReplies) {
488 let data = res.data as GetRepliesResponse;
489 this.state.replies = data.replies;
490 this.state.loading = false;
491 this.sendUnreadCount();
492 window.scrollTo(0, 0);
493 this.setState(this.state);
495 } else if (res.op == UserOperation.GetUserMentions) {
496 let data = res.data as GetUserMentionsResponse;
497 this.state.mentions = data.mentions;
498 this.sendUnreadCount();
499 window.scrollTo(0, 0);
500 this.setState(this.state);
502 } else if (res.op == UserOperation.GetPrivateMessages) {
503 let data = res.data as PrivateMessagesResponse;
504 this.state.messages = data.messages;
505 this.sendUnreadCount();
506 window.scrollTo(0, 0);
507 this.setState(this.state);
509 } else if (res.op == UserOperation.EditPrivateMessage) {
510 let data = res.data as PrivateMessageResponse;
511 let found: PrivateMessageI = this.state.messages.find(
512 m => m.id === data.message.id
515 found.content = data.message.content;
516 found.updated = data.message.updated;
518 this.setState(this.state);
519 } else if (res.op == UserOperation.DeletePrivateMessage) {
520 let data = res.data as PrivateMessageResponse;
521 let found: PrivateMessageI = this.state.messages.find(
522 m => m.id === data.message.id
525 found.deleted = data.message.deleted;
526 found.updated = data.message.updated;
528 this.setState(this.state);
529 } else if (res.op == UserOperation.MarkPrivateMessageAsRead) {
530 let data = res.data as PrivateMessageResponse;
531 let found: PrivateMessageI = this.state.messages.find(
532 m => m.id === data.message.id
536 found.updated = data.message.updated;
538 // If youre in the unread view, just remove it from the list
539 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) {
540 this.state.messages = this.state.messages.filter(
541 r => r.id !== data.message.id
544 let found = this.state.messages.find(c => c.id == data.message.id);
545 found.read = data.message.read;
548 this.sendUnreadCount();
549 this.setState(this.state);
550 } else if (res.op == UserOperation.MarkAllAsRead) {
551 // Moved to be instant
553 res.op == UserOperation.EditComment ||
554 res.op == UserOperation.DeleteComment ||
555 res.op == UserOperation.RemoveComment
557 let data = res.data as CommentResponse;
558 editCommentRes(data, this.state.replies);
559 this.setState(this.state);
560 } else if (res.op == UserOperation.MarkCommentAsRead) {
561 let data = res.data as CommentResponse;
563 // If youre in the unread view, just remove it from the list
564 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) {
565 this.state.replies = this.state.replies.filter(
566 r => r.id !== data.comment.id
569 let found = this.state.replies.find(c => c.id == data.comment.id);
570 found.read = data.comment.read;
572 this.sendUnreadCount();
573 this.setState(this.state);
575 } else if (res.op == UserOperation.MarkUserMentionAsRead) {
576 let data = res.data as UserMentionResponse;
578 let found = this.state.mentions.find(c => c.id == data.mention.id);
579 found.content = data.mention.content;
580 found.updated = data.mention.updated;
581 found.removed = data.mention.removed;
582 found.deleted = data.mention.deleted;
583 found.upvotes = data.mention.upvotes;
584 found.downvotes = data.mention.downvotes;
585 found.score = data.mention.score;
587 // If youre in the unread view, just remove it from the list
588 if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) {
589 this.state.mentions = this.state.mentions.filter(
590 r => r.id !== data.mention.id
593 let found = this.state.mentions.find(c => c.id == data.mention.id);
594 found.read = data.mention.read;
596 this.sendUnreadCount();
597 this.setState(this.state);
598 } else if (res.op == UserOperation.CreateComment) {
599 let data = res.data as CommentResponse;
601 if (data.recipient_ids.includes(UserService.Instance.user.id)) {
602 this.state.replies.unshift(data.comment);
603 this.setState(this.state);
604 } else if (data.comment.creator_id == UserService.Instance.user.id) {
605 toast(i18n.t('reply_sent'));
607 } else if (res.op == UserOperation.CreatePrivateMessage) {
608 let data = res.data as PrivateMessageResponse;
609 if (data.message.recipient_id == UserService.Instance.user.id) {
610 this.state.messages.unshift(data.message);
611 this.setState(this.state);
613 } else if (res.op == UserOperation.SaveComment) {
614 let data = res.data as CommentResponse;
615 saveCommentRes(data, this.state.replies);
616 this.setState(this.state);
618 } else if (res.op == UserOperation.CreateCommentLike) {
619 let data = res.data as CommentResponse;
620 createCommentLikeRes(data, this.state.replies);
621 this.setState(this.state);
626 UserService.Instance.unreadCountSub.next(this.unreadCount());
629 unreadCount(): number {
631 this.state.replies.filter(r => !r.read).length +
632 this.state.mentions.filter(r => !r.read).length +
633 this.state.messages.filter(
635 UserService.Instance.user &&
637 r.creator_id !== UserService.Instance.user.id