Browse Source

Community editing

- Community editing mostly working. Fixes #26
main
Dessalines 3 years ago
parent
commit
01bac7be64
  1. 155
      src/components/community-form.tsx
  2. 8
      src/components/community.tsx
  3. 131
      src/components/create-community.tsx
  4. 1
      src/components/post-form.tsx
  5. 21
      src/components/post-listing.tsx
  6. 8
      src/components/post.tsx
  7. 58
      src/components/sidebar.tsx
  8. 9
      src/interfaces.ts
  9. 9
      src/services/WebSocketService.ts
  10. 2
      tsconfig.json

155
src/components/community-form.tsx

@ -0,0 +1,155 @@
import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { CommunityForm as CommunityFormI, UserOperation, Category, ListCategoriesResponse, CommunityResponse } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { msgOp } from '../utils';
import { Community } from '../interfaces';
interface CommunityFormProps {
community?: Community; // If a community is given, that means this is an edit
onCancel?();
onCreate?(id: number);
onEdit?(community: Community);
}
interface CommunityFormState {
communityForm: CommunityFormI;
categories: Array<Category>;
}
export class CommunityForm extends Component<CommunityFormProps, CommunityFormState> {
private subscription: Subscription;
private emptyState: CommunityFormState = {
communityForm: {
name: null,
title: null,
category_id: null
},
categories: []
}
constructor(props, context) {
super(props, context);
this.state = this.emptyState;
if (this.props.community) {
this.state.communityForm = {
name: this.props.community.name,
title: this.props.community.title,
category_id: this.props.community.category_id,
description: this.props.community.description,
edit_id: this.props.community.id,
auth: null
}
}
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")
);
WebSocketService.Instance.listCategories();
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return (
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} pattern="[a-z0-9_]+" title="lowercase, underscores, and no spaces."/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Title / Headline</label>
<div class="col-sm-10">
<input type="text" value={this.state.communityForm.title} onInput={linkEvent(this, this.handleCommunityTitleChange)} class="form-control" required minLength={3} />
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Description / Sidebar</label>
<div class="col-sm-10">
<textarea value={this.state.communityForm.description} onInput={linkEvent(this, this.handleCommunityDescriptionChange)} class="form-control" rows={6} />
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Category</label>
<div class="col-sm-10">
<select class="form-control" value={this.state.communityForm.category_id} onInput={linkEvent(this, this.handleCommunityCategoryChange)}>
{this.state.categories.map(category =>
<option value={category.id}>{category.name}</option>
)}
</select>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-secondary">{this.props.community ? 'Edit' : 'Create'} Community</button>
</div>
</div>
</form>
);
}
handleCreateCommunitySubmit(i: CommunityForm, event) {
event.preventDefault();
if (i.props.community) {
WebSocketService.Instance.editCommunity(i.state.communityForm);
} else {
WebSocketService.Instance.createCommunity(i.state.communityForm);
}
}
handleCommunityNameChange(i: CommunityForm, event) {
i.state.communityForm.name = event.target.value;
i.setState(i.state);
}
handleCommunityTitleChange(i: CommunityForm, event) {
i.state.communityForm.title = event.target.value;
i.setState(i.state);
}
handleCommunityDescriptionChange(i: CommunityForm, event) {
i.state.communityForm.description = event.target.value;
i.setState(i.state);
}
handleCommunityCategoryChange(i: CommunityForm, event) {
i.state.communityForm.category_id = Number(event.target.value);
i.setState(i.state);
}
parseMessage(msg: any) {
let op: UserOperation = msgOp(msg);
console.log(msg);
if (msg.error) {
alert(msg.error);
return;
} else if (op == UserOperation.ListCategories){
let res: ListCategoriesResponse = msg;
this.state.categories = res.categories;
this.state.communityForm.category_id = res.categories[0].id;
this.setState(this.state);
} else if (op == UserOperation.CreateCommunity) {
let res: CommunityResponse = msg;
this.props.onCreate(res.community.id);
} else if (op == UserOperation.EditCommunity) {
let res: CommunityResponse = msg;
this.props.onEdit(res.community);
}
}
}

8
src/components/community.tsx

@ -2,7 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Community as CommunityI, CommunityResponse, Post, GetPostsForm, ListingSortType, ListingType, GetPostsResponse, CreatePostLikeForm, CreatePostLikeResponse, CommunityUser} from '../interfaces';
import { UserOperation, Community as CommunityI, GetCommunityResponse, CommunityResponse, Post, GetPostsForm, ListingSortType, ListingType, GetPostsResponse, CreatePostLikeForm, CreatePostLikeResponse, CommunityUser} from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { MomentTime } from './moment-time';
import { PostListing } from './post-listing';
@ -127,7 +127,7 @@ export class Community extends Component<any, State> {
alert(msg.error);
return;
} else if (op == UserOperation.GetCommunity) {
let res: CommunityResponse = msg;
let res: GetCommunityResponse = msg;
this.state.community = res.community;
this.state.moderators = res.moderators;
this.setState(this.state);
@ -143,6 +143,10 @@ export class Community extends Component<any, State> {
found.upvotes = res.post.upvotes;
found.downvotes = res.post.downvotes;
this.setState(this.state);
} else if (op == UserOperation.EditCommunity) {
let res: CommunityResponse = msg;
this.state.community = res.community;
this.setState(this.state);
}
}
}

