Add post, inbox, and user routes.

This commit is contained in:
Dessalines 2020-09-08 19:48:17 -05:00
parent bfce461a14
commit 95b74ad74c
10 changed files with 509 additions and 512 deletions

View file

@ -202,7 +202,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}} }}
/> />
<span class="mx-2"></span> <span class="mx-2"></span>
<Link class="mr-2" to={`/post/${node.comment.post_id}`}> <Link className="mr-2" to={`/post/${node.comment.post_id}`}>
{node.comment.post_name} {node.comment.post_name}
</Link> </Link>
</> </>
@ -343,7 +343,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{!this.myComment && ( {!this.myComment && (
<button class="btn btn-link btn-animate"> <button class="btn btn-link btn-animate">
<Link <Link
class="text-muted" className="text-muted"
to={`/create_private_message/recipient/${node.comment.creator_id}`} to={`/create_private_message/recipient/${node.comment.creator_id}`}
title={i18n.t('message').toLowerCase()} title={i18n.t('message').toLowerCase()}
> >
@ -757,7 +757,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
let node = this.props.node; let node = this.props.node;
return ( return (
<Link <Link
class="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
to={`/post/${node.comment.post_id}/comment/${node.comment.id}`} to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')} title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')}
> >

View file

@ -1,7 +1,6 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Helmet } from 'inferno-helmet'; import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { import {
UserOperation, UserOperation,
Comment, Comment,
@ -17,7 +16,6 @@ import {
GetPrivateMessagesForm, GetPrivateMessagesForm,
PrivateMessagesResponse, PrivateMessagesResponse,
PrivateMessageResponse, PrivateMessageResponse,
GetSiteResponse,
Site, Site,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
@ -31,6 +29,11 @@ import {
createCommentLikeRes, createCommentLikeRes,
commentsToFlatNodes, commentsToFlatNodes,
setupTippy, setupTippy,
setIsoData,
wsSubscribe,
lemmyHttp,
setAuth,
isBrowser,
} from '../utils'; } from '../utils';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { PrivateMessage } from './private-message'; import { PrivateMessage } from './private-message';
@ -60,9 +63,11 @@ interface InboxState {
sort: SortType; sort: SortType;
page: number; page: number;
site: Site; site: Site;
loading: boolean;
} }
export class Inbox extends Component<any, InboxState> { export class Inbox extends Component<any, InboxState> {
private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: InboxState = { private emptyState: InboxState = {
unreadOrAll: UnreadOrAll.Unread, unreadOrAll: UnreadOrAll.Unread,
@ -72,20 +77,8 @@ export class Inbox extends Component<any, InboxState> {
messages: [], messages: [],
sort: SortType.New, sort: SortType.New,
page: 1, page: 1,
site: { site: this.isoData.site.site,
id: undefined, loading: true,
name: undefined,
creator_id: undefined,
published: undefined,
creator_name: undefined,
number_of_users: undefined,
number_of_posts: undefined,
number_of_comments: undefined,
number_of_communities: undefined,
enable_downvotes: undefined,
open_registration: undefined,
enable_nsfw: undefined,
},
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -94,77 +87,88 @@ export class Inbox extends Component<any, InboxState> {
this.state = this.emptyState; this.state = this.emptyState;
this.handleSortChange = this.handleSortChange.bind(this); this.handleSortChange = this.handleSortChange.bind(this);
this.subscription = WebSocketService.Instance.subject this.parseMessage = this.parseMessage.bind(this);
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) this.subscription = wsSubscribe(this.parseMessage);
.subscribe(
msg => this.parseMessage(msg),
err => console.error(err),
() => console.log('complete')
);
this.refetch(); // Only fetch the data if coming from another route
WebSocketService.Instance.getSite(); if (this.isoData.path == this.context.router.route.match.url) {
this.state.replies = this.isoData.routeData[0].replies;
this.state.mentions = this.isoData.routeData[1].mentions;
this.state.messages = this.isoData.routeData[2].messages;
this.sendUnreadCount();
this.state.loading = false;
} else {
this.refetch();
}
} }
componentWillUnmount() { componentWillUnmount() {
this.subscription.unsubscribe(); if (isBrowser()) {
this.subscription.unsubscribe();
}
} }
get documentTitle(): string { get documentTitle(): string {
if (this.state.site.name) { return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${
return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${ this.state.site.name
this.state.site.name }`;
}`;
} else {
return 'Lemmy';
}
} }
render() { render() {
return ( return (
<div class="container"> <div class="container">
<Helmet title={this.documentTitle} /> <Helmet title={this.documentTitle} />
<div class="row"> {this.state.loading ? (
<div class="col-12"> <h5>
<h5 class="mb-1"> <svg class="icon icon-spinner spin">
{i18n.t('inbox')} <use xlinkHref="#icon-spinner"></use>
<small> </svg>
<a </h5>
href={`/feeds/inbox/${UserService.Instance.auth}.xml`} ) : (
target="_blank" <div class="row">
title="RSS" <div class="col-12">
rel="noopener" <h5 class="mb-1">
> {i18n.t('inbox')}
<svg class="icon ml-2 text-muted small"> <small>
<use xlinkHref="#icon-rss">#</use> <a
</svg> href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
</a> target="_blank"
</small> title="RSS"
</h5> rel="noopener"
{this.state.replies.length + >
this.state.mentions.length + <svg class="icon ml-2 text-muted small">
this.state.messages.length > <use xlinkHref="#icon-rss">#</use>
0 && </svg>
this.state.unreadOrAll == UnreadOrAll.Unread && ( </a>
<ul class="list-inline mb-1 text-muted small font-weight-bold"> </small>
<li className="list-inline-item"> </h5>
<span {this.state.replies.length +
class="pointer" this.state.mentions.length +
onClick={linkEvent(this, this.markAllAsRead)} this.state.messages.length >
> 0 &&
{i18n.t('mark_all_as_read')} this.state.unreadOrAll == UnreadOrAll.Unread && (
</span> <ul class="list-inline mb-1 text-muted small font-weight-bold">
</li> <li className="list-inline-item">
</ul> <span
)} class="pointer"
{this.selects()} onClick={linkEvent(this, this.markAllAsRead)}
{this.state.messageType == MessageType.All && this.all()} >
{this.state.messageType == MessageType.Replies && this.replies()} {i18n.t('mark_all_as_read')}
{this.state.messageType == MessageType.Mentions && this.mentions()} </span>
{this.state.messageType == MessageType.Messages && this.messages()} </li>
{this.paginator()} </ul>
)}
{this.selects()}
{this.state.messageType == MessageType.All && this.all()}
{this.state.messageType == MessageType.Replies && this.replies()}
{this.state.messageType == MessageType.Mentions &&
this.mentions()}
{this.state.messageType == MessageType.Messages &&
this.messages()}
{this.paginator()}
</div>
</div> </div>
</div> )}
</div> </div>
); );
} }
@ -397,6 +401,39 @@ export class Inbox extends Component<any, InboxState> {
i.refetch(); i.refetch();
} }
static fetchInitialData(auth: string, _path: string): Promise<any>[] {
let promises: Promise<any>[] = [];
// It can be /u/me, or /username/1
let repliesForm: GetRepliesForm = {
sort: SortType.New,
unread_only: true,
page: 1,
limit: fetchLimit,
};
setAuth(repliesForm, auth);
promises.push(lemmyHttp.getReplies(repliesForm));
let userMentionsForm: GetUserMentionsForm = {
sort: SortType.New,
unread_only: true,
page: 1,
limit: fetchLimit,
};
setAuth(userMentionsForm, auth);
promises.push(lemmyHttp.getUserMentions(userMentionsForm));
let privateMessagesForm: GetPrivateMessagesForm = {
unread_only: true,
page: 1,
limit: fetchLimit,
};
setAuth(privateMessagesForm, auth);
promises.push(lemmyHttp.getPrivateMessages(privateMessagesForm));
return promises;
}
refetch() { refetch() {
let repliesForm: GetRepliesForm = { let repliesForm: GetRepliesForm = {
sort: this.state.sort, sort: this.state.sort,
@ -450,6 +487,7 @@ export class Inbox extends Component<any, InboxState> {
} else if (res.op == UserOperation.GetReplies) { } else if (res.op == UserOperation.GetReplies) {
let data = res.data as GetRepliesResponse; let data = res.data as GetRepliesResponse;
this.state.replies = data.replies; this.state.replies = data.replies;
this.state.loading = false;
this.sendUnreadCount(); this.sendUnreadCount();
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
@ -581,10 +619,6 @@ export class Inbox extends Component<any, InboxState> {
let data = res.data as CommentResponse; let data = res.data as CommentResponse;
createCommentLikeRes(data, this.state.replies); createCommentLikeRes(data, this.state.replies);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse;
this.state.site = data.site;
this.setState(this.state);
} }
} }

View file

@ -115,6 +115,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
} }
} }
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
} }

View file

@ -17,6 +17,7 @@ import {
AddAdminForm, AddAdminForm,
TransferSiteForm, TransferSiteForm,
TransferCommunityForm, TransferCommunityForm,
Community,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { BanType } from '../interfaces'; import { BanType } from '../interfaces';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
@ -61,6 +62,7 @@ interface PostListingState {
interface PostListingProps { interface PostListingProps {
post: Post; post: Post;
communities: Community[];
showCommunity?: boolean; showCommunity?: boolean;
showBody?: boolean; showBody?: boolean;
moderators?: CommunityUser[]; moderators?: CommunityUser[];
@ -127,6 +129,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onCancel={this.handleEditCancel} onCancel={this.handleEditCancel}
enableNsfw={this.props.enableNsfw} enableNsfw={this.props.enableNsfw}
enableDownvotes={this.props.enableDownvotes} enableDownvotes={this.props.enableDownvotes}
communities={this.props.communities}
/> />
</div> </div>
)} )}
@ -184,6 +187,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
} else if (post.thumbnail_url) { } else if (post.thumbnail_url) {
return pictrsImage(post.thumbnail_url, thumbnail); return pictrsImage(post.thumbnail_url, thumbnail);
} else {
return null;
} }
} }
@ -598,7 +603,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<Link <Link
class="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
to={`/create_post${this.crossPostParams}`} to={`/create_post${this.crossPostParams}`}
title={i18n.t('cross_post')} title={i18n.t('cross_post')}
> >

