summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ui/src/components/communities.tsx41
-rw-r--r--ui/src/components/community.tsx68
-rw-r--r--ui/src/components/data-type-select.tsx15
-rw-r--r--ui/src/components/listing-type-select.tsx15
-rw-r--r--ui/src/components/main.tsx86
-rw-r--r--ui/src/components/search.tsx109
-rw-r--r--ui/src/components/sort-select.tsx10
-rw-r--r--ui/src/components/user-details.tsx307
-rw-r--r--ui/src/components/user.tsx425
-rw-r--r--ui/src/interfaces.ts7
10 files changed, 630 insertions, 453 deletions
diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx
index 10a3ab80..ba362acc 100644
--- a/ui/src/components/communities.tsx
+++ b/ui/src/components/communities.tsx
@@ -13,7 +13,7 @@ import {
GetSiteResponse,
} from '../interfaces';
import { WebSocketService } from '../services';
-import { wsJsonToRes, toast } from '../utils';
+import { wsJsonToRes, toast, getPageFromProps } from '../utils';
import { CommunityLink } from './community-link';
import { i18n } from '../i18next';
@@ -27,12 +27,16 @@ interface CommunitiesState {
loading: boolean;
}
+interface CommunitiesProps {
+ page: number;
+}
+
export class Communities extends Component<any, CommunitiesState> {
private subscription: Subscription;
private emptyState: CommunitiesState = {
communities: [],
loading: true,
- page: this.getPageFromProps(this.props),
+ page: getPageFromProps(this.props),
};
constructor(props: any, context: any) {
@@ -50,19 +54,19 @@ export class Communities extends Component<any, CommunitiesState> {
WebSocketService.Instance.getSite();
}
- getPageFromProps(props: any): number {
- return props.match.params.page ? Number(props.match.params.page) : 1;
- }
-
componentWillUnmount() {
this.subscription.unsubscribe();
}
- // Necessary for back button for some reason
- componentWillReceiveProps(nextProps: any) {
- if (nextProps.history.action == 'POP') {
- this.state = this.emptyState;
- this.state.page = this.getPageFromProps(nextProps);
+ static getDerivedStateFromProps(props: any): CommunitiesProps {
+ return {
+ page: getPageFromProps(props),
+ };
+ }
+
+ componentDidUpdate(_: any, lastState: CommunitiesState) {
+ if (lastState.page !== this.state.page) {
+ this.setState({ loading: true });
this.refetch();
}
}
@@ -172,22 +176,17 @@ export class Communities extends Component<any, CommunitiesState> {
);
}
- updateUrl() {
- this.props.history.push(`/communities/page/${this.state.page}`);
+ updateUrl(paramUpdates: CommunitiesProps) {
+ const page = paramUpdates.page || this.state.page;
+ this.props.history.push(`/communities/page/${page}`);
}
nextPage(i: Communities) {
- i.state.page++;
- i.setState(i.state);
- i.updateUrl();
- i.refetch();
+ i.updateUrl({ page: i.state.page + 1 });
}
prevPage(i: Communities) {
- i.state.page--;
- i.setState(i.state);
- i.updateUrl();
- i.refetch();
+ i.updateUrl({ page: i.state.page - 1 });
}
handleUnsubscribe(communityId: number) {
diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx
index fc999b25..99b692ca 100644
--- a/ui/src/components/community.tsx
+++ b/ui/src/components/community.tsx
@@ -65,6 +65,18 @@ interface State {
site: Site;
}
+interface CommunityProps {
+ dataType: DataType;
+ sort: SortType;
+ page: number;
+}
+
+interface UrlParams {
+ dataType?: string;
+ sort?: string;
+ page?: number;
+}
+
export class Community extends Component<any, State> {
private subscription: Subscription;
private emptyState: State = {
@@ -143,16 +155,21 @@ export class Community extends Component<any, State> {
this.subscription.unsubscribe();
}
- // Necessary for back button for some reason
- componentWillReceiveProps(nextProps: any) {
+ static getDerivedStateFromProps(props: any): CommunityProps {
+ return {
+ dataType: getDataTypeFromProps(props),
+ sort: getSortTypeFromProps(props),
+ page: getPageFromProps(props),
+ };
+ }
+
+ componentDidUpdate(_: any, lastState: State) {
if (
- nextProps.history.action == 'POP' ||
- nextProps.history.action == 'PUSH'
+ lastState.dataType !== this.state.dataType ||
+ lastState.sort !== this.state.sort ||
+ lastState.page !== this.state.page
) {
- this.state.dataType = getDataTypeFromProps(nextProps);
- this.state.sort = getSortTypeFromProps(nextProps);
- this.state.page = getPageFromProps(nextProps);
- this.setState(this.state);
+ this.setState({ loading: true });
this.fetchData();
}
}
@@ -273,46 +290,33 @@ export class Community extends Component<any, State> {
}
nextPage(i: Community) {
- i.state.page++;
- i.setState(i.state);
- i.updateUrl();
- i.fetchData();
+ i.updateUrl({ page: i.state.page + 1 });
window.scrollTo(0, 0);
}
prevPage(i: Community) {
- i.state.page--;
- i.setState(i.state);
- i.updateUrl();
- i.fetchData();
+ i.updateUrl({ page: i.state.page - 1 });
window.scrollTo(0, 0);
}
handleSortChange(val: SortType) {
- this.state.sort = val;
- this.state.page = 1;
- this.state.loading = true;
- this.setState(this.state);
- this.updateUrl();
- this.fetchData();
+ this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
window.scrollTo(0, 0);
}
handleDataTypeChange(val: DataType) {
- this.state.dataType = val;
- this.state.page = 1;
- this.state.loading = true;
- this.setState(this.state);
- this.updateUrl();
- this.fetchData();
+ this.updateUrl({ dataType: DataType[val].toLowerCase(), page: 1 });
window.scrollTo(0, 0);
}
- updateUrl() {
- let dataTypeStr = DataType[this.state.dataType].toLowerCase();
- let sortStr = SortType[this.state.sort].toLowerCase();
+ updateUrl(paramUpdates: UrlParams) {
+ const dataTypeStr =
+ paramUpdates.dataType || DataType[this.state.dataType].toLowerCase();
+ const sortStr =
+ paramUpdates.sort || SortType[this.state.sort].toLowerCase();
+ const page = paramUpdates.page || this.state.page;
this.props.history.push(
- `/c/${this.state.community.name}/data_type/${dataTypeStr}/sort/${sortStr}/page/${this.state.page}`
+ `/c/${this.state.community.name}/data_type/${dataTypeStr}/sort/${sortStr}/page/${page}`
);
}
diff --git a/ui/src/components/data-type-select.tsx b/ui/src/components/data-type-select.tsx
index f2539c81..8e905bf4 100644
--- a/ui/src/components/data-type-select.tsx
+++ b/ui/src/components/data-type-select.tsx
@@ -25,6 +25,12 @@ export class DataTypeSelect extends Component<
this.state = this.emptyState;
}
+ static getDerivedStateFromProps(props) {
+ return {
+ type_: props.type_,
+ };
+ }
+
render() {
return (
<div class="btn-group btn-group-toggle">
@@ -42,8 +48,9 @@ export class DataTypeSelect extends Component<
{i18n.t('posts')}
</label>
<label
- className={`pointer btn btn-sm btn-secondary ${this.state.type_ ==
- DataType.Comment && 'active'}`}
+ className={`pointer btn btn-sm btn-secondary ${
+ this.state.type_ == DataType.Comment && 'active'
+ }`}
>
<input
type="radio"
@@ -58,8 +65,6 @@ export class DataTypeSelect extends Component<
}
handleTypeChange(i: DataTypeSelect, event: any) {
- i.state.type_ = Number(event.target.value);
- i.setState(i.state);
- i.props.onChange(i.state.type_);
+ i.props.onChange(Number(event.target.value));
}
}
diff --git a/ui/src/components/listing-type-select.tsx b/ui/src/components/listing-type-select.tsx
index d583b93c..6d13f19a 100644
--- a/ui/src/components/listing-type-select.tsx
+++ b/ui/src/components/listing-type-select.tsx
@@ -26,6 +26,12 @@ export class ListingTypeSelect extends Component<
this.state = this.emptyState;
}
+ static getDerivedStateFromProps(props) {
+ return {
+ type_: props.type_,
+ };
+ }
+
render() {
return (
<div class="btn-group btn-group-toggle">
@@ -45,8 +51,9 @@ export class ListingTypeSelect extends Component<
{i18n.t('subscribed')}
</label>
<label
- className={`pointer btn btn-sm btn-secondary ${this.state.type_ ==
- ListingType.All && 'active'}`}
+ className={`pointer btn btn-sm btn-secondary ${
+ this.state.type_ == ListingType.All && 'active'
+ }`}
>
<input
type="radio"
@@ -61,8 +68,6 @@ export class ListingTypeSelect extends Component<
}
handleTypeChange(i: ListingTypeSelect, event: any) {
- i.state.type_ = Number(event.target.value);
- i.setState(i.state);
- i.props.onChange(i.state.type_);
+ i.props.onChange(Number(event.target.value));
}
}
diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx
index 9063a039..0560e802 100644
--- a/ui/src/components/main.tsx
+++ b/ui/src/components/main.tsx
@@ -70,6 +70,20 @@ interface MainState {
page: number;
}
+interface MainProps {
+ listingType: ListingType;
+ dataType: DataType;
+ sort: SortType;
+ page: number;
+}
+
+interface UrlParams {
+ listingType?: string;
+ dataType?: string;
+ sort?: string;
+ page?: number;
+}
+
export class Main extends Component<any, MainState> {
private subscription: Subscription;
private emptyState: MainState = {
@@ -141,17 +155,23 @@ export class Main extends Component<any, MainState> {
this.subscription.unsubscribe();
}
- // Necessary for back button for some reason
- componentWillReceiveProps(nextProps: any) {
+ static getDerivedStateFromProps(props: any): MainProps {
+ return {
+ listingType: getListingTypeFromProps(props),
+ dataType: getDataTypeFromProps(props),
+ sort: getSortTypeFromProps(props),
+ page: getPageFromProps(props),
+ };
+ }
+
+ componentDidUpdate(_: any, lastState: MainState) {
if (
- nextProps.history.action == 'POP' ||
- nextProps.history.action == 'PUSH'
+ lastState.listingType !== this.state.listingType ||
+ lastState.dataType !== this.state.dataType ||
+ lastState.sort !== this.state.sort ||
+ lastState.page !== this.state.page
) {
- this.state.listingType = getListingTypeFromProps(nextProps);
- this.state.dataType = getDataTypeFromProps(nextProps);
- this.state.sort = getSortTypeFromProps(nextProps);
- this.state.page = getPageFromProps(nextProps);
- this.setState(this.state);
+ this.setState({ loading: true });
this.fetchData();
}
}
@@ -257,12 +277,17 @@ export class Main extends Component<any, MainState> {
);
}
- updateUrl() {
- let listingTypeStr = ListingType[this.state.listingType].toLowerCase();
- let dataTypeStr = DataType[this.state.dataType].toLowerCase();
- let sortStr = SortType[this.state.sort].toLowerCase();
+ updateUrl(paramUpdates: UrlParams) {
+ const listingTypeStr =
+ paramUpdates.listingType ||
+ ListingType[this.state.listingType].toLowerCase();
+ const dataTypeStr =
+ paramUpdates.dataType || DataType[this.state.dataType].toLowerCase();
+ const sortStr =
+ paramUpdates.sort || SortType[this.state.sort].toLowerCase();
+ const page = paramUpdates.page || this.state.page;
this.props.history.push(
- `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${this.state.page}`
+ `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
);
}
@@ -529,50 +554,27 @@ export class Main extends Component<any, MainState> {
}
nextPage(i: Main) {
- i.state.page++;
- i.state.loading = true;
- i.setState(i.state);
- i.updateUrl();
- i.fetchData();
+ i.updateUrl({ page: this.state.page + 1 });
window.scrollTo(0, 0);
}
prevPage(i: Main) {
- i.state.page--;
- i.state.loading = true;
- i.setState(i.state);
- i.updateUrl();
- i.fetchData();
+ i.updateUrl({ page: this.state.page - 1 });
window.scrollTo(0, 0);
}
handleSortChange(val: SortType) {
- this.state.sort = val;
- this.state.page = 1;
- this.state.loading = true;
- this.setState(this.state);
- this.updateUrl();
- this.fetchData();
+ this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
window.scrollTo(0, 0);
}
handleListingTypeChange(val: ListingType) {
- this.state.listingType = val;
- this.state.page = 1;
- this.state.loading = true;
- this.setState(this.state);
- this.updateUrl();
- this.fetchData();
+ this.updateUrl({ listingType: ListingType[val].toLowerCase(), page: 1 });
window.scrollTo(0, 0);
}
handleDataTypeChange(val: DataType) {
- this.state.dataType = val;
- this.state.page = 1;
- this.state.loading = true;
- this.setState(this.state);
- this.updateUrl();
- this.fetchData();
+ this.updateUrl({ dataType: DataType[val].toLowerCase(), page: 1 });
window.scrollTo(0, 0);
}
diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx
index b2e55497..d1d99cee 100644
--- a/ui/src/components/search.tsx
+++ b/ui/src/components/search.tsx
@@ -28,6 +28,7 @@ import {
createCommentLikeRes,
createPostLikeFindRes,
commentsToFlatNodes,
+ getPageFromProps,
} from '../utils';
import { PostListing } from './post-listing';
import { UserListing } from './user-listing';
@@ -44,15 +45,31 @@ interface SearchState {
searchResponse: SearchResponse;
loading: boolean;
site: Site;
+ searchText: string;
+}
+
+interface SearchProps {
+ q: string;
+ type_: SearchType;
+ sort: SortType;
+ page: number;
+}
+
+interface UrlParams {
+ q?: string;
+ type_?: string;
+ sort?: string;
+ page?: number;
}
export class Search extends Component<any, SearchState> {
private subscription: Subscription;
private emptyState: SearchState = {
- q: this.getSearchQueryFromProps(this.props),
- type_: this.getSearchTypeFromProps(this.props),
- sort: this.getSortTypeFromProps(this.props),
- page: this.getPageFromProps(this.props),
+ q: Search.getSearchQueryFromProps(this.props),
+ type_: Search.getSearchTypeFromProps(this.props),
+ sort: Search.getSortTypeFromProps(this.props),
+ page: getPageFromProps(this.props),
+ searchText: Search.getSearchQueryFromProps(this.props),
searchResponse: {
type_: null,
posts: [],
@@ -77,26 +94,22 @@ export class Search extends Component<any, SearchState> {
},
};
- getSearchQueryFromProps(props: any): string {
+ static getSearchQueryFromProps(props: any): string {
return props.match.params.q ? props.match.params.q : '';
}
- getSearchTypeFromProps(props: any): SearchType {
+ static getSearchTypeFromProps(props: any): SearchType {
return props.match.params.type
? routeSearchTypeToEnum(props.match.params.type)
: SearchType.All;
}
- getSortTypeFromProps(props: any): SortType {
+ static getSortTypeFromProps(props: any): SortType {
return props.match.params.sort
? routeSortTypeToEnum(props.match.params.sort)
: SortType.TopAll;
}
- getPageFromProps(props: any): number {
- return props.match.params.page ? Number(props.match.params.page) : 1;
- }
-
constructor(props: any, context: any) {
super(props, context);
@@ -122,17 +135,23 @@ export class Search extends Component<any, SearchState> {
this.subscription.unsubscribe();
}
- // Necessary for back button for some reason
- componentWillReceiveProps(nextProps: any) {
+ static getDerivedStateFromProps(props: any): SearchProps {
+ return {
+ q: Search.getSearchQueryFromProps(props),
+ type_: Search.getSearchTypeFromProps(props),
+ sort: Search.getSortTypeFromProps(props),
+ page: getPageFromProps(props),
+ };
+ }
+
+ componentDidUpdate(_: any, lastState: SearchState) {
if (
- nextProps.history.action == 'POP' ||
- nextProps.history.action == 'PUSH'
+ lastState.q !== this.state.q ||
+ lastState.type_ !== this.state.type_ ||
+ lastState.sort !== this.state.sort ||
+ lastState.page !== this.state.page
) {
- this.state.q = this.getSearchQueryFromProps(nextProps);
- this.state.type_ = this.getSearchTypeFromProps(nextProps);
- this.state.sort = this.getSortTypeFromProps(nextProps);
- this.state.page = this.getPageFromProps(nextProps);
- this.setState(this.state);
+ this.setState({ loading: true, searchText: this.state.q });
this.search();
}
}
@@ -163,7 +182,7 @@ export class Search extends Component<any, SearchState> {
<input
type="text"
class="form-control mr-2"
- value={this.state.q}
+ value={this.state.searchText}
placeholder={`${i18n.t('search')}...`}
onInput={linkEvent(this, this.handleQChange)}
required
@@ -413,17 +432,11 @@ export class Search extends Component<any, SearchState> {
}
nextPage(i: Search) {
- i.state.page++;
- i.setState(i.state);
- i.updateUrl();
- i.search();
+ i.updateUrl({ page: i.state.page + 1 });
}
prevPage(i: Search) {
- i.state.page--;
- i.setState(i.state);
- i.updateUrl();
- i.search();
+ i.updateUrl({ page: i.state.page - 1 });
}
search() {
@@ -441,37 +454,39 @@ export class Search extends Component<any, SearchState> {
}
handleSortChange(val: SortType) {
- this.state.sort = val;
- this.state.page = 1;
- this.setState(this.state);
- this.updateUrl();
+ this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
}
handleTypeChange(i: Search, event: any) {
- i.state.type_ = Number(event.target.value);
- i.state.page = 1;
- i.setState(i.state);
- i.updateUrl();
+ i.updateUrl({
+ type_: SearchType[Number(event.target.value)].toLowerCase(),
+ page: 1,
+ });
}
handleSearchSubmit(i: Search, event: any) {
event.preventDefault();
- i.state.loading = true;
- i.search();
- i.setState(i.state);
- i.updateUrl();
+ i.updateUrl({
+ q: i.state.searchText,
+ type_: SearchType[i.state.type_].toLowerCase(),
+ sort: SortType[i.state.sort].toLowerCase(),
+ page: i.state.page,
+ });
}
handleQChange(i: Search, event: any) {
- i.state.q = event.target.value;
- i.setState(i.state);
+ i.setState({ searchText: event.target.value });
}
- updateUrl() {
- let typeStr = SearchType[this.state.type_].toLowerCase();
- let sortStr = SortType[this.state.sort].toLowerCase();
+ updateUrl(paramUpdates: UrlParams) {
+ const qStr = paramUpdates.q || this.state.q;
+ const typeStr =
+ paramUpdates.type_ || SearchType[this.state.type_].toLowerCase();
+ const sortStr =
+ paramUpdates.sort || SortType[this.state.sort].toLowerCase();
+ const page = paramUpdates.page || this.state.page;
this.props.history.push(
- `/search/q/${this.state.q}/type/${typeStr}/sort/${sortStr}/page/${this.state.page}`
+ `/search/q/${qStr}/type/${typeStr}/sort/${sortStr}/page/${page}`
);
}
diff --git a/ui/src/components/sort-select.tsx b/ui/src/components/sort-select.tsx
index 05abdb20..33d65819 100644
--- a/ui/src/components/sort-select.tsx
+++ b/ui/src/components/sort-select.tsx
@@ -23,6 +23,12 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
this.state = this.emptyState;
}
+ static getDerivedStateFromProps(props: any): SortSelectState {
+ return {
+ sort: props.sort,
+ };
+ }
+
render() {
return (
<>
@@ -59,8 +65,6 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
}
handleSortChange(i: SortSelect, event: any) {
- i.state.sort = Number(event.target.value);
- i.setState(i.state);
- i.props.onChange(i.state.sort);
+ i.props.onChange(event.target.value);
}
}
diff --git a/ui/src/components/user-details.tsx b/ui/src/components/user-details.tsx
new file mode 100644
index 00000000..e4b4b24a
--- /dev/null
+++ b/ui/src/components/user-details.tsx
@@ -0,0 +1,307 @@
+import { Component, linkEvent } from 'inferno';
+import { WebSocketService, UserService } from '../services';
+import { Subscription } from 'rxjs';
+import { retryWhen, delay, take, last } from 'rxjs/operators';
+import { i18n } from '../i18next';
+import {
+ UserOperation,
+ Post,
+ Comment,
+ CommunityUser,
+ SortType,
+ UserDetailsResponse,
+ UserView,
+ WebSocketJsonResponse,
+ UserDetailsView,
+ CommentResponse,
+ BanUserResponse,
+ PostResponse,
+ AddAdminResponse,
+} from '../interfaces';
+import {
+ wsJsonToRes,
+ toast,
+ commentsToFlatNodes,
+ setupTippy,
+ editCommentRes,
+ saveCommentRes,
+ createCommentLikeRes,
+ createPostLikeFindRes,
+} from '../utils';
+import { PostListing } from './post-listing';
+import { CommentNodes } from './comment-nodes';
+
+interface UserDetailsProps {
+ username?: string;
+ user_id?: number;
+ page: number;
+ limit: number;
+ sort: string;
+ enableDownvotes: boolean;
+ enableNsfw: boolean;
+ view: UserDetailsView;
+ onPageChange(page: number): number | any;
+}
+
+interface UserDetailsState {
+ follows: Array<CommunityUser>;
+ moderates: Array<CommunityUser>;
+ comments: Array<Comment>;
+ posts: Array<Post>;
+ saved?: Array<Post>;
+ admins: Array<UserView>;
+}
+
+export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
+ private subscription: Subscription;
+ constructor(props: any, context: any) {
+ super(props, context);
+
+ this.state = {
+ follows: [],
+ moderates: [],
+ comments: [],
+ posts: [],
+ saved: [],
+ admins: [],
+ };
+
+ 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();
+ }
+
+ componentDidMount() {
+ this.fetchUserData();
+ }
+
+ componentDidUpdate(lastProps: UserDetailsProps) {
+ for (const key of Object.keys(lastProps)) {
+ if (lastProps[key] !== this.props[key]) {
+ this.fetchUserData();
+ break;
+ }
+ }
+ setupTippy();
+ }
+
+ 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() {
+ return (
+ <div>
+ {this.viewSelector(this.props.view)}
+ {this.paginator()}
+ </div>
+ );
+ }
+
+ viewSelector(view: UserDetailsView) {
+ if (view === UserDetailsView.Overview || view === UserDetailsView.Saved) {
+ return this.overview();
+ }
+ if (view === UserDetailsView.Comments) {
+ return this.comments();
+ }
+ if (view === UserDetailsView.Posts) {
+ return this.posts();
+ }
+ }
+
+ overview() {
+ const comments = this.state.comments.map((c: Comment) => {
+ return { type: 'comments', data: c };
+ });
+ const posts = this.state.posts.map((p: Post) => {
+ return { type: 'posts', data: p };
+ });
+
+ const combined: Array<{ type: string; data: Comment | Post }> = [
+ ...comments,
+ ...posts,
+ ];
+
+ // Sort it
+ if (SortType[this.props.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 => (
+ <div>
+ {i.type === 'posts' ? (
+ <PostListing
+ post={i.data as Post}
+ admins={this.state.admins}
+ showCommunity
+ enableDownvotes={this.props.enableDownvotes}
+ enableNsfw={this.props.enableNsfw}
+ />
+ ) : (
+ <CommentNodes
+ nodes={[{ comment: i.data as Comment }]}
+ admins={this.state.admins}
+ noIndent
+ showContext
+ enableDownvotes={this.props.enableDownvotes}
+ />
+ )}
+ </div>
+ ))}
+ </div>
+ );
+ }
+
+ comments() {
+ return (
+ <div>
+ <CommentNodes
+ nodes={commentsToFlatNodes(this.state.comments)}
+ admins={this.state.admins}
+ noIndent
+ showContext
+ enableDownvotes={this.props.enableDownvotes}
+ />
+ </div>
+ );
+ }
+
+ posts() {
+ return (
+ <div>
+ {this.state.posts.map(post => (
+ <PostListing
+ post={post}
+ admins={this.state.admins}
+ showCommunity
+ enableDownvotes={this.props.enableDownvotes}
+ enableNsfw={this.props.enableNsfw}
+ />
+ ))}
+ </div>
+ );
+ }
+
+ paginator() {
+ return (
+ <div class="my-2">
+ {this.props.page > 1 && (
+ <button
+ class="btn btn-sm btn-secondary mr-1"
+ onClick={linkEvent(this, this.prevPage)}
+ >
+ {i18n.t('prev')}
+ </button>
+ )}
+ {this.state.comments.length + this.state.posts.length > 0 && (
+ <button
+ class="btn btn-sm btn-secondary"
+ onClick={linkEvent(this, this.nextPage)}
+ >
+ {i18n.t('next')}
+ </button>
+ )}
+ </div>
+ );
+ }
+
+ nextPage(i: UserDetails) {
+ i.props.onPageChange(i.props.page + 1);
+ }
+
+ prevPage(i: UserDetails) {
+ i.props.onPageChange(i.props.page - 1);
+ }
+
+ parseMessage(msg: WebSocketJsonResponse) {
+ 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,
+ admins: data.admins,
+ });
+ } 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) {
+ 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,
+ });
+ } else if (res.op == UserOperation.AddAdmin) {
+ const data = res.data as AddAdminResponse;
+ this.setState({
+ admins: data.admins,
+ });
+ }
+ }
+}
diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx
index 854dd6ef..945206c1 100644
--- a/ui/src/components/user.tsx
+++ b/ui/src/components/user.tsx
@@ -4,24 +4,18 @@ import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
UserOperation,
- Post,
- Comment,
CommunityUser,
- GetUserDetailsForm,
SortType,
ListingType,
- UserDetailsResponse,
UserView,
- CommentResponse,
UserSettingsForm,
LoginResponse,
- BanUserResponse,
- AddAdminResponse,
DeleteAccountForm,
- PostResponse,
WebSocketJsonResponse,
GetSiteResponse,
Site,
+ UserDetailsView,
+ UserDetailsResponse,
} from '../interfaces';
import { WebSocketService, UserService } from '../services';
import {
@@ -34,28 +28,15 @@ import {
languages,
showAvatars,
toast,
- editCommentRes,
- saveCommentRes,
- createCommentLikeRes,
- createPostLikeFindRes,
- commentsToFlatNodes,
setupTippy,
} from '../utils';
-import { PostListing } from './post-listing';
import { UserListing } from './user-listing';
import { SortSelect } from './sort-select';
import { ListingTypeSelect } from './listing-type-select';
-import { CommentNodes } from './comment-nodes';
import { MomentTime } from './moment-time';
import { i18n } from '../i18next';
import moment from 'moment';
-
-enum View {
- Overview,
- Comments,
- Posts,
- Saved,
-}
+import { UserDetails } from './user-details';
interface UserState {
user: UserView;
@@ -63,11 +44,7 @@ interface UserState {
username: string;
follows: Array<CommunityUser>;
moderates: Array<CommunityUser>;
- comments: Array<Comment>;
- posts: Array<Post>;
- saved?: Array<Post>;
- admins: Array<UserView>;
- view: View;
+ view: UserDetailsView;
sort: SortType;
page: number;
loading: boolean;
@@ -80,6 +57,20 @@ interface UserState {
site: Site;
}
+interface UserProps {
+ view: UserDetailsView;
+ sort: SortType;
+ page: number;
+ user_id: number | null;
+ username: string;
+}
+
+interface UrlParams {
+ view?: string;
+ sort?: string;
+ page?: number;
+}
+
export class User extends Component<any, UserState> {
private subscription: Subscription;
private emptyState: UserState = {
@@ -102,14 +93,11 @@ export class User extends Component<any, UserState> {
username: null,
follows: [],
moderates: [],
- comments: [],
- posts: [],
- admins: [],
- loading: true,
+ loading: false,
avatarLoading: false,
- view: this.getViewFromProps(this.props),
- sort: this.getSortTypeFromProps(this.props),
- page: this.getPageFromProps(this.props),
+ view: User.getViewFromProps(this.props.match.view),
+ sort: User.getSortTypeFromProps(this.props.match.sort),
+ page: User.getPageFromProps(this.props.match.page),
userSettingsForm: {
show_nsfw: null,