diff options
Diffstat (limited to 'ui/src/components')
-rw-r--r-- | ui/src/components/community.tsx | 57 | ||||
-rw-r--r-- | ui/src/components/inbox.tsx | 23 | ||||
-rw-r--r-- | ui/src/components/login.tsx | 2 | ||||
-rw-r--r-- | ui/src/components/main.tsx | 105 | ||||
-rw-r--r-- | ui/src/components/navbar.tsx | 88 | ||||
-rw-r--r-- | ui/src/components/post.tsx | 11 | ||||
-rw-r--r-- | ui/src/components/sidebar.tsx | 17 |
7 files changed, 204 insertions, 99 deletions
diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 3c5f6890..866b9eec 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -14,20 +14,17 @@ import { GetCommunityForm, ListingType, GetPostsResponse, + PostResponse, CreatePostLikeResponse, + AddModToCommunityResponse, + BanFromCommunityResponse, WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { PostListings } from './post-listings'; import { SortSelect } from './sort-select'; import { Sidebar } from './sidebar'; -import { - wsJsonToRes, - routeSortTypeToEnum, - fetchLimit, - postRefetchSeconds, - toast, -} from '../utils'; +import { wsJsonToRes, routeSortTypeToEnum, fetchLimit, toast } from '../utils'; import { T } from 'inferno-i18next'; import { i18n } from '../i18next'; @@ -37,6 +34,7 @@ interface State { communityName: string; moderators: Array<CommunityUser>; admins: Array<UserView>; + online: number; loading: boolean; posts: Array<Post>; sort: SortType; @@ -45,7 +43,6 @@ interface State { export class Community extends Component<any, State> { private subscription: Subscription; - private postFetcher: any; private emptyState: State = { community: { id: null, @@ -67,6 +64,7 @@ export class Community extends Component<any, State> { admins: [], communityId: Number(this.props.match.params.id), communityName: this.props.match.params.name, + online: null, loading: true, posts: [], sort: this.getSortTypeFromProps(this.props), @@ -108,7 +106,6 @@ export class Community extends Component<any, State> { componentWillUnmount() { this.subscription.unsubscribe(); - clearInterval(this.postFetcher); } // Necessary for back button for some reason @@ -158,6 +155,7 @@ export class Community extends Component<any, State> { community={this.state.community} moderators={this.state.moderators} admins={this.state.admins} + online={this.state.online} /> </div> </div> @@ -240,11 +238,6 @@ export class Community extends Component<any, State> { ); } - keepFetchingPosts() { - this.fetchPosts(); - this.postFetcher = setInterval(() => this.fetchPosts(), postRefetchSeconds); - } - fetchPosts() { let getPostsForm: GetPostsForm = { page: this.state.page, @@ -268,9 +261,10 @@ export class Community extends Component<any, State> { this.state.community = data.community; this.state.moderators = data.moderators; this.state.admins = data.admins; + this.state.online = data.online; document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`; this.setState(this.state); - this.keepFetchingPosts(); + this.fetchPosts(); } else if (res.op == UserOperation.EditCommunity) { let data = res.data as CommunityResponse; this.state.community = data.community; @@ -286,13 +280,44 @@ export class Community extends Component<any, State> { this.state.posts = data.posts; this.state.loading = false; this.setState(this.state); + } else if (res.op == UserOperation.EditPost) { + let data = res.data as PostResponse; + let found = this.state.posts.find(c => c.id == data.post.id); + + found.url = data.post.url; + found.name = data.post.name; + found.nsfw = data.post.nsfw; + + this.setState(this.state); + } else if (res.op == UserOperation.CreatePost) { + let data = res.data as PostResponse; + this.state.posts.unshift(data.post); + this.setState(this.state); } else if (res.op == UserOperation.CreatePostLike) { let data = res.data as CreatePostLikeResponse; let found = this.state.posts.find(c => c.id == data.post.id); - found.my_vote = data.post.my_vote; + found.score = data.post.score; found.upvotes = data.post.upvotes; found.downvotes = data.post.downvotes; + if (data.post.my_vote !== null) { + found.my_vote = data.post.my_vote; + found.upvoteLoading = false; + found.downvoteLoading = false; + } + + this.setState(this.state); + } else if (res.op == UserOperation.AddModToCommunity) { + let data = res.data as AddModToCommunityResponse; + this.state.moderators = data.moderators; + this.setState(this.state); + } else if (res.op == UserOperation.BanFromCommunity) { + let data = res.data as BanFromCommunityResponse; + + this.state.posts + .filter(p => p.creator_id == data.user.id) + .forEach(p => (p.banned = data.banned)); + this.setState(this.state); } } diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx index 41c1ce60..5fd9dbdc 100644 --- a/ui/src/components/inbox.tsx +++ b/ui/src/components/inbox.tsx @@ -421,10 +421,25 @@ export class Inbox extends Component<any, InboxState> { this.sendUnreadCount(); this.setState(this.state); } else if (res.op == UserOperation.CreateComment) { - // let res: CommentResponse = msg; - toast(i18n.t('reply_sent')); - // this.state.replies.unshift(res.comment); // TODO do this right - // this.setState(this.state); + let data = res.data as CommentResponse; + + if (data.recipient_ids.includes(UserService.Instance.user.id)) { + this.state.replies.unshift(data.comment); + this.setState(this.state); + } else if (data.comment.creator_id == UserService.Instance.user.id) { + toast(i18n.t('reply_sent')); + } + this.setState(this.state); + } else if (res.op == UserOperation.CreatePrivateMessage) { + let data = res.data as PrivateMessageResponse; + + if (data.message.recipient_id == UserService.Instance.user.id) { + this.state.messages.unshift(data.message); + this.setState(this.state); + } else if (data.message.creator_id == UserService.Instance.user.id) { + toast(i18n.t('message_sent')); + } + this.setState(this.state); } else if (res.op == UserOperation.SaveComment) { let data = res.data as CommentResponse; let found = this.state.replies.find(c => c.id == data.comment.id); diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index 64687a3d..b65b7a83 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -306,6 +306,7 @@ export class Login extends Component<any, State> { this.state = this.emptyState; this.setState(this.state); UserService.Instance.login(data); + WebSocketService.Instance.userJoin(); toast(i18n.t('logged_in')); this.props.history.push('/'); } else if (res.op == UserOperation.Register) { @@ -313,6 +314,7 @@ export class Login extends Component<any, State> { this.state = this.emptyState; this.setState(this.state); UserService.Instance.login(data); + WebSocketService.Instance.userJoin(); this.props.history.push('/communities'); } else if (res.op == UserOperation.PasswordReset) { toast(i18n.t('reset_password_mail_sent')); diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 6bf4164f..9ff6af44 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -15,8 +15,11 @@ import { SiteResponse, GetPostsResponse, CreatePostLikeResponse, + PostResponse, Post, GetPostsForm, + AddAdminResponse, + BanUserResponse, WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; @@ -31,7 +34,6 @@ import { fetchLimit, routeSortTypeToEnum, routeListingTypeToEnum, - postRefetchSeconds, pictshareAvatarThumbnail, showAvatars, toast, @@ -42,7 +44,7 @@ import { T } from 'inferno-i18next'; interface MainState { subscribedCommunities: Array<CommunityUser>; trendingCommunities: Array<Community>; - site: GetSiteResponse; + siteRes: GetSiteResponse; showEditSite: boolean; loading: boolean; posts: Array<Post>; @@ -53,11 +55,10 @@ interface MainState { export class Main extends Component<any, MainState> { private subscription: Subscription; - private postFetcher: any; private emptyState: MainState = { subscribedCommunities: [], trendingCommunities: [], - site: { + siteRes: { site: { id: null, name: null, @@ -133,12 +134,11 @@ export class Main extends Component<any, MainState> { WebSocketService.Instance.listCommunities(listCommunitiesForm); - this.keepFetchingPosts(); + this.fetchPosts(); } componentWillUnmount() { this.subscription.unsubscribe(); - clearInterval(this.postFetcher); } // Necessary for back button for some reason @@ -241,7 +241,7 @@ export class Main extends Component<any, MainState> { this.siteInfo() ) : ( <SiteForm - site={this.state.site.site} + site={this.state.siteRes.site} onCancel={this.handleEditCancel} /> )} @@ -262,7 +262,7 @@ export class Main extends Component<any, MainState> { <div> <div class="card border-secondary mb-3"> <div class="card-body"> - <h5 class="mb-0">{`${this.state.site.site.name}`}</h5> + <h5 class="mb-0">{`${this.state.siteRes.site.name}`}</h5> {this.canAdmin && ( <ul class="list-inline mb-1 text-muted small font-weight-bold"> <li className="list-inline-item"> @@ -279,7 +279,7 @@ export class Main extends Component<any, MainState> { <li className="list-inline-item badge badge-secondary"> <T i18nKey="number_online" - interpolation={{ count: this.state.site.online }} + interpolation={{ count: this.state.siteRes.online }} > # </T> @@ -288,7 +288,7 @@ export class Main extends Component<any, MainState> { <T i18nKey="number_of_users" interpolation={{ - count: this.state.site.site.number_of_users, + count: this.state.siteRes.site.number_of_users, }} > # @@ -298,7 +298,7 @@ export class Main extends Component<any, MainState> { <T i18nKey="number_of_communities" interpolation={{ - count: this.state.site.site.number_of_communities, + count: this.state.siteRes.site.number_of_communities, }} > # @@ -308,7 +308,7 @@ export class Main extends Component<any, MainState> { <T i18nKey="number_of_posts" interpolation={{ - count: this.state.site.site.number_of_posts, + count: this.state.siteRes.site.number_of_posts, }} > # @@ -318,7 +318,7 @@ export class Main extends Component<any, MainState> { <T i18nKey="number_of_comments" interpolation={{ - count: this.state.site.site.number_of_comments, + count: this.state.siteRes.site.number_of_comments, }} > # @@ -337,7 +337,7 @@ export class Main extends Component<any, MainState> { </T> : </li> - {this.state.site.admins.map(admin => ( + {this.state.siteRes.admins.map(admin => ( <li class="list-inline-item"> <Link class="text-info" to={`/u/${admin.name}`}> {admin.avatar && showAvatars() && ( @@ -355,13 +355,13 @@ export class Main extends Component<any, MainState> { </ul> </div> </div> - {this.state.site.site.description && ( + {this.state.siteRes.site.description && ( <div class="card border-secondary mb-3"> <div class="card-body"> <div className="md-div" dangerouslySetInnerHTML={mdToHtml( - this.state.site.site.description + this.state.siteRes.site.description )} /> </div> @@ -494,7 +494,7 @@ export class Main extends Component<any, MainState> { get canAdmin(): boolean { return ( UserService.Instance.user && - this.state.site.admins + this.state.siteRes.admins .map(a => a.id) .includes(UserService.Instance.user.id) ); @@ -548,11 +548,6 @@ export class Main extends Component<any, MainState> { window.scrollTo(0, 0); } - keepFetchingPosts() { - this.fetchPosts(); - this.postFetcher = setInterval(() => this.fetchPosts(), postRefetchSeconds); - } - fetchPosts() { let getPostsForm: GetPostsForm = { page: this.state.page, @@ -584,15 +579,15 @@ export class Main extends Component<any, MainState> { if (!data.site) { this.context.router.history.push('/setup'); } - this.state.site.admins = data.admins; - this.state.site.site = data.site; - this.state.site.banned = data.banned; - this.state.site.online = data.online; + this.state.siteRes.admins = data.admins; + this.state.siteRes.site = data.site; + this.state.siteRes.banned = data.banned; + this.state.siteRes.online = data.online; this.setState(this.state); document.title = `${WebSocketService.Instance.site.name}`; } else if (res.op == UserOperation.EditSite) { let data = res.data as SiteResponse; - this.state.site.site = data.site; + this.state.siteRes.site = data.site; this.state.showEditSite = false; this.setState(this.state); } else if (res.op == UserOperation.GetPosts) { @@ -600,13 +595,67 @@ export class Main extends Component<any, MainState> { this.state.posts = data.posts; this.state.loading = false; this.setState(this.state); + } else if (res.op == UserOperation.CreatePost) { + let data = res.data as PostResponse; + + // If you're on subscribed, only push it if you're subscribed. + if (this.state.type_ == ListingType.Subscribed) { + if ( + this.state.subscribedCommunities + .map(c => c.community_id) + .includes(data.post.community_id) + ) { + this.state.posts.unshift(data.post); + } + } else { + this.state.posts.unshift(data.post); + } + + this.setState(this.state); + } else if (res.op == UserOperation.EditPost) { + let data = res.data as PostResponse; + let found = this.state.posts.find(c => c.id == data.post.id); + + found.url = data.post.url; + found.name = data.post.name; + found.nsfw = data.post.nsfw; + + this.setState(this.state); } else if (res.op == UserOperation.CreatePostLike) { let data = res.data as CreatePostLikeResponse; let found = this.state.posts.find(c => c.id == data.post.id); - found.my_vote = data.post.my_vote; + found.score = data.post.score; found.upvotes = data.post.upvotes; found.downvotes = data.post.downvotes; + if (data.post.my_vote !== null) { + found.my_vote = data.post.my_vote; + found.upvoteLoading = false; + found.downvoteLoading = false; + } + + this.setState(this.state); + } else if (res.op == UserOperation.AddAdmin) { + let data = res.data as AddAdminResponse; + this.state.siteRes.admins = data.admins; + this.setState(this.state); + } else if (res.op == UserOperation.BanUser) { + let data = res.data as BanUserResponse; + let found = this.state.siteRes.banned.find(u => (u.id = data.user.id)); + + // Remove the banned if its found in the list, and the action is an unban + if (found && !data.banned) { + this.state.siteRes.banned = this.state.siteRes.banned.filter( + i => i.id !== data.user.id + ); + } else { + this.state.siteRes.banned.push(data.user); + } + + this.state.posts + .filter(p => p.creator_id == data.user.id) + .forEach(p => (p.banned = data.banned)); + this.setState(this.state); } } diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index eccc8116..c03dcd87 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -14,7 +14,9 @@ import { SortType, GetSiteResponse, Comment, + CommentResponse, PrivateMessage, + PrivateMessageResponse, WebSocketJsonResponse, } from '../interfaces'; import { @@ -35,7 +37,6 @@ interface NavbarState { replies: Array<Comment>; mentions: Array<Comment>; messages: Array<PrivateMessage>; - fetchCount: number; unreadCount: number; siteName: string; } @@ -46,7 +47,6 @@ export class Navbar extends Component<any, NavbarState> { emptyState: NavbarState = { isLoggedIn: UserService.Instance.user !== undefined, unreadCount: 0, - fetchCount: 0, replies: [], mentions: [], messages: [], @@ -58,8 +58,6 @@ export class Navbar extends Component<any, NavbarState> { super(props, context); this.state = this.emptyState; - this.keepFetchingUnreads(); - // Subscribe to user changes this.userSub = UserService.Instance.sub.subscribe(user => { this.state.isLoggedIn = user.user !== undefined; @@ -78,6 +76,8 @@ export class Navbar extends Component<any, NavbarState> { if (this.state.isLoggedIn) { this.requestNotificationPermission(); + // TODO couldn't get re-logging in to re-fetch unreads + this.fetchUnreads(); } WebSocketService.Instance.getSite(); @@ -211,45 +211,51 @@ export class Navbar extends Component<any, NavbarState> { } else if (res.op == UserOperation.GetReplies) { let data = res.data as GetRepliesResponse; let unreadReplies = data.replies.filter(r => !r.read); - if ( - unreadReplies.length > 0 && - this.state.fetchCount > 1 && - JSON.stringify(this.state.replies) !== JSON.stringify(unreadReplies) - ) { - this.notify(unreadReplies); - } this.state.replies = unreadReplies; + this.state.unreadCount = this.calculateUnreadCount(); this.setState(this.state); this.sendUnreadCount(); } else if (res.op == UserOperation.GetUserMentions) { let data = res.data as GetUserMentionsResponse; let unreadMentions = data.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.state.unreadCount = this.calculateUnreadCount(); this.setState(this.state); this.sendUnreadCount(); } else if (res.op == UserOperation.GetPrivateMessages) { let data = res.data as PrivateMessagesResponse; let unreadMessages = data.messages.filter(r => !r.read); - if ( - unreadMessages.length > 0 && - this.state.fetchCount > 1 && - JSON.stringify(this.state.messages) !== JSON.stringify(unreadMessages) - ) { - this.notify(unreadMessages); - } this.state.messages = unreadMessages; + this.state.unreadCount = this.calculateUnreadCount(); this.setState(this.state); this.sendUnreadCount(); + } else if (res.op == UserOperation.CreateComment) { + let data = res.data as CommentResponse; + + if (this.state.isLoggedIn) { + if (data.recipient_ids.includes(UserService.Instance.user.id)) { + this.state.replies.push(data.comment); + this.state.unreadCount++; + this.setState(this.state); + this.sendUnreadCount(); + this.notify(data.comment); + } + } + } else if (res.op == UserOperation.CreatePrivateMessage) { + let data = res.data as PrivateMessageResponse; + + if (this.state.isLoggedIn) { + if (data.message.recipient_id == UserService.Instance.user.id) { + this.state.messages.push(data.message); + this.state.unreadCount++; + this.setState(this.state); + this.sendUnreadCount(); + this.notify(data.message); + } + } } else if (res.op == UserOperation.GetSite) { let data = res.data as GetSiteResponse; @@ -261,11 +267,6 @@ export class Navbar extends Component<any, NavbarState> { } } - keepFetchingUnreads() { - this.fetchUnreads(); - setInterval(() => this.fetchUnreads(), 15000); - } - fetchUnreads() { if (this.state.isLoggedIn) { let repliesForm: GetRepliesForm = { @@ -292,7 +293,6 @@ export class Navbar extends Component<any, NavbarState> { WebSocketService.Instance.getReplies(repliesForm); WebSocketService.Instance.getUserMentions(userMentionsForm); WebSocketService.Instance.getPrivateMessages(privateMessagesForm); - this.state.fetchCount++; } } } @@ -304,11 +304,11 @@ export class Navbar extends Component<any, NavbarState> { sendUnreadCount() { UserService.Instance.sub.next({ user: UserService.Instance.user, - unreadCount: this.unreadCount, + unreadCount: this.state.unreadCount, }); } - get unreadCount() { + calculateUnreadCount(): number { return ( this.state.replies.filter(r => !r.read).length + this.state.mentions.filter(r => !r.read).length + @@ -330,24 +330,20 @@ export class Navbar extends Component<any, NavbarState> { } } - notify(replies: Array<Comment | PrivateMessage>) { - let recentReply = replies[0]; + notify(reply: Comment | PrivateMessage) { if (Notification.permission !== 'granted') Notification.requestPermission(); else { - var notification = new Notification( - `${replies.length} ${i18n.t('unread_messages')}`, - { - icon: recentReply.creator_avatar - ? recentReply.creator_avatar - : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`, - body: `${recentReply.creator_name}: ${recentReply.content}`, - } - ); + var notification = new Notification(reply.creator_name, { + icon: reply.creator_avatar + ? reply.creator_avatar + : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`, + body: `${reply.content}`, + }); notification.onclick = () => { this.context.router.history.push( - isCommentType(recentReply) - ? `/post/${recentReply.post_id}/comment/${recentReply.id}` + isCommentType(reply) + ? `/post/${reply.post_id}/comment/${reply.id}` : `/inbox` ); }; diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 36621248..99a20b46 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -47,6 +47,7 @@ interface PostState { community: Community; moderators: Array<CommunityUser>; admins: Array<UserView>; + online: number; scrolled?: boolean; scrolled_comment_id?: number; loading: boolean; @@ -62,6 +63,7 @@ export class Post extends Component<any, PostState> { community: null, moderators: [], admins: [], + online: null, scrolled: false, loading: true, crossPosts: [], @@ -280,6 +282,7 @@ export class Post extends Component<any, PostState> { community={this.state.community} moderators={this.state.moderators} admins={this.state.admins} + online={this.state.online} /> </div> ); @@ -378,6 +381,7 @@ export class Post extends Component<any, PostState> { this.state.community = data.community; this.state.moderators = data.moderators; this.state.admins = data.admins; + this.state.online = data.online; this.state.loading = false; document.title = `${this.state.post.name} - ${WebSocketService.Instance.site.name}`; @@ -432,10 +436,15 @@ export class Post extends Component<any, PostState> { this.setState(this.state); } else if (res.op == UserOperation.CreatePostLike) { let data = res.data as CreatePostLikeResponse; - this.state.post.my_vote = data.post.my_vote; this.state.post.score = data.post.score; this.state.post.upvotes = data.post.upvotes; this.state.post.downvotes = data.post.downvotes; + if (data.post.my_vote !== null) { + this.state.post.my_vote = data.post.my_vote; + this.state.post.upvoteLoading = false; + this.state.post.downvoteLoading = false; + } + this.setState(this.state); } else if (res.op == UserOperation.EditPost) { let data = res.data as PostResponse; diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx index 089dd558..0e6b68e6 100644 --- a/ui/src/components/sidebar.tsx +++ b/ui/src/components/sidebar.tsx @@ -22,6 +22,7 @@ interface SidebarProps { community: Community; moderators: Array<CommunityUser>; admins: Array<UserView>; + online: number; } interface SidebarState { @@ -156,10 +157,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { </form> )} <ul class="my-1 list-inline"> - <li className="list-inline-item"> - <Link className="badge badge-secondary" to="/communities"> - {community.category_name} - </Link> + <li className="list-inline-item badge badge-secondary"> + <T + i18nKey="number_online" + interpolation={{ count: this.props.online }} + > + # + </T> </li> <li className="list-inline-item badge badge-secondary"> <T @@ -186,6 +190,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { </T> </li> <li className="list-inline-item"> + <Link className="badge badge-secondary" to="/communities"> + {community.category_name} + </Link> + </li> + <li className="list-inline-item"> <Link className="badge badge-secondary" to={`/modlog/community/${this.props.community.id}`} |