summaryrefslogtreecommitdiffstats
path: root/ui/src
diff options
context:
space:
mode:
authorDessalines <tyhou13@gmx.com>2019-04-02 23:49:32 -0700
committerDessalines <tyhou13@gmx.com>2019-04-02 23:49:32 -0700
commitc7864643812645ecfb560154bcb1e758555126de (patch)
tree26a8754f8ca377efeff8446c2f660ed3d4fdfd50 /ui/src
parent923110c7d4c94f9c5aab329bc8c80d59a7e32007 (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.tsx82
-rw-r--r--ui/src/components/navbar.tsx5
-rw-r--r--ui/src/components/post-listing.tsx104
-rw-r--r--ui/src/components/post.tsx67
-rw-r--r--ui/src/interfaces.ts47
-rw-r--r--ui/src/main.css4
-rw-r--r--ui/src/services/WebSocketService.ts16
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";
}