diff options
author | Dessalines <tyhou13@gmx.com> | 2019-04-02 23:49:32 -0700 |
---|---|---|
committer | Dessalines <tyhou13@gmx.com> | 2019-04-02 23:49:32 -0700 |
commit | c7864643812645ecfb560154bcb1e758555126de (patch) | |
tree | 26a8754f8ca377efeff8446c2f660ed3d4fdfd50 /ui/src | |
parent | 923110c7d4c94f9c5aab329bc8c80d59a7e32007 (diff) |
Adding forum / community pages
- Adding main forum page. Fixes #11
- Adding view version for posts. #21
- Got rid of fedi user ids. Fixes #22
- Post sorting working. Fixes #24
Diffstat (limited to 'ui/src')
-rw-r--r-- | ui/src/components/community.tsx | 82 | ||||
-rw-r--r-- | ui/src/components/navbar.tsx | 5 | ||||
-rw-r--r-- | ui/src/components/post-listing.tsx | 104 | ||||
-rw-r--r-- | ui/src/components/post.tsx | 67 | ||||
-rw-r--r-- | ui/src/interfaces.ts | 47 | ||||
-rw-r--r-- | ui/src/main.css | 4 | ||||
-rw-r--r-- | ui/src/services/WebSocketService.ts | 16 |
7 files changed, 272 insertions, 53 deletions
diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index b0322635..5bef29bb 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -1,13 +1,17 @@ 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 } from '../interfaces'; +import { UserOperation, Community as CommunityI, CommunityResponse, Post, GetPostsForm, ListingSortType, ListingType, GetPostsResponse, CreatePostLikeForm, CreatePostLikeResponse} from '../interfaces'; import { WebSocketService, UserService } from '../services'; +import { MomentTime } from './moment-time'; +import { PostListing } from './post-listing'; import { msgOp } from '../utils'; interface State { community: CommunityI; posts: Array<Post>; + sortType: ListingSortType; } export class Community extends Component<any, State> { @@ -19,7 +23,8 @@ export class Community extends Component<any, State> { name: null, published: null }, - posts: [] + posts: [], + sortType: ListingSortType.Hot, } constructor(props, context) { @@ -27,8 +32,6 @@ export class Community extends Component<any, State> { this.state = this.emptyState; - console.log(this.props.match.params.id); - this.subscription = WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) .subscribe( @@ -39,6 +42,14 @@ export class Community extends Component<any, State> { let communityId = Number(this.props.match.params.id); WebSocketService.Instance.getCommunity(communityId); + + let getPostsForm: GetPostsForm = { + community_id: communityId, + limit: 10, + sort: ListingSortType[ListingSortType.Hot], + type_: ListingType[ListingType.Community] + } + WebSocketService.Instance.getPosts(getPostsForm); } componentWillUnmount() { @@ -49,14 +60,57 @@ export class Community extends Component<any, State> { return ( <div class="container"> <div class="row"> - <div class="col-12 col-lg-6 mb-4"> - {this.state.community.name} + <div class="col-12 col-sm-10 col-lg-9"> + <h4>/f/{this.state.community.name}</h4> + <div>{this.selects()}</div> + {this.state.posts.length > 0 + ? this.state.posts.map(post => + <PostListing post={post} />) + : <div>no listings</div> + } </div> + <div class="col-12 col-sm-2 col-lg-3"> + Sidebar + </div> + + </div> </div> ) } + selects() { + return ( + <div className="mb-2"> + <select value={this.state.sortType} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto"> + <option disabled>Sort Type</option> + <option value={ListingSortType.Hot}>Hot</option> + <option value={ListingSortType.New}>New</option> + <option disabled>──────────</option> + <option value={ListingSortType.TopDay}>Top Day</option> + <option value={ListingSortType.TopWeek}>Week</option> + <option value={ListingSortType.TopMonth}>Month</option> + <option value={ListingSortType.TopYear}>Year</option> + <option value={ListingSortType.TopAll}>All</option> + </select> + </div> + ) + + } + + handleSortChange(i: Community, event) { + i.state.sortType = Number(event.target.value); + i.setState(i.state); + + let getPostsForm: GetPostsForm = { + community_id: i.state.community.id, + limit: 10, + sort: ListingSortType[i.state.sortType], + type_: ListingType[ListingType.Community] + } + WebSocketService.Instance.getPosts(getPostsForm); + } + parseMessage(msg: any) { console.log(msg); let op: UserOperation = msgOp(msg); @@ -67,6 +121,20 @@ export class Community extends Component<any, State> { let res: CommunityResponse = msg; this.state.community = res.community; this.setState(this.state); - } + } else if (op == UserOperation.GetPosts) { + let res: GetPostsResponse = msg; + this.state.posts = res.posts; + this.setState(this.state); + } else if (op == UserOperation.CreatePostLike) { + let res: CreatePostLikeResponse = msg; + let found = this.state.posts.find(c => c.id == res.post.id); + found.my_vote = res.post.my_vote; + found.score = res.post.score; + found.upvotes = res.post.upvotes; + found.downvotes = res.post.downvotes; + this.setState(this.state); + } } } + + diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index ae2d90b3..1af592b4 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -34,7 +34,10 @@ export class Navbar extends Component<any, any> { <div class="collapse navbar-collapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> - <a class="nav-link" href={repoUrl}>github</a> + <a class="nav-link" href={repoUrl}>About</a> + </li> + <li class="nav-item"> + <a class="nav-link" href={repoUrl}>Forums</a> </li> <li class="nav-item"> <Link class="nav-link" to="/create_post">Create Post</Link> diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx new file mode 100644 index 00000000..861f0f15 --- /dev/null +++ b/ui/src/components/post-listing.tsx @@ -0,0 +1,104 @@ +import { Component, linkEvent } from 'inferno'; +import { Link } from 'inferno-router'; +import { Subscription } from "rxjs"; +import { retryWhen, delay, take } from 'rxjs/operators'; +import { WebSocketService, UserService } from '../services'; +import { Post, CreatePostLikeResponse, CreatePostLikeForm } from '../interfaces'; +import { MomentTime } from './moment-time'; +import { mdToHtml } from '../utils'; + +interface PostListingState { +} + +interface PostListingProps { + post: Post; + showCommunity?: boolean; + showBody?: boolean; +} + +export class PostListing extends Component<PostListingProps, PostListingState> { + + private emptyState: PostListingState = { + } + + constructor(props, context) { + super(props, context); + + this.state = this.emptyState; + this.handlePostLike = this.handlePostLike.bind(this); + this.handlePostDisLike = this.handlePostDisLike.bind(this); + } + + render() { + let post = this.props.post; + return ( + <div class="listing"> + <div className="float-left small text-center"> + <div className={`pointer upvote ${post.my_vote == 1 ? 'text-info' : 'text-muted'}`} onClick={linkEvent(this, this.handlePostLike)}>▲</div> + <div>{post.score}</div> + <div className={`pointer downvote ${post.my_vote == -1 && 'text-danger'}`} onClick={linkEvent(this, this.handlePostDisLike)}>▼</div> + </div> + <div className="ml-4"> + {post.url + ? <h5 className="mb-0"> + <a className="text-white" href={post.url}>{post.name}</a> + <small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small> + </h5> + : <h5 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link></h5> + } + </div> + <div className="details ml-4 mb-1"> + <ul class="list-inline mb-0 text-muted small"> + <li className="list-inline-item"> + <span>by </span> + <a href={post.creator_id.toString()}>{post.creator_name}</a> + {this.props.showCommunity && + <span> + <span> to </span> + <Link to={`/community/${post.community_id}`}>{post.community_name}</Link> + </span> + } + </li> + <li className="list-inline-item"> + <span><MomentTime data={post} /></span> + </li> + <li className="list-inline-item"> + <span>( + <span className="text-info">+{post.upvotes}</span> + <span> | </span> + <span className="text-danger">-{post.downvotes}</span> + <span>) </span> + </span> + </li> + <li className="list-inline-item"> + <Link to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link> + </li> + </ul> + {this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />} + </div> + </div> + ) + } + + // private get myPost(): boolean { + // return this.props.node.comment.attributed_to == UserService.Instance.fediUserId; + // } + + handlePostLike(i: PostListing, event) { + + let form: CreatePostLikeForm = { + post_id: i.props.post.id, + score: (i.props.post.my_vote == 1) ? 0 : 1 + }; + WebSocketService.Instance.likePost(form); + } + + handlePostDisLike(i: PostListing, event) { + let form: CreatePostLikeForm = { + post_id: i.props.post.id, + score: (i.props.post.my_vote == -1) ? 0 : -1 + }; + WebSocketService.Instance.likePost(form); + } +} + diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index adcc8616..914eebb5 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -1,10 +1,11 @@ import { Component, linkEvent } from 'inferno'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; -import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse, CommentSortType } from '../interfaces'; +import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse, CommentLikeForm, CreateCommentLikeResponse, CommentSortType, CreatePostLikeResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { msgOp, hotRank,mdToHtml } from '../utils'; import { MomentTime } from './moment-time'; +import { PostListing } from './post-listing'; import * as autosize from 'autosize'; interface CommentNodeI { @@ -22,13 +23,7 @@ export class Post extends Component<any, State> { private subscription: Subscription; private emptyState: State = { - post: { - name: null, - attributed_to: null, - community_id: null, - id: null, - published: null, - }, + post: null, comments: [], commentSort: CommentSortType.Hot } @@ -38,7 +33,7 @@ export class Post extends Component<any, State> { this.state = this.emptyState; - this.state.post.id = Number(this.props.match.params.id); + let postId = Number(this.props.match.params.id); this.subscription = WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) @@ -48,7 +43,7 @@ export class Post extends Component<any, State> { () => console.log('complete') ); - WebSocketService.Instance.getPost(this.state.post.id); + WebSocketService.Instance.getPost(postId); } componentWillUnmount() { @@ -62,36 +57,23 @@ export class Post extends Component<any, State> { render() { return ( <div class="container"> - <div class="row"> - <div class="col-12 col-sm-8 col-lg-7 mb-3"> - {this.postHeader()} - <CommentForm postId={this.state.post.id} /> - {this.sortRadios()} - {this.commentsTree()} - </div> - <div class="col-12 col-sm-4 col-lg-3 mb-3"> - {this.newComments()} - </div> - <div class="col-12 col-sm-12 col-lg-2"> - {this.sidebar()} + {this.state.post && + <div class="row"> + <div class="col-12 col-sm-8 col-lg-7 mb-3"> + <PostListing post={this.state.post} showBody showCommunity /> + <div className="mb-2" /> + <CommentForm postId={this.state.post.id} /> + {this.sortRadios()} + {this.commentsTree()} + </div> + <div class="col-12 col-sm-4 col-lg-3 mb-3"> + {this.state.comments.length > 0 && this.newComments()} + </div> + <div class="col-12 col-sm-12 col-lg-2"> + {this.sidebar()} + </div> </div> - </div> - </div> - ) - } - - postHeader() { - let title = this.state.post.url - ? <h5> - <a href={this.state.post.url}>{this.state.post.name}</a> - <small><a className="ml-2 text-muted font-italic" href={this.state.post.url}>{(new URL(this.state.post.url)).hostname}</a></small> - </h5> - : <h5>{this.state.post.name}</h5>; - return ( - <div> - <div>{title}</div> - <div>via {this.state.post.attributed_to} <MomentTime data={this.state.post} /></div> - <div>{this.state.post.body}</div> + } </div> ) } @@ -223,6 +205,13 @@ export class Post extends Component<any, State> { if (res.comment.my_vote !== null) found.my_vote = res.comment.my_vote; this.setState(this.state); + } else if (op == UserOperation.CreatePostLike) { + let res: CreatePostLikeResponse = msg; + this.state.post.my_vote = res.post.my_vote; + this.state.post.score = res.post.score; + this.state.post.upvotes = res.post.upvotes; + this.state.post.downvotes = res.post.downvotes; + this.setState(this.state); } } diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 95ea5e09..56d860bd 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -1,5 +1,5 @@ export enum UserOperation { - Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike + Login, Register, CreateCommunity, CreatePost, ListCommunities, GetPost, GetCommunity, CreateComment, EditComment, CreateCommentLike, GetPosts, CreatePostLike } export interface User { @@ -31,12 +31,21 @@ export interface ListCommunitiesResponse { } export interface Post { + user_id?: number; + my_vote?: number; id: number; name: string; url?: string; body?: string; - attributed_to: string; + creator_id: number; + creator_name: string; community_id: number; + community_name: string; + number_of_comments: number; + score: number; + upvotes: number; + downvotes: number; + hot_rank: number; published: string; updated?: string; } @@ -59,7 +68,7 @@ export interface PostResponse { export interface Comment { id: number; content: string; - attributed_to: string; + creator_id: number; post_id: number, parent_id?: number; published: string; @@ -95,6 +104,30 @@ export interface CreateCommentLikeResponse { comment: Comment; } +export interface GetPostsForm { + type_: string; + sort: string; + limit: number; + community_id?: number; + auth?: string; +} + +export interface GetPostsResponse { + op: string; + posts: Array<Post>; +} + +export interface CreatePostLikeForm { + post_id: number; + score: number; + auth?: string; +} + +export interface CreatePostLikeResponse { + op: string; + post: Post; +} + export interface LoginForm { username_or_email: string; password: string; @@ -107,6 +140,7 @@ export interface RegisterForm { password_verify: string; } + export interface LoginResponse { op: string; jwt: string; @@ -116,4 +150,11 @@ export enum CommentSortType { Hot, Top, New } +export enum ListingType { + All, Subscribed, Community +} + +export enum ListingSortType { + Hot, New, TopDay, TopWeek, TopMonth, TopYear, TopAll +} diff --git a/ui/src/main.css b/ui/src/main.css index ab875041..9c13bb9d 100644 --- a/ui/src/main.css +++ b/ui/src/main.css @@ -32,3 +32,7 @@ body { max-width: 100%; height: auto; } + +.listing { + min-height: 61px; +} diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index 1ea207f4..04376ff8 100644 --- a/ui/src/services/WebSocketService.ts +++ b/ui/src/services/WebSocketService.ts @@ -1,5 +1,5 @@ import { wsUri } from '../env'; -import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm } from '../interfaces'; +import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetListingsForm, CreatePostLikeForm } from '../interfaces'; import { webSocket } from 'rxjs/webSocket'; import { Subject } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; @@ -70,15 +70,25 @@ export class WebSocketService { this.subject.next(this.wsSendWrapper(UserOperation.CreateCommentLike, form)); } + public getPosts(form: GetListingsForm) { + this.setAuth(form, false); + this.subject.next(this.wsSendWrapper(UserOperation.GetPosts, form)); + } + + public likePost(form: CreatePostLikeForm) { + this.setAuth(form); + this.subject.next(this.wsSendWrapper(UserOperation.CreatePostLike, form)); + } + private wsSendWrapper(op: UserOperation, data: any) { let send = { op: UserOperation[op], data: data }; console.log(send); return send; } - private setAuth(obj: any) { + private setAuth(obj: any, throwErr: boolean = true) { obj.auth = UserService.Instance.auth; - if (obj.auth == null) { + if (obj.auth == null && throwErr) { alert("Not logged in."); throw "Not logged in"; } |