131
src/components/create-community.tsx

@ -1,47 +1,11 @@
import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { CommunityForm, UserOperation, Category, ListCategoriesResponse } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { msgOp } from '../utils';
import { CommunityForm } from './community-form';
import { Community } from '../interfaces';
interface State {
communityForm: CommunityForm;
categories: Array<Category>;
}
export class CreateCommunity extends Component<any, State> {
private subscription: Subscription;
private emptyState: State = {
communityForm: {
name: null,
title: null,
category_id: null
},
categories: []
}
export class CreateCommunity extends Component<any, any> {
constructor(props, context) {
super(props, context);
this.state = this.emptyState;
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")
);
WebSocketService.Instance.listCategories();
}
componentWillUnmount() {
this.subscription.unsubscribe();
this.handleCommunityCreate = this.handleCommunityCreate.bind(this);
}
render() {
@ -49,96 +13,17 @@ export class CreateCommunity extends Component<any, State> {
<div class="container">
<div class="row">
<div class="col-12 col-lg-6 mb-4">
{this.communityForm()}
<h3>Create Forum</h3>
<CommunityForm onCreate={this.handleCommunityCreate}/>
</div>
</div>
</div>
)
}
communityForm() {
return (
<div>
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
<h3>Create Forum</h3>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} pattern="[a-z0-9_]+" title="lowercase, underscores, and no spaces."/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Title / Headline</label>
<div class="col-sm-10">
<input type="text" value={this.state.communityForm.title} onInput={linkEvent(this, this.handleCommunityTitleChange)} class="form-control" required minLength={3} />
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Description / Sidebar</label>
<div class="col-sm-10">
<textarea value={this.state.communityForm.description} onInput={linkEvent(this, this.handleCommunityDescriptionChange)} class="form-control" rows={6} />
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Category</label>
<div class="col-sm-10">
<select class="form-control" value={this.state.communityForm.category_id} onInput={linkEvent(this, this.handleCommunityCategoryChange)}>
{this.state.categories.map(category =>
<option value={category.id}>{category.name}</option>
)}
</select>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-secondary">Create</button>
</div>
</div>
</form>
</div>
);
}
handleCreateCommunitySubmit(i: CreateCommunity, event) {
event.preventDefault();
WebSocketService.Instance.createCommunity(i.state.communityForm);
}
handleCommunityNameChange(i: CreateCommunity, event) {
i.state.communityForm.name = event.target.value;
i.setState(i.state);
}
handleCommunityTitleChange(i: CreateCommunity, event) {
i.state.communityForm.title = event.target.value;
i.setState(i.state);
}
handleCommunityDescriptionChange(i: CreateCommunity, event) {
i.state.communityForm.description = event.target.value;
i.setState(i.state);
}
handleCommunityCategoryChange(i: CreateCommunity, event) {
i.state.communityForm.category_id = Number(event.target.value);
i.setState(i.state);
handleCommunityCreate(id: number) {
this.props.history.push(`/community/${id}`);
}
}
parseMessage(msg: any) {
let op: UserOperation = msgOp(msg);
console.log(msg);
if (msg.error) {
alert(msg.error);
return;
} else if (op == UserOperation.ListCategories){
let res: ListCategoriesResponse = msg;
this.state.categories = res.categories;
this.state.communityForm.category_id = res.categories[0].id;
this.setState(this.state);
} else if (op == UserOperation.CreateCommunity) {
let community: Community = msg.community;
this.props.history.push(`/community/${community.id}`);
}
}
}

1
src/components/post-form.tsx