View file

@ -1,17 +1,13 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Helmet } from 'inferno-helmet'; import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { import {
UserOperation, UserOperation,
Community,
Post as PostI, Post as PostI,
GetPostResponse, GetPostResponse,
PostResponse, PostResponse,
Comment,
MarkCommentAsReadForm, MarkCommentAsReadForm,
CommentResponse, CommentResponse,
CommunityUser,
CommunityResponse, CommunityResponse,
CommentNode as CommentNodeI, CommentNode as CommentNodeI,
BanFromCommunityResponse, BanFromCommunityResponse,
@ -26,6 +22,8 @@ import {
GetSiteResponse, GetSiteResponse,
GetCommunityResponse, GetCommunityResponse,
WebSocketJsonResponse, WebSocketJsonResponse,
ListCategoriesResponse,
Category,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { CommentSortType, CommentViewType } from '../interfaces'; import { CommentSortType, CommentViewType } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
@ -39,6 +37,13 @@ import {
commentsToFlatNodes, commentsToFlatNodes,
setupTippy, setupTippy,
favIconUrl, favIconUrl,
setIsoData,
getIdFromProps,
getCommentIdFromProps,
wsSubscribe,
setAuth,
lemmyHttp,
isBrowser,
} from '../utils'; } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { Sidebar } from './sidebar'; import { Sidebar } from './sidebar';
@ -48,56 +53,32 @@ import autosize from 'autosize';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface PostState { interface PostState {
post: PostI; postRes: GetPostResponse;
comments: Comment[]; postId: number;
commentId?: number;
commentSort: CommentSortType; commentSort: CommentSortType;
commentViewType: CommentViewType; commentViewType: CommentViewType;
community: Community;
moderators: CommunityUser[];
online: number;
scrolled?: boolean; scrolled?: boolean;
scrolled_comment_id?: number;
loading: boolean; loading: boolean;
crossPosts: PostI[]; crossPosts: PostI[];
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
categories: Category[];
} }
export class Post extends Component<any, PostState> { export class Post extends Component<any, PostState> {
private subscription: Subscription; private subscription: Subscription;
private isoData = setIsoData(this.context);
private emptyState: PostState = { private emptyState: PostState = {
post: null, postRes: null,
comments: [], postId: getIdFromProps(this.props),
commentId: getCommentIdFromProps(this.props),
commentSort: CommentSortType.Hot, commentSort: CommentSortType.Hot,
commentViewType: CommentViewType.Tree, commentViewType: CommentViewType.Tree,
community: null,
moderators: [],
online: null,
scrolled: false, scrolled: false,
loading: true, loading: true,
crossPosts: [], crossPosts: [],
siteRes: { siteRes: this.isoData.site,
admins: [], categories: [],
banned: [],
site: {
id: undefined,
name: undefined,
creator_id: undefined,
published: undefined,
creator_name: undefined,
number_of_users: undefined,
number_of_posts: undefined,
number_of_comments: undefined,
number_of_communities: undefined,
enable_downvotes: undefined,
open_registration: undefined,
enable_nsfw: undefined,
icon: undefined,
banner: undefined,
},
online: null,
version: null,
federated_instances: undefined,
},
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -105,24 +86,46 @@ export class Post extends Component<any, PostState> {
this.state = this.emptyState; this.state = this.emptyState;
let postId = Number(this.props.match.params.id); this.parseMessage = this.parseMessage.bind(this);
if (this.props.match.params.comment_id) { this.subscription = wsSubscribe(this.parseMessage);
this.state.scrolled_comment_id = this.props.match.params.comment_id;
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
this.state.postRes = this.isoData.routeData[0];
this.state.categories = this.isoData.routeData[1].categories;
this.state.loading = false;
if (isBrowser() && this.state.commentId) {
this.scrollCommentIntoView();
}
} else {
this.fetchPost();
WebSocketService.Instance.listCategories();
} }
}
this.subscription = WebSocketService.Instance.subject fetchPost() {
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
msg => this.parseMessage(msg),
err => console.error(err),
() => console.log('complete')
);
let form: GetPostForm = { let form: GetPostForm = {
id: postId, id: this.state.postId,
}; };
WebSocketService.Instance.getPost(form); WebSocketService.Instance.getPost(form);
WebSocketService.Instance.getSite(); }
static fetchInitialData(auth: string, path: string): Promise<any>[] {
let pathSplit = path.split('/');
let promises: Promise<any>[] = [];
let id = Number(pathSplit[2]);
let postForm: GetPostForm = {
id,
};
setAuth(postForm, auth);
promises.push(lemmyHttp.getPost(postForm));
promises.push(lemmyHttp.listCategories());
return promises;
} }
componentWillUnmount() { componentWillUnmount() {
@ -135,17 +138,12 @@ export class Post extends Component<any, PostState> {
componentDidUpdate(_lastProps: any, lastState: PostState, _snapshot: any) { componentDidUpdate(_lastProps: any, lastState: PostState, _snapshot: any) {
if ( if (
this.state.scrolled_comment_id && this.state.commentId &&
!this.state.scrolled && !this.state.scrolled &&
lastState.comments.length > 0 lastState.postRes &&
lastState.postRes.comments.length > 0
) { ) {
var elmnt = document.getElementById( this.scrollCommentIntoView();
`comment-${this.state.scrolled_comment_id}`
);
elmnt.scrollIntoView();
elmnt.classList.add('mark');
this.state.scrolled = true;
this.markScrolledAsRead(this.state.scrolled_comment_id);
} }
// Necessary if you are on a post and you click another post (same route) // Necessary if you are on a post and you click another post (same route)
@ -161,12 +159,20 @@ export class Post extends Component<any, PostState> {
} }
} }
scrollCommentIntoView() {
var elmnt = document.getElementById(`comment-${this.state.commentId}`);
elmnt.scrollIntoView();
elmnt.classList.add('mark');
this.state.scrolled = true;
this.markScrolledAsRead(this.state.commentId);
}
markScrolledAsRead(commentId: number) { markScrolledAsRead(commentId: number) {
let found = this.state.comments.find(c => c.id == commentId); let found = this.state.postRes.comments.find(c => c.id == commentId);
let parent = this.state.comments.find(c => found.parent_id == c.id); let parent = this.state.postRes.comments.find(c => found.parent_id == c.id);
let parent_user_id = parent let parent_user_id = parent
? parent.creator_id ? parent.creator_id
: this.state.post.creator_id; : this.state.postRes.post.creator_id;
if ( if (
UserService.Instance.user && UserService.Instance.user &&
@ -185,8 +191,8 @@ export class Post extends Component<any, PostState> {
} }
get documentTitle(): string { get documentTitle(): string {
if (this.state.post) { if (this.state.postRes) {
return `${this.state.post.name} - ${this.state.siteRes.site.name}`; return `${this.state.postRes.post.name} - ${this.state.siteRes.site.name}`;
} else { } else {
return 'Lemmy'; return 'Lemmy';
} }
@ -219,20 +225,21 @@ export class Post extends Component<any, PostState> {
<div class="row"> <div class="row">
<div class="col-12 col-md-8 mb-3"> <div class="col-12 col-md-8 mb-3">
<PostListing <PostListing
post={this.state.post} communities={[this.state.postRes.community]}
post={this.state.postRes.post}
showBody showBody
showCommunity showCommunity
moderators={this.state.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={this.state.siteRes.site.enable_downvotes}
enableNsfw={this.state.siteRes.site.enable_nsfw} enableNsfw={this.state.siteRes.site.enable_nsfw}
/> />
<div className="mb-2" /> <div className="mb-2" />
<CommentForm <CommentForm
postId={this.state.post.id} postId={this.state.postId}
disabled={this.state.post.locked} disabled={this.state.postRes.post.locked}
/> />
{this.state.comments.length > 0 && this.sortRadios()} {this.state.postRes.comments.length > 0 && this.sortRadios()}
{this.state.commentViewType == CommentViewType.Tree && {this.state.commentViewType == CommentViewType.Tree &&
this.commentsTree()} this.commentsTree()}
{this.state.commentViewType == CommentViewType.Chat && {this.state.commentViewType == CommentViewType.Chat &&
@ -325,12 +332,12 @@ export class Post extends Component<any, PostState> {
return ( return (
<div> <div>
<CommentNodes <CommentNodes
nodes={commentsToFlatNodes(this.state.comments)} nodes={commentsToFlatNodes(this.state.postRes.comments)}
noIndent noIndent
locked={this.state.post.locked} locked={this.state.postRes.post.locked}
moderators={this.state.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
postCreatorId={this.state.post.creator_id} postCreatorId={this.state.postRes.post.creator_id}
showContext showContext
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={this.state.siteRes.site.enable_downvotes}
sort={this.state.commentSort} sort={this.state.commentSort}
@ -343,12 +350,13 @@ export class Post extends Component<any, PostState> {
return ( return (
<div class="mb-3"> <div class="mb-3">
<Sidebar <Sidebar
community={this.state.community} community={this.state.postRes.community}
moderators={this.state.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
online={this.state.online} online={this.state.postRes.online}
enableNsfw={this.state.siteRes.site.enable_nsfw} enableNsfw={this.state.siteRes.site.enable_nsfw}
showIcon showIcon
categories={this.state.categories}
/> />
</div> </div>
); );
@ -368,7 +376,7 @@ export class Post extends Component<any, PostState> {
buildCommentsTree(): CommentNodeI[] { buildCommentsTree(): CommentNodeI[] {
let map = new Map<number, CommentNodeI>(); let map = new Map<number, CommentNodeI>();
for (let comment of this.state.comments) { for (let comment of this.state.postRes.comments) {
let node: CommentNodeI = { let node: CommentNodeI = {
comment: comment, comment: comment,
children: [], children: [],
@ -376,7 +384,7 @@ export class Post extends Component<any, PostState> {
map.set(comment.id, { ...node }); map.set(comment.id, { ...node });
} }
let tree: CommentNodeI[] = []; let tree: CommentNodeI[] = [];
for (let comment of this.state.comments) { for (let comment of this.state.postRes.comments) {
let child = map.get(comment.id); let child = map.get(comment.id);
if (comment.parent_id) { if (comment.parent_id) {
let parent_ = map.get(comment.parent_id); let parent_ = map.get(comment.parent_id);
@ -404,10 +412,10 @@ export class Post extends Component<any, PostState> {
<div> <div>
<CommentNodes <CommentNodes
nodes={nodes} nodes={nodes}
locked={this.state.post.locked} locked={this.state.postRes.post.locked}
moderators={this.state.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
postCreatorId={this.state.post.creator_id} postCreatorId={this.state.postRes.post.creator_id}
sort={this.state.commentSort} sort={this.state.commentSort}
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={this.state.siteRes.site.enable_downvotes}
/> />
@ -427,17 +435,13 @@ export class Post extends Component<any, PostState> {
}); });
} else if (res.op == UserOperation.GetPost) { } else if (res.op == UserOperation.GetPost) {
let data = res.data as GetPostResponse; let data = res.data as GetPostResponse;
this.state.post = data.post; this.state.postRes = data;
this.state.comments = data.comments;
this.state.community = data.community;
this.state.moderators = data.moderators;
this.state.online = data.online;
this.state.loading = false; this.state.loading = false;
// Get cross-posts // Get cross-posts
if (this.state.post.url) { if (this.state.postRes.post.url) {
let form: SearchForm = { let form: SearchForm = {
q: this.state.post.url, q: this.state.postRes.post.url,
type_: SearchType.Url, type_: SearchType.Url,
sort: SortType.TopAll, sort: SortType.TopAll,
page: 1, page: 1,
@ -453,7 +457,7 @@ export class Post extends Component<any, PostState> {
// Necessary since it might be a user reply // Necessary since it might be a user reply
if (data.recipient_ids.length == 0) { if (data.recipient_ids.length == 0) {
this.state.comments.unshift(data.comment); this.state.postRes.comments.unshift(data.comment);
this.setState(this.state); this.setState(this.state);
} }
} else if ( } else if (
@ -462,20 +466,20 @@ export class Post extends Component<any, PostState> {
res.op == UserOperation.RemoveComment res.op == UserOperation.RemoveComment
) { ) {
let data = res.data as CommentResponse; let data = res.data as CommentResponse;
editCommentRes(data, this.state.comments); editCommentRes(data, this.state.postRes.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.SaveComment) { } else if (res.op == UserOperation.SaveComment) {
let data = res.data as CommentResponse; let data = res.data as CommentResponse;
saveCommentRes(data, this.state.comments); saveCommentRes(data, this.state.postRes.comments);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.CreateCommentLike) { } else if (res.op == UserOperation.CreateCommentLike) {
let data = res.data as CommentResponse; let data = res.data as CommentResponse;
createCommentLikeRes(data, this.state.comments); createCommentLikeRes(data, this.state.postRes.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (res.op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse; let data = res.data as PostResponse;
createPostLikeRes(data, this.state.post); createPostLikeRes(data, this.state.postRes.post);
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
res.op == UserOperation.EditPost || res.op == UserOperation.EditPost ||
@ -485,12 +489,12 @@ export class Post extends Component<any, PostState> {
res.op == UserOperation.StickyPost res.op == UserOperation.StickyPost
) { ) {
let data = res.data as PostResponse; let data = res.data as PostResponse;
this.state.post = data.post; this.state.postRes.post = data.post;
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.SavePost) { } else if (res.op == UserOperation.SavePost) {
let data = res.data as PostResponse; let data = res.data as PostResponse;
this.state.post = data.post; this.state.postRes.post = data.post;
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if ( } else if (
@ -499,36 +503,36 @@ export class Post extends Component<any, PostState> {
res.op == UserOperation.RemoveCommunity res.op == UserOperation.RemoveCommunity
) { ) {
let data = res.data as CommunityResponse; let data = res.data as CommunityResponse;
this.state.community = data.community; this.state.postRes.community = data.community;
this.state.post.community_id = data.community.id; this.state.postRes.post.community_id = data.community.id;
this.state.post.community_name = data.community.name; this.state.postRes.post.community_name = data.community.name;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.FollowCommunity) { } else if (res.op == UserOperation.FollowCommunity) {
let data = res.data as CommunityResponse; let data = res.data as CommunityResponse;
this.state.community.subscribed = data.community.subscribed; this.state.postRes.community.subscribed = data.community.subscribed;
this.state.community.number_of_subscribers = this.state.postRes.community.number_of_subscribers =
data.community.number_of_subscribers; data.community.number_of_subscribers;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.BanFromCommunity) { } else if (res.op == UserOperation.BanFromCommunity) {
let data = res.data as BanFromCommunityResponse; let data = res.data as BanFromCommunityResponse;
this.state.comments this.state.postRes.comments
.filter(c => c.creator_id == data.user.id) .filter(c => c.creator_id == data.user.id)
.forEach(c => (c.banned_from_community = data.banned)); .forEach(c => (c.banned_from_community = data.banned));
if (this.state.post.creator_id == data.user.id) { if (this.state.postRes.post.creator_id == data.user.id) {
this.state.post.banned_from_community = data.banned; this.state.postRes.post.banned_from_community = data.banned;
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.AddModToCommunity) { } else if (res.op == UserOperation.AddModToCommunity) {
let data = res.data as AddModToCommunityResponse; let data = res.data as AddModToCommunityResponse;
this.state.moderators = data.moderators; this.state.postRes.moderators = data.moderators;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.BanUser) { } else if (res.op == UserOperation.BanUser) {
let data = res.data as BanUserResponse; let data = res.data as BanUserResponse;
this.state.comments this.state.postRes.comments
.filter(c => c.creator_id == data.user.id) .filter(c => c.creator_id == data.user.id)
.forEach(c => (c.banned = data.banned)); .forEach(c => (c.banned = data.banned));
if (this.state.post.creator_id == data.user.id) { if (this.state.postRes.post.creator_id == data.user.id) {
this.state.post.banned = data.banned; this.state.postRes.post.banned = data.banned;
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.AddAdmin) { } else if (res.op == UserOperation.AddAdmin) {
@ -541,20 +545,21 @@ export class Post extends Component<any, PostState> {
p => p.id != Number(this.props.match.params.id) p => p.id != Number(this.props.match.params.id)
); );
if (this.state.crossPosts.length) { if (this.state.crossPosts.length) {
this.state.post.duplicates = this.state.crossPosts; this.state.postRes.post.duplicates = this.state.crossPosts;
} }
this.setState(this.state); this.setState(this.state);
} else if ( } else if (res.op == UserOperation.TransferSite) {
res.op == UserOperation.TransferSite ||
res.op == UserOperation.GetSite
) {
let data = res.data as GetSiteResponse; let data = res.data as GetSiteResponse;
this.state.siteRes = data; this.state.siteRes = data;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.TransferCommunity) { } else if (res.op == UserOperation.TransferCommunity) {
let data = res.data as GetCommunityResponse; let data = res.data as GetCommunityResponse;
this.state.community = data.community; this.state.postRes.community = data.community;
this.state.moderators = data.moderators; this.state.postRes.moderators = data.moderators;
this.setState(this.state);
} else if (res.op == UserOperation.ListCategories) {
let data = res.data as ListCategoriesResponse;
this.state.categories = data.categories;
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -1,38 +1,13 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { WebSocketService, UserService } from '../services';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { import { Post, Comment, SortType, UserDetailsResponse } from 'lemmy-js-client';
UserOperation,
Post,
Comment,
CommunityUser,
SortType,
UserDetailsResponse,
UserView,
WebSocketJsonResponse,
CommentResponse,
BanUserResponse,
PostResponse,
} from 'lemmy-js-client';
import { UserDetailsView } from '../interfaces'; import { UserDetailsView } from '../interfaces';
import { import { commentsToFlatNodes, setupTippy } from '../utils';
wsJsonToRes,
toast,
commentsToFlatNodes,
setupTippy,
editCommentRes,
saveCommentRes,
createCommentLikeRes,
createPostLikeFindRes,
} from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
interface UserDetailsProps { interface UserDetailsProps {
username?: string; userRes: UserDetailsResponse;
user_id?: number;
page: number; page: number;
limit: number; limit: number;
sort: SortType; sort: SortType;
@ -40,67 +15,29 @@ interface UserDetailsProps {
enableNsfw: boolean; enableNsfw: boolean;
view: UserDetailsView; view: UserDetailsView;
onPageChange(page: number): number | any; onPageChange(page: number): number | any;
admins: UserView[];
} }
interface UserDetailsState { interface UserDetailsState {}
follows: CommunityUser[];
moderates: CommunityUser[];
comments: Comment[];
posts: Post[];
saved?: Post[];
}
export class UserDetails extends Component<UserDetailsProps, UserDetailsState> { export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
private subscription: Subscription;
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
this.state = {
follows: [],
moderates: [],
comments: [],
posts: [],
saved: [],
};
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
msg => this.parseMessage(msg),
err => console.error(err),
() => console.log('complete')
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
} }
// TODO needed here?
componentDidMount() { componentDidMount() {
this.fetchUserData();
setupTippy(); setupTippy();
} }
componentDidUpdate(lastProps: UserDetailsProps) { // TODO wut?
for (const key of Object.keys(lastProps)) { // componentDidUpdate(lastProps: UserDetailsProps) {
if (lastProps[key] !== this.props[key]) { // for (const key of Object.keys(lastProps)) {
this.fetchUserData(); // if (lastProps[key] !== this.props[key]) {
break; // this.fetchUserData();
} // break;
} // }
} // }
// }
fetchUserData() {
WebSocketService.Instance.getUserDetails({
user_id: this.props.user_id,
username: this.props.username,
sort: this.props.sort,
saved_only: this.props.view === UserDetailsView.Saved,
page: this.props.page,
limit: this.props.limit,
});
}
render() { render() {
return ( return (
@ -114,20 +51,20 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
viewSelector(view: UserDetailsView) { viewSelector(view: UserDetailsView) {
if (view === UserDetailsView.Overview || view === UserDetailsView.Saved) { if (view === UserDetailsView.Overview || view === UserDetailsView.Saved) {
return this.overview(); return this.overview();
} } else if (view === UserDetailsView.Comments) {
if (view === UserDetailsView.Comments) {
return this.comments(); return this.comments();
} } else if (view === UserDetailsView.Posts) {
if (view === UserDetailsView.Posts) {
return this.posts(); return this.posts();
} else {
return null;
} }
} }
overview() { overview() {
const comments = this.state.comments.map((c: Comment) => { const comments = this.props.userRes.comments.map((c: Comment) => {
return { type: 'comments', data: c }; return { type: 'comments', data: c };
}); });
const posts = this.state.posts.map((p: Post) => { const posts = this.props.userRes.posts.map((p: Post) => {
return { type: 'posts', data: p }; return { type: 'posts', data: p };
}); });
@ -150,9 +87,10 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
<div> <div>
{i.type === 'posts' ? ( {i.type === 'posts' ? (
<PostListing <PostListing
communities={[]}
key={(i.data as Post).id} key={(i.data as Post).id}
post={i.data as Post} post={i.data as Post}
admins={this.props.admins} admins={this.props.userRes.admins}
showCommunity showCommunity
enableDownvotes={this.props.enableDownvotes} enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw} enableNsfw={this.props.enableNsfw}
@ -161,7 +99,7 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
<CommentNodes <CommentNodes
key={(i.data as Comment).id} key={(i.data as Comment).id}
nodes={[{ comment: i.data as Comment }]} nodes={[{ comment: i.data as Comment }]}
admins={this.props.admins} admins={this.props.userRes.admins}
noBorder noBorder
noIndent noIndent
showCommunity showCommunity
@ -181,8 +119,8 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
return ( return (
<div> <div>
<CommentNodes <CommentNodes
nodes={commentsToFlatNodes(this.state.comments)} nodes={commentsToFlatNodes(this.props.userRes.comments)}
admins={this.props.admins} admins={this.props.userRes.admins}
noIndent noIndent
showCommunity showCommunity
showContext showContext
@ -195,11 +133,12 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
posts() { posts() {
return ( return (
<div> <div>
{this.state.posts.map(post => ( {this.props.userRes.posts.map(post => (
<> <>
<PostListing <PostListing
communities={[]}
post={post} post={post}
admins={this.props.admins} admins={this.props.userRes.admins}
showCommunity showCommunity
enableDownvotes={this.props.enableDownvotes} enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw} enableNsfw={this.props.enableNsfw}
@ -222,7 +161,8 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
{i18n.t('prev')} {i18n.t('prev')}
</button> </button>
)} )}
{this.state.comments.length + this.state.posts.length > 0 && ( {this.props.userRes.comments.length + this.props.userRes.posts.length >
0 && (
<button <button
class="btn btn-secondary" class="btn btn-secondary"
onClick={linkEvent(this, this.nextPage)} onClick={linkEvent(this, this.nextPage)}
@ -241,75 +181,4 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
prevPage(i: UserDetails) { prevPage(i: UserDetails) {
i.props.onPageChange(i.props.page - 1); i.props.onPageChange(i.props.page - 1);
} }
parseMessage(msg: WebSocketJsonResponse) {
console.log(msg);
const res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
if (msg.error == 'couldnt_find_that_username_or_email') {
this.context.router.history.push('/');
}
return;
} else if (msg.reconnect) {
this.fetchUserData();
} else if (res.op == UserOperation.GetUserDetails) {
const data = res.data as UserDetailsResponse;
this.setState({
comments: data.comments,
follows: data.follows,
moderates: data.moderates,
posts: data.posts,
});
} else if (res.op == UserOperation.CreateCommentLike) {
const data = res.data as CommentResponse;
createCommentLikeRes(data, this.state.comments);
this.setState({
comments: this.state.comments,
});
} else if (
res.op == UserOperation.EditComment ||
res.op == UserOperation.DeleteComment ||
res.op == UserOperation.RemoveComment
) {
const data = res.data as CommentResponse;
editCommentRes(data, this.state.comments);
this.setState({
comments: this.state.comments,
});
} else if (res.op == UserOperation.CreateComment) {
const data = res.data as CommentResponse;
if (
UserService.Instance.user &&
data.comment.creator_id == UserService.Instance.user.id
) {
toast(i18n.t('reply_sent'));
}
} else if (res.op == UserOperation.SaveComment) {
const data = res.data as CommentResponse;
saveCommentRes(data, this.state.comments);
this.setState({
comments: this.state.comments,
});
} else if (res.op == UserOperation.CreatePostLike) {
const data = res.data as PostResponse;
createPostLikeFindRes(data, this.state.posts);
this.setState({
posts: this.state.posts,
});
} else if (res.op == UserOperation.BanUser) {
const data = res.data as BanUserResponse;
this.state.comments
.filter(c => c.creator_id == data.user.id)
.forEach(c => (c.banned = data.banned));
this.state.posts
.filter(c => c.creator_id == data.user.id)
.forEach(c => (c.banned = data.banned));
this.setState({
posts: this.state.posts,
comments: this.state.comments,
});
}
}
} }

View file

@ -2,13 +2,10 @@ import { Component, linkEvent } from 'inferno';
import { Helmet } from 'inferno-helmet'; import { Helmet } from 'inferno-helmet';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import { import {
UserOperation, UserOperation,
CommunityUser,
SortType, SortType,
ListingType, ListingType,
UserView,
UserSettingsForm, UserSettingsForm,
LoginResponse, LoginResponse,
DeleteAccountForm, DeleteAccountForm,
@ -16,6 +13,10 @@ import {
GetSiteResponse, GetSiteResponse,
UserDetailsResponse, UserDetailsResponse,
AddAdminResponse, AddAdminResponse,
GetUserDetailsForm,
CommentResponse,
PostResponse,
BanUserResponse,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { UserDetailsView } from '../interfaces'; import { UserDetailsView } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
@ -33,6 +34,16 @@ import {
mdToHtml, mdToHtml,
elementUrl, elementUrl,
favIconUrl, favIconUrl,
setIsoData,
getIdFromProps,
getUsernameFromProps,
wsSubscribe,
createCommentLikeRes,
editCommentRes,
saveCommentRes,
createPostLikeFindRes,
setAuth,
lemmyHttp,
} from '../utils'; } from '../utils';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { SortSelect } from './sort-select'; import { SortSelect } from './sort-select';
@ -46,11 +57,9 @@ import { ImageUploadForm } from './image-upload-form';
import { BannerIconHeader } from './banner-icon-header'; import { BannerIconHeader } from './banner-icon-header';
interface UserState { interface UserState {
user: UserView; userRes: UserDetailsResponse;
user_id: number; userId: number;
username: string; userName: string;
follows: CommunityUser[];
moderates: CommunityUser[];
view: UserDetailsView; view: UserDetailsView;
sort: SortType; sort: SortType;
page: number; page: number;
@ -78,25 +87,12 @@ interface UrlParams {
} }
export class User extends Component<any, UserState> { export class User extends Component<any, UserState> {
private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: UserState = { private emptyState: UserState = {
user: { userRes: undefined,
id: null, userId: getIdFromProps(this.props),
name: null, userName: getUsernameFromProps(this.props),
published: null,
number_of_posts: null,
post_score: null,
number_of_comments: null,
comment_score: null,
banned: null,
avatar: null,
actor_id: null,
local: null,
},
user_id: null,
username: null,
follows: [],
moderates: [],
loading: true, loading: true,
view: User.getViewFromProps(this.props.match.view), view: User.getViewFromProps(this.props.match.view),
sort: User.getSortTypeFromProps(this.props.match.sort), sort: User.getSortTypeFromProps(this.props.match.sort),
@ -119,31 +115,7 @@ export class User extends Component<any, UserState> {
deleteAccountForm: { deleteAccountForm: {
password: null, password: null,
}, },
siteRes: { siteRes: this.isoData.site,
admins: [],
banned: [],
online: undefined,
site: {
id: undefined,
name: undefined,
creator_id: undefined,
published: undefined,
creator_name: undefined,
number_of_users: undefined,
number_of_posts: undefined,
number_of_comments: undefined,
number_of_communities: undefined,
enable_downvotes: undefined,
open_registration: undefined,
enable_nsfw: undefined,
icon: undefined,
banner: undefined,
creator_preferred_username: undefined,
},
version: undefined,
my_user: undefined,
federated_instances: undefined,
},
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -168,25 +140,37 @@ export class User extends Component<any, UserState> {
this.handleBannerUpload = this.handleBannerUpload.bind(this); this.handleBannerUpload = this.handleBannerUpload.bind(this);
this.handleBannerRemove = this.handleBannerRemove.bind(this); this.handleBannerRemove = this.handleBannerRemove.bind(this);
this.state.user_id = Number(this.props.match.params.id) || null; this.parseMessage = this.parseMessage.bind(this);
this.state.username = this.props.match.params.username; this.subscription = wsSubscribe(this.parseMessage);
this.subscription = WebSocketService.Instance.subject // Only fetch the data if coming from another route
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) if (this.isoData.path == this.context.router.route.match.url) {
.subscribe( this.state.userRes = this.isoData.routeData[0];
msg => this.parseMessage(msg), this.setUserInfo();
err => console.error(err), this.state.loading = false;
() => console.log('complete') } else {
); this.fetchUserData();
}
WebSocketService.Instance.getSite();
setupTippy(); setupTippy();
} }
fetchUserData() {
let form: GetUserDetailsForm = {
user_id: this.state.userId,
username: this.state.userName,
sort: this.state.sort,
saved_only: this.state.view === UserDetailsView.Saved,
page: this.state.page,
limit: fetchLimit,
};
WebSocketService.Instance.getUserDetails(form);
}
get isCurrentUser() { get isCurrentUser() {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
UserService.Instance.user.id == this.state.user.id UserService.Instance.user.id == this.state.userRes.user.id
); );
} }
@ -202,6 +186,44 @@ export class User extends Component<any, UserState> {
return page ? Number(page) : 1; return page ? Number(page) : 1;
} }
static fetchInitialData(auth: string, path: string): Promise<any>[] {
let pathSplit = path.split('/');
let promises: Promise<any>[] = [];
// It can be /u/me, or /username/1
let idOrName = pathSplit[2];
let user_id: number;
let username: string;
if (isNaN(Number(idOrName))) {
username = idOrName;
} else {
user_id = Number(idOrName);
}
let view = this.getViewFromProps(pathSplit[4]);
let sort = this.getSortTypeFromProps(pathSplit[6]);
let page = this.getPageFromProps(Number(pathSplit[8]));
let form: GetUserDetailsForm = {
sort,
saved_only: view === UserDetailsView.Saved,
page,
limit: fetchLimit,
};
this.setIdOrName(form, user_id, username);
setAuth(form, auth);
promises.push(lemmyHttp.getUserDetails(form));
return promises;
}
static setIdOrName(obj: any, id: number, name_: string) {
if (id) {
obj.user_id = id;
} else {
obj.username = name_;
}
}
componentWillUnmount() { componentWillUnmount() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
@ -229,7 +251,7 @@ export class User extends Component<any, UserState> {
get documentTitle(): string { get documentTitle(): string {
if (this.state.siteRes.site.name) { if (this.state.siteRes.site.name) {
return `@${this.state.username} - ${this.state.siteRes.site.name}`; return `@${this.state.userName} - ${this.state.siteRes.site.name}`;
} else { } else {
return 'Lemmy'; return 'Lemmy';
} }
@ -252,43 +274,41 @@ export class User extends Component<any, UserState> {
href={this.favIcon} href={this.favIcon}
/> />
</Helmet> </Helmet>
<div class="row"> {this.state.loading ? (
<div class="col-12 col-md-8"> <h5>
{this.state.loading ? ( <svg class="icon icon-spinner spin">
<h5> <use xlinkHref="#icon-spinner"></use>
<svg class="icon icon-spinner spin"> </svg>
<use xlinkHref="#icon-spinner"></use> </h5>
</svg> ) : (
</h5> <div class="row">
) : ( <div class="col-12 col-md-8">
<> <>
{this.userInfo()} {this.userInfo()}
<hr /> <hr />
</> </>
)} {!this.state.loading && this.selects()}
{!this.state.loading && this.selects()} <UserDetails
<UserDetails userRes={this.state.userRes}
user_id={this.state.user_id} sort={this.state.sort}
username={this.state.username} page={this.state.page}
sort={this.state.sort} limit={fetchLimit}
page={this.state.page} enableDownvotes={this.state.siteRes.site.enable_downvotes}
limit={fetchLimit} enableNsfw={this.state.siteRes.site.enable_nsfw}
enableDownvotes={this.state.siteRes.site.enable_downvotes} view={this.state.view}
enableNsfw={this.state.siteRes.site.enable_nsfw} onPageChange={this.handlePageChange}
admins={this.state.siteRes.admins} />
view={this.state.view}
onPageChange={this.handlePageChange}
/>
</div>
{!this.state.loading && (
<div class="col-12 col-md-4">
{this.isCurrentUser && this.userSettings()}
{this.moderates()}
{this.follows()}
</div> </div>
)}
</div> {!this.state.loading && (
<div class="col-12 col-md-4">
{this.isCurrentUser && this.userSettings()}
{this.moderates()}
{this.follows()}
</div>
)}
</div>
)}
</div> </div>
); );
} }
@ -362,7 +382,7 @@ export class User extends Component<any, UserState> {
hideHot hideHot
/> />
<a <a
href={`/feeds/u/${this.state.username}.xml?sort=${this.state.sort}`} href={`/feeds/u/${this.state.userName}.xml?sort=${this.state.sort}`}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
title="RSS" title="RSS"
@ -376,14 +396,11 @@ export class User extends Component<any, UserState> {
} }
userInfo() { userInfo() {
let user = this.state.user; let user = this.state.userRes.user;
return ( return (
<div> <div>
<BannerIconHeader <BannerIconHeader banner={user.banner} icon={user.avatar} />
banner={this.state.user.banner}
icon={this.state.user.avatar}
/>
<div class="mb-3"> <div class="mb-3">
<div class=""> <div class="">
<div class="mb-0 d-flex flex-wrap"> <div class="mb-0 d-flex flex-wrap">
@ -420,17 +437,17 @@ export class User extends Component<any, UserState> {
<> <>
<a <a
className={`d-flex align-self-start btn btn-secondary ml-2 ${ className={`d-flex align-self-start btn btn-secondary ml-2 ${
!this.state.user.matrix_user_id && 'invisible' !user.matrix_user_id && 'invisible'
}`} }`}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
href={`https://matrix.to/#/${this.state.user.matrix_user_id}`} href={`https://matrix.to/#/${user.matrix_user_id}`}
> >
{i18n.t('send_secure_message')} {i18n.t('send_secure_message')}
</a> </a>
<Link <Link
class="d-flex align-self-start btn btn-secondary ml-2" class="d-flex align-self-start btn btn-secondary ml-2"
to={`/create_private_message/recipient/${this.state.user.id}`} to={`/create_private_message/recipient/${user.id}`}
> >
{i18n.t('send_message')} {i18n.t('send_message')}
</Link> </Link>
@ -818,12 +835,12 @@ export class User extends Component<any, UserState> {
moderates() { moderates() {
return ( return (
<div> <div>
{this.state.moderates.length > 0 && ( {this.state.userRes.moderates.length > 0 && (
<div class="card bg-transparent border-secondary mb-3"> <div class="card bg-transparent border-secondary mb-3">
<div class="card-body"> <div class="card-body">
<h5>{i18n.t('moderates')}</h5> <h5>{i18n.t('moderates')}</h5>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
{this.state.moderates.map(community => ( {this.state.userRes.moderates.map(community => (
<li> <li>
<Link to={`/c/${community.community_name}`}> <Link to={`/c/${community.community_name}`}>
{community.community_name} {community.community_name}
@ -841,12 +858,12 @@ export class User extends Component<any, UserState> {
follows() { follows() {
return ( return (
<div> <div>
{this.state.follows.length > 0 && ( {this.state.userRes.follows.length > 0 && (
<div class="card bg-transparent border-secondary mb-3"> <div class="card bg-transparent border-secondary mb-3">
<div class="card-body"> <div class="card-body">
<h5>{i18n.t('subscribed')}</h5> <h5>{i18n.t('subscribed')}</h5>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
{this.state.follows.map(community => ( {this.state.userRes.follows.map(community => (
<li> <li>
<Link to={`/c/${community.community_name}`}> <Link to={`/c/${community.community_name}`}>
{community.community_name} {community.community_name}
@ -866,7 +883,7 @@ export class User extends Component<any, UserState> {
const viewStr = paramUpdates.view || UserDetailsView[this.state.view]; const viewStr = paramUpdates.view || UserDetailsView[this.state.view];
const sortStr = paramUpdates.sort || this.state.sort; const sortStr = paramUpdates.sort || this.state.sort;
this.props.history.push( this.props.history.push(
`/u/${this.state.username}/view/${viewStr}/sort/${sortStr}/page/${page}` `/u/${this.state.userName}/view/${viewStr}/sort/${sortStr}/page/${page}`
); );
} }
@ -966,7 +983,7 @@ export class User extends Component<any, UserState> {
i.state.userSettingsForm.matrix_user_id = event.target.value; i.state.userSettingsForm.matrix_user_id = event.target.value;
if ( if (
i.state.userSettingsForm.matrix_user_id == '' && i.state.userSettingsForm.matrix_user_id == '' &&
!i.state.user.matrix_user_id !i.state.userRes.user.matrix_user_id
) { ) {
i.state.userSettingsForm.matrix_user_id = undefined; i.state.userSettingsForm.matrix_user_id = undefined;
} }
@ -1029,6 +1046,33 @@ export class User extends Component<any, UserState> {
WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm); WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm);
} }
setUserInfo() {
if (this.isCurrentUser) {
this.state.userSettingsForm.show_nsfw =
UserService.Instance.user.show_nsfw;
this.state.userSettingsForm.theme = UserService.Instance.user.theme
? UserService.Instance.user.theme
: 'darkly';
this.state.userSettingsForm.default_sort_type =
UserService.Instance.user.default_sort_type;
this.state.userSettingsForm.default_listing_type =
UserService.Instance.user.default_listing_type;
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
this.state.userSettingsForm.banner = UserService.Instance.user.banner;
this.state.userSettingsForm.preferred_username =
UserService.Instance.user.preferred_username;
this.state.userSettingsForm.show_avatars =
UserService.Instance.user.show_avatars;
this.state.userSettingsForm.email = UserService.Instance.user.email;
this.state.userSettingsForm.bio = UserService.Instance.user.bio;
this.state.userSettingsForm.send_notifications_to_email =
UserService.Instance.user.send_notifications_to_email;
this.state.userSettingsForm.matrix_user_id =
UserService.Instance.user.matrix_user_id;
}
}
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: WebSocketJsonResponse) {
console.log(msg); console.log(msg);
const res = wsJsonToRes(msg); const res = wsJsonToRes(msg);
@ -1042,50 +1086,24 @@ export class User extends Component<any, UserState> {
userSettingsLoading: false, userSettingsLoading: false,
}); });
return; return;
} else if (msg.reconnect) {
this.fetchUserData();
} else if (res.op == UserOperation.GetUserDetails) { } else if (res.op == UserOperation.GetUserDetails) {
// Since the UserDetails contains posts/comments as well as some general user info we listen here as well // Since the UserDetails contains posts/comments as well as some general user info we listen here as well
// and set the parent state if it is not set or differs // and set the parent state if it is not set or differs
// TODO this might need to get abstracted
const data = res.data as UserDetailsResponse; const data = res.data as UserDetailsResponse;
this.state.userRes = data;
if (this.state.user.id !== data.user.id) { this.setUserInfo();
this.state.user = data.user; this.state.loading = false;
this.state.follows = data.follows; this.setState(this.state);
this.state.moderates = data.moderates;
if (this.isCurrentUser) {
this.state.userSettingsForm.show_nsfw =
UserService.Instance.user.show_nsfw;
this.state.userSettingsForm.theme = UserService.Instance.user.theme
? UserService.Instance.user.theme
: 'darkly';
this.state.userSettingsForm.default_sort_type =
UserService.Instance.user.default_sort_type;
this.state.userSettingsForm.default_listing_type =
UserService.Instance.user.default_listing_type;
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
this.state.userSettingsForm.banner = UserService.Instance.user.banner;
this.state.userSettingsForm.preferred_username =
UserService.Instance.user.preferred_username;
this.state.userSettingsForm.show_avatars =
UserService.Instance.user.show_avatars;
this.state.userSettingsForm.email = UserService.Instance.user.email;
this.state.userSettingsForm.bio = UserService.Instance.user.bio;
this.state.userSettingsForm.send_notifications_to_email =
UserService.Instance.user.send_notifications_to_email;
this.state.userSettingsForm.matrix_user_id =
UserService.Instance.user.matrix_user_id;
}
this.state.loading = false;
this.setState(this.state);
}
} else if (res.op == UserOperation.SaveUserSettings) { } else if (res.op == UserOperation.SaveUserSettings) {
const data = res.data as LoginResponse; const data = res.data as LoginResponse;
UserService.Instance.login(data); UserService.Instance.login(data);
this.state.user.bio = this.state.userSettingsForm.bio; this.state.userRes.user.bio = this.state.userSettingsForm.bio;
this.state.user.preferred_username = this.state.userSettingsForm.preferred_username; this.state.userRes.user.preferred_username = this.state.userSettingsForm.preferred_username;
this.state.user.banner = this.state.userSettingsForm.banner; this.state.userRes.user.banner = this.state.userSettingsForm.banner;
this.state.user.avatar = this.state.userSettingsForm.avatar; this.state.userRes.user.avatar = this.state.userSettingsForm.avatar;
this.state.userSettingsLoading = false; this.state.userSettingsLoading = false;
this.setState(this.state); this.setState(this.state);
@ -1096,14 +1114,47 @@ export class User extends Component<any, UserState> {
deleteAccountShowConfirm: false, deleteAccountShowConfirm: false,
}); });
this.context.router.history.push('/'); this.context.router.history.push('/');
} else if (res.op == UserOperation.GetSite) {
const data = res.data as GetSiteResponse;
this.state.siteRes = data;
this.setState(this.state);
} else if (res.op == UserOperation.AddAdmin) { } else if (res.op == UserOperation.AddAdmin) {
const data = res.data as AddAdminResponse; const data = res.data as AddAdminResponse;
this.state.siteRes.admins = data.admins; this.state.siteRes.admins = data.admins;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateCommentLike) {
const data = res.data as CommentResponse;
createCommentLikeRes(data, this.state.userRes.comments);
this.setState(this.state);
} else if (
res.op == UserOperation.EditComment ||
res.op == UserOperation.DeleteComment ||
res.op == UserOperation.RemoveComment
) {
const data = res.data as CommentResponse;
editCommentRes(data, this.state.userRes.comments);
this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) {
const data = res.data as CommentResponse;
if (
UserService.Instance.user &&
data.comment.creator_id == UserService.Instance.user.id
) {
toast(i18n.t('reply_sent'));
}
} else if (res.op == UserOperation.SaveComment) {
const data = res.data as CommentResponse;
saveCommentRes(data, this.state.userRes.comments);
this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) {
const data = res.data as PostResponse;
createPostLikeFindRes(data, this.state.userRes.posts);
this.setState(this.state);
} else if (res.op == UserOperation.BanUser) {
const data = res.data as BanUserResponse;
this.state.userRes.comments
.filter(c => c.creator_id == data.user.id)
.forEach(c => (c.banned = data.banned));
this.state.userRes.posts
.filter(c => c.creator_id == data.user.id)
.forEach(c => (c.banned = data.banned));
this.setState(this.state);
} }
} }
} }

View file

@ -64,8 +64,13 @@ export const routes: IRoutePropsWithFetch[] = [
{ {
path: `/post/:id/comment/:comment_id`, path: `/post/:id/comment/:comment_id`,
component: Post, component: Post,
fetchInitialData: (auth, path) => Post.fetchInitialData(auth, path),
},
{
path: `/post/:id`,
component: Post,
fetchInitialData: (auth, path) => Post.fetchInitialData(auth, path),
}, },
{ path: `/post/:id`, component: Post },
{ {
path: `/c/:name/data_type/:data_type/sort/:sort/page/:page`, path: `/c/:name/data_type/:data_type/sort/:sort/page/:page`,
component: Community, component: Community,
@ -84,10 +89,23 @@ export const routes: IRoutePropsWithFetch[] = [
{ {
path: `/u/:username/view/:view/sort/:sort/page/:page`, path: `/u/:username/view/:view/sort/:sort/page/:page`,
component: User, component: User,
fetchInitialData: (auth, path) => User.fetchInitialData(auth, path),
},
{
path: `/user/:id`,
component: User,
fetchInitialData: (auth, path) => User.fetchInitialData(auth, path),
},
{
path: `/u/:username`,
component: User,
fetchInitialData: (auth, path) => User.fetchInitialData(auth, path),
},
{
path: `/inbox`,
component: Inbox,
fetchInitialData: (auth, path) => Inbox.fetchInitialData(auth, path),
}, },
{ path: `/user/:id`, component: User },
{ path: `/u/:username`, component: User },
{ path: `/inbox`, component: Inbox },
{ {
path: `/modlog/community/:community_id`, path: `/modlog/community/:community_id`,
component: Modlog, component: Modlog,

View file

@ -31,7 +31,9 @@ export class UserService {
public login(res: LoginResponse) { public login(res: LoginResponse) {
this.setClaims(res.jwt); this.setClaims(res.jwt);
IsomorphicCookie.save('jwt', res.jwt, { expires: 365 }); let expires = new Date();
expires.setDate(expires.getDate() + 365);
IsomorphicCookie.save('jwt', res.jwt, { expires });
console.log('jwt cookie set'); console.log('jwt cookie set');
} }

View file

@ -845,6 +845,18 @@ export function getRecipientIdFromProps(props: any): number {
: 1; : 1;
} }
export function getIdFromProps(props: any): number {
return Number(props.match.params.id);
}
export function getCommentIdFromProps(props: any): number {
return Number(props.match.params.comment_id);
}
export function getUsernameFromProps(props: any): string {
return props.match.params.username;
}
export function editCommentRes(data: CommentResponse, comments: Comment[]) { export function editCommentRes(data: CommentResponse, comments: Comment[]) {
let found = comments.find(c => c.id == data.comment.id); let found = comments.find(c => c.id == data.comment.id);
if (found) { if (found) {