Browse Source

Merge branch 'main' into dev

feature/static-downtime-page
eiknat 1 year ago
parent
commit
93f2769f66
  1. 119
      src/components/communities.tsx
  2. 41
      src/components/main.tsx
  3. 229
      src/components/post-form.tsx
  4. 1
      src/interfaces.ts
  5. 6
      src/services/WebSocketService.ts

119
src/components/communities.tsx

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import React, { Component } from 'react';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
@ -7,12 +8,11 @@ import {
ListCommunitiesResponse,
CommunityResponse,
FollowCommunityForm,
ListCommunitiesForm,
SortType,
WebSocketJsonResponse,
} from '../interfaces';
import { WebSocketService } from '../services';
import { wsJsonToRes, toast, getPageFromProps, siteName } from '../utils';
import { wsJsonToRes, toast, getPageFromProps, siteName, api } from '../utils';
import { CommunityLink } from './community-link';
import { i18n } from '../i18next';
import { linkEvent } from '../linkEvent';
@ -20,6 +20,7 @@ import { SpinnerSection } from './Spinner';
import Button from './elements/Button';
import { Heading } from 'theme-ui';
import Block from './elements/Block';
import { AxiosError } from 'axios';
const communityLimit = 100;
@ -53,10 +54,10 @@ export class Communities extends Component<any, CommunitiesState> {
() => console.log('complete')
);
this.refetch();
this.fetchCommunityList();
}
componentWillUnmount() {
componentWillUnmount(): void {
this.subscription.unsubscribe();
}
@ -66,14 +67,14 @@ export class Communities extends Component<any, CommunitiesState> {
};
}
componentDidUpdate(_: any, lastState: CommunitiesState) {
componentDidUpdate(_: any, lastState: CommunitiesState): void {
if (lastState.page !== this.state.page) {
this.setState({ loading: true });
this.refetch();
this.fetchCommunityList();
}
}
render() {
render(): JSX.Element {
const { subscribed, notSubscribed } = this.state.communities.reduce(
(result, community) => {
if (community.subscribed) {
@ -89,7 +90,6 @@ export class Communities extends Component<any, CommunitiesState> {
}
);
// console.log({ combinedCommunities });
return (
<div className="container">
{this.state.loading ? (
@ -148,12 +148,7 @@ export class Communities extends Component<any, CommunitiesState> {
{community.title}
</Block>
<Block display="flex" justifyContent="space-between">
<Block>
{/* <Block as="small" display="block">
{i18n.t('category')}:
</Block> */}
{community.category_name}
</Block>
<Block>{community.category_name}</Block>
<Block>
{community.number_of_subscribers}{' '}
{i18n.t('subscribers')}
@ -172,36 +167,6 @@ export class Communities extends Component<any, CommunitiesState> {
</>
);
})}
{/* {this.state.communities} */}
{/* <div className="table-responsive">
<Box
as="table"
id="community_table"
className="table table-sm table-hover"
color="text"
>
<thead className="pointer">
<tr>
<th>{i18n.t('name')}</th>
<th className="d-none d-lg-table-cell">
{i18n.t('title')}
</th>
<th>{i18n.t('category')}</th>
<th className="text-right">{i18n.t('subscribers')}</th>
<th className="text-right d-none d-lg-table-cell">
{i18n.t('posts')}
</th>
<th className="text-right d-none d-lg-table-cell">
{i18n.t('comments')}
</th>
<th />
</tr>
</thead>
<tbody>
</tbody>
</Box>
</div> */}
{this.paginator()}
</Block>
)}
@ -209,7 +174,7 @@ export class Communities extends Component<any, CommunitiesState> {
);
}
paginator() {
paginator(): JSX.Element {
return (
<div className="mt-2">
{this.state.page > 1 && (
@ -233,20 +198,20 @@ export class Communities extends Component<any, CommunitiesState> {
);
}
updateUrl(paramUpdates: CommunitiesProps) {
updateUrl(paramUpdates: CommunitiesProps): void {
const page = paramUpdates.page || this.state.page;
this.props.history.push(`/communities/page/${page}`);
}
nextPage(i: Communities) {
nextPage(i: Communities): void {
i.updateUrl({ page: i.state.page + 1 });
}
prevPage(i: Communities) {
prevPage(i: Communities): void {
i.updateUrl({ page: i.state.page - 1 });
}
handleUnsubscribe(communityId: number) {
handleUnsubscribe(communityId: number): void {
let form: FollowCommunityForm = {
community_id: communityId,
follow: false,
@ -254,7 +219,7 @@ export class Communities extends Component<any, CommunitiesState> {
WebSocketService.Instance.followCommunity(form);
}
handleSubscribe(communityId: number) {
handleSubscribe(communityId: number): void {
let form: FollowCommunityForm = {
community_id: communityId,
follow: true,
@ -262,38 +227,48 @@ export class Communities extends Component<any, CommunitiesState> {
WebSocketService.Instance.followCommunity(form);
}
refetch() {
let listCommunitiesForm: ListCommunitiesForm = {
sort: SortType[SortType.TopAll],
async fetchCommunityList(): Promise<void> {
const params = new URLSearchParams({
sort: SortType[SortType.New],
limit: communityLimit,
page: this.state.page,
};
} as any);
WebSocketService.Instance.listCommunities(listCommunitiesForm);
api
.get<ListCommunitiesResponse>(`community/list?${params.toString()}`)
.then(res => {
const data = res.data;
this.setState(
{
communities: data.communities.sort(
(a, b) => b.number_of_subscribers - a.number_of_subscribers
),
loading: false,
},
() => {
window.scrollTo(0, 0);
// XXX: Sort this table?
let table = document.querySelector('#community_table');
return table;
}
);
})
.catch((err: Error | AxiosError) => {
const res = (err as AxiosError).response;
if (res) {
const data = res.data as { error: string };
toast(i18n.t(data.error), 'danger');
} else {
console.log(err);
}
});
}
parseMessage(msg: WebSocketJsonResponse) {
console.log(msg);
let res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
return;
} else if (res.op == UserOperation.ListCommunities) {
let data = res.data as ListCommunitiesResponse;
this.setState(
{
communities: data.communities.sort(
(a, b) => b.number_of_subscribers - a.number_of_subscribers
),
loading: false,
},
() => {
window.scrollTo(0, 0);
// XXX: Sort this table?
let table = document.querySelector('#community_table');
return table;
}
);
} else if (res.op == UserOperation.FollowCommunity) {
let data = res.data as CommunityResponse;
let found = this.state.communities.find(c => c.id == data.community.id);

41
src/components/main.tsx

@ -9,14 +9,12 @@ import {
UserOperation,
CommunityUser,
GetFollowedCommunitiesResponse,
ListCommunitiesForm,
ListCommunitiesResponse,
Community,
SortType,
GetSiteResponse,
ListingType,
DataType,
GetPostsResponse,
PostResponse,
Post,
Comment,
@ -61,6 +59,7 @@ import {
getRandomTagline,
getMoscowTime,
siteName,
api,
} from '../utils';
import { BASE_PATH } from '../isProduction';
import { i18n } from '../i18next';
@ -79,6 +78,7 @@ import Tooltip from './Tooltip';
import Header, { Separator } from './Header';
import { siteSubject } from '../services/SiteService';
import FeaturedPosts from './FeaturedPosts';
import { AxiosError } from 'axios';
interface MainState {
subscribedCommunities: Array<CommunityUser>;
@ -189,11 +189,6 @@ class Main extends Component<MainProps & RouteComponentProps, MainState> {
if (UserService.Instance.user) {
WebSocketService.Instance.getFollowedCommunities();
}
let listCommunitiesForm: ListCommunitiesForm = {
sort: SortType[SortType.Hot],
limit: 6,
};
WebSocketService.Instance.listCommunities(listCommunitiesForm);
this.fetchData();
}
@ -738,10 +733,35 @@ class Main extends Component<MainProps & RouteComponentProps, MainState> {
};
WebSocketService.Instance.getComments(getCommentsForm);
}
this.fetchCommunityList();
}
async fetchCommunityList() {
const params = new URLSearchParams({
sort: SortType[SortType.Hot],
limit: 6,
} as any);
api
.get<ListCommunitiesResponse>(`community/list?${params.toString()}`)
.then(res => {
this.setState({
trendingCommunities: res.data.communities,
});
})
.catch((err: Error | AxiosError) => {
const res = (err as AxiosError).response;
if (res) {
const data = res.data as { error: string };
toast(i18n.t(data.error), 'danger');
} else {
console.log(err);
}
});
}
parseMessage(msg: WebSocketJsonResponse) {
console.log(msg);
let res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
@ -753,11 +773,6 @@ class Main extends Component<MainProps & RouteComponentProps, MainState> {
this.setState({
subscribedCommunities: data.communities,
});
} else if (res.op === UserOperation.ListCommunities) {
let data = res.data as ListCommunitiesResponse;
this.setState({
trendingCommunities: data.communities,
});
} else if (res.op === UserOperation.CreatePost) {
let data = res.data as PostResponse;

229
src/components/post-form.tsx

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import React, { Component, useState } from 'react';
import { Prompt } from 'react-router-dom';
import { PostListings } from './post-listings';
@ -35,6 +36,7 @@ import {
pictrsDeleteToast,
validTitle,
isPostChanged,
api,
} from '../utils';
import { i18n } from '../i18next';
import { cleanURL } from '../clean-url';
@ -51,6 +53,7 @@ import {
} from '@reach/combobox';
import '@reach/combobox/styles.css';
import { Alert, Button, Input, Textarea } from 'theme-ui';
import { AxiosError } from 'axios';
export const MAX_POST_TITLE_LENGTH = 160;
export const MAX_POST_BODY_LENGTH = 20000;
@ -219,7 +222,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.handlePostBodyChange = this.handlePostBodyChange.bind(this);
}
componentDidMount() {
componentDidMount(): void {
if (this.props.post) {
this.state.postForm = {
body: this.props.post.body,
@ -260,17 +263,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
() => console.log('complete')
);
let listCommunitiesForm: ListCommunitiesForm = {
sort: SortType[SortType.TopAll],
limit: 9999,
};
WebSocketService.Instance.listCommunities(listCommunitiesForm);
this.fetchCommunityList();
setupTippy();
}
componentDidUpdate() {
componentDidUpdate(): void {
if (
!this.state.loading &&
(this.state.postForm.name ||
@ -283,13 +280,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
}
}
componentWillUnmount() {
componentWillUnmount(): void {
// this.choices && this.choices.destroy();
this.subscription.unsubscribe();
window.onbeforeunload = null;
}
render() {
render(): JSX.Element {
const postTitleBlank =
this.state.postForm.name === null ||
this.state.postForm.name.trim() === '';
@ -340,6 +337,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<div
className="mt-1 text-muted small font-weight-bold pointer"
onClick={linkEvent(this, this.copySuggestedTitle)}
onKeyDown={linkEvent(this, this.copySuggestedTitle)}
>
{i18n.t('copy_suggested_title', {
title: this.state.suggestedTitle,
@ -467,35 +465,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{i18n.t('community')}
</label>
<div className="col-sm-10">
{/* <select
className="form-control"
id="post-community"
value={this.state.postForm.community_id}
onInput={linkEvent(this, this.handlePostCommunityChange)}
>
<option>{i18n.t('select_a_community')}</option>
{this.state.communities
.filter(community => {
// don't allow crossposting to same community as original
if (this.state.crosspostCommunityId) {
// remove main community
const MAIN_COMMUNITY_ID = 2;
return (
community.id !== this.state.crosspostCommunityId &&
community.id != MAIN_COMMUNITY_ID
);
}
return true;
})
.map(community => (
<option key={community.id} value={community.id}>
{community.local
? community.name
: `${hostname(community.actor_id)}/${community.name}`}
</option>
))}
</select> */}
<CommunityInput
initialValue={this.props.params?.community}
onSelect={this.handlePostCommunityChange}
@ -558,7 +527,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
);
}
handlePostSubmit(i: PostForm, event: any) {
handlePostSubmit(i: PostForm, event: any): void {
event.preventDefault();
// make sure post title is not just whitespace
@ -588,7 +557,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
i.setState(i.state);
}
copySuggestedTitle(i: PostForm) {
copySuggestedTitle(i: PostForm): void {
i.state.postForm.name = i.state.suggestedTitle.substring(
0,
MAX_POST_TITLE_LENGTH
@ -597,15 +566,15 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
i.setState(i.state);
}
handlePostUrlChange = (event: any) => {
handlePostUrlChange = (event: any): void => {
// i.state.postForm.url = event.target.value;
this.setState({
postForm: { ...this.state.postForm, url: event.target.value },
});
this.setState(prev => ({
postForm: { ...prev.postForm, url: event.target.value },
}));
this.fetchPageTitle();
};
async fetchPageTitle() {
async fetchPageTitle(): Promise<void> {
if (validURL(this.state.postForm.url)) {
let form: SearchForm = {
q: this.state.postForm.url,
@ -620,23 +589,19 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
// Fetch the page title
const title = await getPageTitle(this.state.postForm.url);
if (title !== null) {
this.state.suggestedTitle = title;
this.setState(this.state);
this.setState({ suggestedTitle: title });
}
} else {
this.state.suggestedTitle = undefined;
this.state.crossPosts = [];
this.setState({ suggestedTitle: undefined, crossPosts: [] });
}
}
handlePostNameChange(i: PostForm, event: any) {
let spooked = event.target.value.replace('hexbear', 'hexedbear');
i.state.postForm.name = spooked;
handlePostNameChange(i: PostForm): void {
i.setState(i.state);
i.fetchSimilarPosts();
}
fetchSimilarPosts() {
fetchSimilarPosts(): void {
let form: SearchForm = {
q: this.state.postForm.name,
type_: SearchType[SearchType.Posts],
@ -649,43 +614,40 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
if (this.state.postForm.name !== '') {
WebSocketService.Instance.search(form);
} else {
this.state.suggestedPosts = [];
this.setState({ suggestedPosts: [] });
}
this.setState(this.state);
}
handlePostBodyChange = (val: string) => {
this.setState({ postForm: { ...this.state.postForm, body: val } });
handlePostBodyChange = (val: string): void => {
this.setState(prev => ({ postForm: { ...prev.postForm, body: val } }));
};
handlePostCommunityChange = (community_id: number) => {
this.setState({ postForm: { ...this.state.postForm, community_id } });
handlePostCommunityChange = (community_id: number): void => {
this.setState(prev => ({ postForm: { ...prev.postForm, community_id } }));
};
handlePostNsfwChange(i: PostForm, event: any) {
handlePostNsfwChange(i: PostForm, event: any): void {
i.state.postForm.nsfw = event.target.checked;
i.setState(i.state);
}
handleCancel(i: PostForm) {
handleCancel(i: PostForm): void {
i.props.onCancel();
}
handlePreviewToggle(i: PostForm, event: any) {
handlePreviewToggle(i: PostForm, event: any): void {
event.preventDefault();
i.state.previewMode = !i.state.previewMode;
i.setState(i.state);
}
handleImageUploadPaste(i: PostForm, event: any) {
console.log('PASTING');
handleImageUploadPaste(i: PostForm, event: any): void {
let image = event.clipboardData.files[0];
if (image) {
i.handleImageUpload(i, image);
}
}
handleImageUpload(i: PostForm, event: any) {
handleImageUpload(i: PostForm, event: any): void {
let file: any;
if (event.target) {
event.preventDefault();
@ -735,105 +697,78 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
});
}
// initChoices = (selectId: any) => {
// setTimeout(() => {
// this.choices = new Choices(selectId, {
// shouldSort: false,
// classNames: {
// containerOuter: 'choices',
// containerInner: 'choices__inner bg-secondary border-0',
// input: 'form-control',
// inputCloned: 'choices__input--cloned',
// list: 'choices__list',
// listItems: 'choices__list--multiple',
// listSingle: 'choices__list--single',
// listDropdown: 'choices__list--dropdown',
// item: 'choices__item bg-secondary',
// itemSelectable: 'choices__item--selectable',
// itemDisabled: 'choices__item--disabled',
// itemChoice: 'choices__item--choice',
// placeholder: 'choices__placeholder',
// group: 'choices__group',
// groupHeading: 'choices__heading',
// button: 'choices__button',
// activeState: 'is-active',
// focusState: 'is-focused',
// openState: 'is-open',
// disabledState: 'is-disabled',
// highlightedState: 'text-info',
// selectedState: 'text-info',
// flippedState: 'is-flipped',
// loadingState: 'is-loading',
// noResults: 'has-no-results',
// noChoices: 'has-no-choices',
// },
// });
// this.choices.passedElement.element.addEventListener(
// 'choice',
// (e: any) => {
// this.setState({ postForm: { ...this.state.postForm, community_id: Number(e.detail.choice.value) } });
// },
// false
// );
// }, 10)
// }
parseMessage(msg: WebSocketJsonResponse) {
async fetchCommunityList(): Promise<void> {
const params = new URLSearchParams({
sort: SortType[SortType.Hot],
limit: 9999,
} as any);
api
.get<ListCommunitiesResponse>(`community/list?${params.toString()}`)
.then(res => {
const data = res.data;
this.setState(
{
communities: data.communities,
},
() => {
if (this.props.post) {
this.setState(prev => ({
postForm: {
...prev.postForm,
community_id: this.props.post.community_id,
},
}));
} else if (this.props.params && this.props.params.community) {
let foundCommunityId = data.communities.find(
r => r.name == this.props.params.community
).id;
this.setState(prev => ({
postForm: { ...prev.postForm, community_id: foundCommunityId },
}));
} else {
// By default, the null valued 'Select a Community'
}
}
);
})
.catch((err: Error | AxiosError) => {
const res = (err as AxiosError).response;
if (res) {
const data = res.data as { error: string };
toast(i18n.t(data.error), 'danger');
} else {
console.log(err);
}
});
}
parseMessage(msg: WebSocketJsonResponse): void {
let res = wsJsonToRes(msg);
if (msg.error) {
toast(i18n.t(msg.error), 'danger');
this.state.loading = false;
this.setState(this.state);
this.setState({ loading: false });
return;
} else if (res.op == UserOperation.ListCommunities) {
let data = res.data as ListCommunitiesResponse;
// this.state.communities = data.communities;
this.setState({ communities: data.communities });
if (this.props.post) {
this.setState({
postForm: {
...this.state.postForm,
community_id: this.props.post.community_id,
},
});
} else if (this.props.params && this.props.params.community) {
let foundCommunityId = data.communities.find(
r => r.name == this.props.params.community
).id;
this.setState({
postForm: { ...this.state.postForm, community_id: foundCommunityId },
});
} else {
// By default, the null valued 'Select a Community'
}
// this.setState(this.state);
// Set up select searching
let selectId = document.getElementById('post-community');
if (selectId) {
// this.initChoices(selectId);
}
} else if (res.op == UserOperation.CreatePost) {
let data = res.data as PostResponse;
if (data.post.creator_id == UserService.Instance.user.id) {
this.state.loading = false;
this.setState({ loading: false });
this.props.onCreate(data.post.id);
}
} else if (isPostChanged(res.op)) {
let data = res.data as PostResponse;
if (data.post.creator_id == UserService.Instance.user.id) {
this.state.loading = false;
this.setState({ loading: false });
this.props.onEdit(data.post);
}
} else if (res.op == UserOperation.Search) {
let data = res.data as SearchResponse;
if (data.type_ == SearchType[SearchType.Posts]) {
this.state.suggestedPosts = data.posts;
this.setState({ suggestedPosts: data.posts });
} else if (data.type_ == SearchType[SearchType.Url]) {
this.state.crossPosts = data.posts;
this.setState({ crossPosts: data.posts });
}
this.setState(this.state);
}
}
}

1
src/interfaces.ts

@ -3,7 +3,6 @@ export enum UserOperation {
Register,
CreateCommunity,
CreatePost,
ListCommunities,
ListCategories,
GetCommunity,
CreateComment,

6
src/services/WebSocketService.ts

@ -14,7 +14,6 @@ import {
GetCommunityForm,
FollowCommunityForm,
GetFollowedCommunitiesForm,
ListCommunitiesForm,
GetModlogForm,
BanFromCommunityForm,
AddModToCommunityForm,
@ -171,11 +170,6 @@ export class WebSocketService {
);
}
public listCommunities(form: ListCommunitiesForm) {
this.setAuth(form, false);
this.ws.send(this.wsSendWrapper(UserOperation.ListCommunities, form));
}
public getFollowedCommunities() {
let form: GetFollowedCommunitiesForm = { auth: UserService.Instance.auth };
this.ws.send(

Loading…
Cancel
Save