@ -105,7 +105,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
handlePostSubmit(i: PostForm, event) {
event.preventDefault();
console.log(i.state.postForm);
if (i.props.post) {
WebSocketService.Instance.editPost(i.state.postForm);
} else {

21
src/components/post-listing.tsx

@ -34,15 +34,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleEditPost = this.handleEditPost.bind(this);
}
render() {
return (
<div>
{!this.state.showEdit
? this.listing()
: <PostForm post={this.props.post} onEdit={this.handleEditPost} />
}
</div>
)
render() {
return (
<div>
{!this.state.showEdit
? this.listing()
: <PostForm post={this.props.post} onEdit={this.handleEditPost} />
}
</div>
)
}
listing() {
@ -90,7 +90,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<Link to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link>
</li>
</ul>
{this.myPost &&
{this.myPost &&
<ul class="list-inline mb-1 text-muted small font-weight-bold">
<li className="list-inline-item">
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
@ -132,6 +132,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
i.setState(i.state);
}
// The actual editing is done in the recieve for post
handleEditPost(post: Post) {
this.state.showEdit = false;
this.setState(this.state);

8
src/components/post.tsx

@ -2,7 +2,7 @@ import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CommentSortType, CreatePostLikeResponse, CommunityUser } from '../interfaces';
import { UserOperation, Community, Post as PostI, GetPostResponse, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CommentSortType, CreatePostLikeResponse, CommunityUser, CommunityResponse } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { msgOp, hotRank,mdToHtml } from '../utils';
import { MomentTime } from './moment-time';
@ -223,6 +223,12 @@ export class Post extends Component<any, PostState> {
let res: PostResponse = msg;
this.state.post = res.post;
this.setState(this.state);
} else if (op == UserOperation.EditCommunity) {
let res: CommunityResponse = msg;
this.state.community = res.community;
this.state.post.community_id = res.community.id;
this.state.post.community_name = res.community.name;
this.setState(this.state);
}
}

58
src/components/sidebar.tsx

@ -1,7 +1,9 @@
import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router';
import { Community, CommunityUser } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { mdToHtml } from '../utils';
import { CommunityForm } from './community-form';
interface SidebarProps {
community: Community;
@ -9,20 +11,49 @@ interface SidebarProps {
}
interface SidebarState {
showEdit: boolean;
}
export class Sidebar extends Component<SidebarProps, SidebarState> {
private emptyState: SidebarState = {
showEdit: false
}
constructor(props, context) {
super(props, context);
this.state = this.emptyState;
this.handleEditCommunity = this.handleEditCommunity.bind(this);
}
render() {
return (
<div>
{!this.state.showEdit
? this.sidebar()
: <CommunityForm community={this.props.community} onEdit={this.handleEditCommunity} />
}
</div>
)
}
sidebar() {
let community = this.props.community;
return (
<div>
<h4>{community.title}</h4>
{this.amMod &&
<ul class="list-inline mb-1 text-muted small font-weight-bold">
<li className="list-inline-item">
<span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
</li>
{this.amCreator &&
<li className="list-inline-item">
{/* <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> */}
</li>
}
</ul>
}
<ul class="list-inline">
<li className="list-inline-item"><Link className="badge badge-light" to="/communities">{community.category_name}</Link></li>
<li className="list-inline-item badge badge-light">{community.number_of_subscribers} Subscribers</li>
@ -44,4 +75,29 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
</div>
);
}
handleEditClick(i: Sidebar, event) {
i.state.showEdit = true;
i.setState(i.state);
}
handleEditCommunity(community: Community) {
this.state.showEdit = false;
this.setState(this.state);
}
// TODO no deleting communities yet
handleDeleteClick(i: Sidebar, event) {
}
private get amCreator(): boolean {
return UserService.Instance.loggedIn && this.props.community.creator_id == UserService.Instance.user.id;
}
private get amMod(): boolean {
console.log(this.props.moderators);
console.log(this.props);
return UserService.Instance.loggedIn &&
this.props.moderators.map(m => m.user_id).includes(UserService.Instance.user.id);
}
}

9
src/interfaces.ts

@ -38,15 +38,22 @@ export interface CommunityForm {
title: string;
description?: string,
category_id: number,
edit_id?: number;
auth?: string;
}
export interface CommunityResponse {
export interface GetCommunityResponse {
op: string;
community: Community;
moderators: Array<CommunityUser>;
}
export interface CommunityResponse {
op: string;
community: Community;
}
export interface ListCommunitiesResponse {
op: string;
communities: Array<Community>;

9
src/services/WebSocketService.ts

@ -1,5 +1,5 @@
import { wsUri } from '../env';
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetListingsForm, CreatePostLikeForm } from '../interfaces';
import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
@ -37,6 +37,11 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.CreateCommunity, communityForm));
}
public editCommunity(communityForm: CommunityForm) {
this.setAuth(communityForm);
this.subject.next(this.wsSendWrapper(UserOperation.EditCommunity, communityForm));
}
public listCommunities() {
this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, undefined));
}
@ -74,7 +79,7 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.CreateCommentLike, form));
}
public getPosts(form: GetListingsForm) {
public getPosts(form: GetPostsForm) {
this.setAuth(form, false);
this.subject.next(this.wsSendWrapper(UserOperation.GetPosts, form));
}

2
tsconfig.json

@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"target": "es2016",
"sourceMap": true,
"inlineSources": true,
"jsx": "preserve",

Loading…
Cancel
Save