diff options
author | Dessalines <tyhou13@gmx.com> | 2019-10-19 17:46:29 -0700 |
---|---|---|
committer | Dessalines <tyhou13@gmx.com> | 2019-10-19 17:46:29 -0700 |
commit | 02dd9ac32a491e7ee41a012b11ff90666455066b (patch) | |
tree | 446a4d3acff2b3b7f17e6ce3a9555143a6f79d53 /ui/src/components | |
parent | 5547ecdeafe09be78c76236fbf01e73c638addf2 (diff) |
Adding username mentions / tagging from comments.
- Fixes #293
Diffstat (limited to 'ui/src/components')
-rw-r--r-- | ui/src/components/comment-node.tsx | 30 | ||||
-rw-r--r-- | ui/src/components/inbox.tsx | 168 | ||||
-rw-r--r-- | ui/src/components/navbar.tsx | 50 | ||||
-rw-r--r-- | ui/src/components/search.tsx | 16 |
4 files changed, 217 insertions, 47 deletions
diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 7eb5d9d4..e3d82196 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -4,6 +4,7 @@ import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, + EditUserMentionForm, SaveCommentForm, BanFromCommunityForm, BanUserForm, @@ -686,16 +687,25 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handleMarkRead(i: CommentNode) { - let form: CommentFormI = { - content: i.props.node.comment.content, - edit_id: i.props.node.comment.id, - creator_id: i.props.node.comment.creator_id, - post_id: i.props.node.comment.post_id, - parent_id: i.props.node.comment.parent_id, - read: !i.props.node.comment.read, - auth: null, - }; - WebSocketService.Instance.editComment(form); + // if it has a user_mention_id field, then its a mention + if (i.props.node.comment.user_mention_id) { + let form: EditUserMentionForm = { + user_mention_id: i.props.node.comment.user_mention_id, + read: !i.props.node.comment.read, + }; + WebSocketService.Instance.editUserMention(form); + } else { + let form: CommentFormI = { + content: i.props.node.comment.content, + edit_id: i.props.node.comment.id, + creator_id: i.props.node.comment.creator_id, + post_id: i.props.node.comment.post_id, + parent_id: i.props.node.comment.parent_id, + read: !i.props.node.comment.read, + auth: null, + }; + WebSocketService.Instance.editComment(form); + } } handleModBanFromCommunityShow(i: CommentNode) { diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx index 9d548f8d..6e961b17 100644 --- a/ui/src/components/inbox.tsx +++ b/ui/src/components/inbox.tsx @@ -8,6 +8,9 @@ import { SortType, GetRepliesForm, GetRepliesResponse, + GetUserMentionsForm, + GetUserMentionsResponse, + UserMentionResponse, CommentResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; @@ -16,14 +19,22 @@ import { CommentNodes } from './comment-nodes'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; -enum UnreadType { +enum UnreadOrAll { Unread, All, } +enum UnreadType { + Both, + Replies, + Mentions, +} + interface InboxState { + unreadOrAll: UnreadOrAll; unreadType: UnreadType; replies: Array<Comment>; + mentions: Array<Comment>; sort: SortType; page: number; } @@ -31,8 +42,10 @@ interface InboxState { export class Inbox extends Component<any, InboxState> { private subscription: Subscription; private emptyState: InboxState = { - unreadType: UnreadType.Unread, + unreadOrAll: UnreadOrAll.Unread, + unreadType: UnreadType.Both, replies: [], + mentions: [], sort: SortType.New, page: 1, }; @@ -83,8 +96,8 @@ export class Inbox extends Component<any, InboxState> { </T> </span> </h5> - {this.state.replies.length > 0 && - this.state.unreadType == UnreadType.Unread && ( + {this.state.replies.length + this.state.mentions.length > 0 && + this.state.unreadOrAll == UnreadOrAll.Unread && ( <ul class="list-inline mb-1 text-muted small font-weight-bold"> <li className="list-inline-item"> <span class="pointer" onClick={this.markAllAsRead}> @@ -94,7 +107,9 @@ export class Inbox extends Component<any, InboxState> { </ul> )} {this.selects()} - {this.replies()} + {this.state.unreadType == UnreadType.Both && this.both()} + {this.state.unreadType == UnreadType.Replies && this.replies()} + {this.state.unreadType == UnreadType.Mentions && this.mentions()} {this.paginator()} </div> </div> @@ -106,24 +121,42 @@ export class Inbox extends Component<any, InboxState> { return ( <div className="mb-2"> <select - value={this.state.unreadType} - onChange={linkEvent(this, this.handleUnreadTypeChange)} - class="custom-select custom-select-sm w-auto" + value={this.state.unreadOrAll} + onChange={linkEvent(this, this.handleUnreadOrAllChange)} + class="custom-select custom-select-sm w-auto mr-2" > <option disabled> <T i18nKey="type">#</T> </option> - <option value={UnreadType.Unread}> + <option value={UnreadOrAll.Unread}> <T i18nKey="unread">#</T> </option> - <option value={UnreadType.All}> + <option value={UnreadOrAll.All}> <T i18nKey="all">#</T> </option> </select> <select + value={this.state.unreadType} + onChange={linkEvent(this, this.handleUnreadTypeChange)} + class="custom-select custom-select-sm w-auto mr-2" + > + <option disabled> + <T i18nKey="type">#</T> + </option> + <option value={UnreadType.Both}> + <T i18nKey="both">#</T> + </option> + <option value={UnreadType.Replies}> + <T i18nKey="replies">#</T> + </option> + <option value={UnreadType.Mentions}> + <T i18nKey="mentions">#</T> + </option> + </select> + <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} - class="custom-select custom-select-sm w-auto ml-2" + class="custom-select custom-select-sm w-auto" > <option disabled> <T i18nKey="sort_type">#</T> @@ -151,6 +184,37 @@ export class Inbox extends Component<any, InboxState> { ); } + both() { + let combined: Array<{ + type_: string; + data: Comment; + }> = []; + let replies = this.state.replies.map(e => { + return { type_: 'replies', data: e }; + }); + let mentions = this.state.mentions.map(e => { + return { type_: 'mentions', data: e }; + }); + + combined.push(...replies); + combined.push(...mentions); + + // Sort it + if (this.state.sort == SortType.New) { + combined.sort((a, b) => b.data.published.localeCompare(a.data.published)); + } else { + combined.sort((a, b) => b.data.score - a.data.score); + } + + return ( + <div> + {combined.map(i => ( + <CommentNodes nodes={[{ comment: i.data }]} noIndent markable /> + ))} + </div> + ); + } + replies() { return ( <div> @@ -161,6 +225,16 @@ export class Inbox extends Component<any, InboxState> { ); } + mentions() { + return ( + <div> + {this.state.mentions.map(mention => ( + <CommentNodes nodes={[{ comment: mention }]} noIndent markable /> + ))} + </div> + ); + } + paginator() { return ( <div class="mt-2"> @@ -194,6 +268,13 @@ export class Inbox extends Component<any, InboxState> { i.refetch(); } + handleUnreadOrAllChange(i: Inbox, event: any) { + i.state.unreadOrAll = Number(event.target.value); + i.state.page = 1; + i.setState(i.state); + i.refetch(); + } + handleUnreadTypeChange(i: Inbox, event: any) { i.state.unreadType = Number(event.target.value); i.state.page = 1; @@ -202,13 +283,21 @@ export class Inbox extends Component<any, InboxState> { } refetch() { - let form: GetRepliesForm = { + let repliesForm: GetRepliesForm = { sort: SortType[this.state.sort], - unread_only: this.state.unreadType == UnreadType.Unread, + unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, page: this.state.page, limit: 9999, }; - WebSocketService.Instance.getReplies(form); + WebSocketService.Instance.getReplies(repliesForm); + + let userMentionsForm: GetUserMentionsForm = { + sort: SortType[this.state.sort], + unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, + page: this.state.page, + limit: 9999, + }; + WebSocketService.Instance.getUserMentions(userMentionsForm); } handleSortChange(i: Inbox, event: any) { @@ -228,13 +317,21 @@ export class Inbox extends Component<any, InboxState> { if (msg.error) { alert(i18n.t(msg.error)); return; - } else if ( - op == UserOperation.GetReplies || - op == UserOperation.MarkAllAsRead - ) { + } else if (op == UserOperation.GetReplies) { let res: GetRepliesResponse = msg; this.state.replies = res.replies; - this.sendRepliesCount(); + this.sendUnreadCount(); + window.scrollTo(0, 0); + this.setState(this.state); + } else if (op == UserOperation.GetUserMentions) { + let res: GetUserMentionsResponse = msg; + this.state.mentions = res.mentions; + this.sendUnreadCount(); + window.scrollTo(0, 0); + this.setState(this.state); + } else if (op == UserOperation.MarkAllAsRead) { + this.state.replies = []; + this.state.mentions = []; window.scrollTo(0, 0); this.setState(this.state); } else if (op == UserOperation.EditComment) { @@ -250,7 +347,7 @@ export class Inbox extends Component<any, InboxState> { found.score = res.comment.score; // If youre in the unread view, just remove it from the list - if (this.state.unreadType == UnreadType.Unread && res.comment.read) { + if (this.state.unreadOrAll == UnreadOrAll.Unread && res.comment.read) { this.state.replies = this.state.replies.filter( r => r.id !== res.comment.id ); @@ -258,8 +355,30 @@ export class Inbox extends Component<any, InboxState> { let found = this.state.replies.find(c => c.id == res.comment.id); found.read = res.comment.read; } - this.sendRepliesCount(); + this.sendUnreadCount(); + this.setState(this.state); + } else if (op == UserOperation.EditUserMention) { + let res: UserMentionResponse = msg; + let found = this.state.mentions.find(c => c.id == res.mention.id); + found.content = res.mention.content; + found.updated = res.mention.updated; + found.removed = res.mention.removed; + found.deleted = res.mention.deleted; + found.upvotes = res.mention.upvotes; + found.downvotes = res.mention.downvotes; + found.score = res.mention.score; + + // If youre in the unread view, just remove it from the list + if (this.state.unreadOrAll == UnreadOrAll.Unread && res.mention.read) { + this.state.mentions = this.state.mentions.filter( + r => r.id !== res.mention.id + ); + } else { + let found = this.state.mentions.find(c => c.id == res.mention.id); + found.read = res.mention.read; + } + this.sendUnreadCount(); this.setState(this.state); } else if (op == UserOperation.CreateComment) { // let res: CommentResponse = msg; @@ -284,10 +403,13 @@ export class Inbox extends Component<any, InboxState> { } } - sendRepliesCount() { + sendUnreadCount() { + let count = + this.state.replies.filter(r => !r.read).length + + this.state.mentions.filter(r => !r.read).length; UserService.Instance.sub.next({ user: UserService.Instance.user, - unreadCount: this.state.replies.filter(r => !r.read).length, + unreadCount: count, }); } } diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index ba0dead2..151559df 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -7,6 +7,8 @@ import { UserOperation, GetRepliesForm, GetRepliesResponse, + GetUserMentionsForm, + GetUserMentionsResponse, SortType, GetSiteResponse, Comment, @@ -21,6 +23,7 @@ interface NavbarState { expanded: boolean; expandUserDropdown: boolean; replies: Array<Comment>; + mentions: Array<Comment>; fetchCount: number; unreadCount: number; siteName: string; @@ -34,6 +37,7 @@ export class Navbar extends Component<any, NavbarState> { unreadCount: 0, fetchCount: 0, replies: [], + mentions: [], expanded: false, expandUserDropdown: false, siteName: undefined, @@ -44,7 +48,7 @@ export class Navbar extends Component<any, NavbarState> { this.state = this.emptyState; this.handleOverviewClick = this.handleOverviewClick.bind(this); - this.keepFetchingReplies(); + this.keepFetchingUnreads(); // Subscribe to user changes this.userSub = UserService.Instance.sub.subscribe(user => { @@ -233,7 +237,22 @@ export class Navbar extends Component<any, NavbarState> { } this.state.replies = unreadReplies; - this.sendRepliesCount(res); + this.setState(this.state); + this.sendUnreadCount(); + } else if (op == UserOperation.GetUserMentions) { + let res: GetUserMentionsResponse = msg; + let unreadMentions = res.mentions.filter(r => !r.read); + if ( + unreadMentions.length > 0 && + this.state.fetchCount > 1 && + JSON.stringify(this.state.mentions) !== JSON.stringify(unreadMentions) + ) { + this.notify(unreadMentions); + } + + this.state.mentions = unreadMentions; + this.setState(this.state); + this.sendUnreadCount(); } else if (op == UserOperation.GetSite) { let res: GetSiteResponse = msg; @@ -245,12 +264,12 @@ export class Navbar extends Component<any, NavbarState> { } } - keepFetchingReplies() { - this.fetchReplies(); - setInterval(() => this.fetchReplies(), 15000); + keepFetchingUnreads() { + this.fetchUnreads(); + setInterval(() => this.fetchUnreads(), 15000); } - fetchReplies() { + fetchUnreads() { if (this.state.isLoggedIn) { let repliesForm: GetRepliesForm = { sort: SortType[SortType.New], @@ -258,8 +277,16 @@ export class Navbar extends Component<any, NavbarState> { page: 1, limit: 9999, }; + + let userMentionsForm: GetUserMentionsForm = { + sort: SortType[SortType.New], + unread_only: true, + page: 1, + limit: 9999, + }; if (this.currentLocation !== '/inbox') { WebSocketService.Instance.getReplies(repliesForm); + WebSocketService.Instance.getUserMentions(userMentionsForm); this.state.fetchCount++; } } @@ -269,13 +296,20 @@ export class Navbar extends Component<any, NavbarState> { return this.context.router.history.location.pathname; } - sendRepliesCount(res: GetRepliesResponse) { + sendUnreadCount() { UserService.Instance.sub.next({ user: UserService.Instance.user, - unreadCount: res.replies.filter(r => !r.read).length, + unreadCount: this.unreadCount, }); } + get unreadCount() { + return ( + this.state.replies.filter(r => !r.read).length + + this.state.mentions.filter(r => !r.read).length + ); + } + requestNotificationPermission() { if (UserService.Instance.user) { document.addEventListener('DOMContentLoaded', function() { diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx index daf9aa2d..68b4ee88 100644 --- a/ui/src/components/search.tsx +++ b/ui/src/components/search.tsx @@ -396,11 +396,16 @@ export class Search extends Component<any, SearchState> { let res = this.state.searchResponse; return ( <div> - {res && res.op && res.posts.length == 0 && res.comments.length == 0 && ( - <span> - <T i18nKey="no_results">#</T> - </span> - )} + {res && + res.op && + res.posts.length == 0 && + res.comments.length == 0 && + res.communities.length == 0 && + res.users.length == 0 && ( + <span> + <T i18nKey="no_results">#</T> + </span> + )} </div> ); } @@ -420,7 +425,6 @@ export class Search extends Component<any, SearchState> { } search() { - // TODO community let form: SearchForm = { q: this.state.q, type_: SearchType[this.state.type_], |