Merge branch 'comments' of Asara/sudoscientist-js-frontend into master
This commit is contained in:
commit
b53cd23708
7 changed files with 184 additions and 85 deletions
1
TODO.md
1
TODO.md
|
@ -2,4 +2,3 @@
|
||||||
|
|
||||||
1. Fix up UX, return errors to screen instead of doing nothing.
|
1. Fix up UX, return errors to screen instead of doing nothing.
|
||||||
2. Add filtering posts by tags
|
2. Add filtering posts by tags
|
||||||
3. Comments
|
|
||||||
|
|
|
@ -7,8 +7,11 @@ export const fetchPosts = () => async (dispatch) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchPost = (slug) => async (dispatch) => {
|
export const fetchPost = (slug) => async (dispatch) => {
|
||||||
const response = await sudoscientist.get('/blog/posts/by-slug/' + slug);
|
const post = await sudoscientist.get('/blog/posts/by-slug/' + slug)
|
||||||
dispatch({ type: 'FETCH_POST', payload: response.data })
|
const comments = await sudoscientist.get('/blog/comments/' + post.data.id);
|
||||||
|
const response = { post: post.data }
|
||||||
|
response.post.comments = comments.data
|
||||||
|
dispatch({ type: 'FETCH_POST', payload: response })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,3 +64,10 @@ export const newBlogPost = (payload) => async (dispatch) => {
|
||||||
history.push('/posts/' + response.data.slug)
|
history.push('/posts/' + response.data.slug)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const newComment = (payload, parent_id) => async (dispatch) => {
|
||||||
|
const response = await sudoscientist.post('/blog/comments/' + parent_id, payload)
|
||||||
|
if (response.status === 201) {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
49
src/components/Comments.js
Normal file
49
src/components/Comments.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactMde from "react-mde";
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import 'github-markdown-css'
|
||||||
|
import "react-mde/lib/styles/css/react-mde-all.css"
|
||||||
|
import { newComment } from '../actions';
|
||||||
|
|
||||||
|
const Comment = (props) => {
|
||||||
|
const [content, setContent] = React.useState("");
|
||||||
|
const [selectedTab, setSelectedTab] = React.useState("write");
|
||||||
|
const submitComment = () => {
|
||||||
|
const payload = {
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
props.newComment(payload, props.post.currentId)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="comment">
|
||||||
|
<h3><b>Leave a comment!</b></h3>
|
||||||
|
</div>
|
||||||
|
<div className="markdown-body">
|
||||||
|
<ReactMde
|
||||||
|
value={content}
|
||||||
|
onChange={setContent}
|
||||||
|
selectedTab={selectedTab}
|
||||||
|
onTabChange={setSelectedTab}
|
||||||
|
generateMarkdownPreview={(markdown) =>
|
||||||
|
Promise.resolve(<ReactMarkdown source={markdown} />)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={submitComment}>Submit Comment</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
post: state.posts,
|
||||||
|
user: state.auth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
{ newComment },
|
||||||
|
)(Comment);
|
|
@ -2,57 +2,96 @@ import React from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import 'github-markdown-css'
|
import 'github-markdown-css'
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { fetchPost } from '../actions';
|
import { fetchPost,newComment } from '../actions';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import Comment from './Comments';
|
||||||
|
|
||||||
class Post extends React.Component {
|
class Post extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: true
|
isLoading: true
|
||||||
}
|
|
||||||
}
|
|
||||||
componentDidMount() {
|
|
||||||
const { slug } = this.props.match.params
|
|
||||||
this.props.fetchPost(slug)
|
|
||||||
.then(() => this.setState({isLoading: false}))
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
componentDidMount() {
|
||||||
const {posts} = this.props
|
const { slug } = this.props.match.params
|
||||||
const {isLoading} = this.state
|
this.props.fetchPost(slug)
|
||||||
|
.then(() => this.setState({isLoading: false, slug: slug}))
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
renderComments(comments, parent_id) {
|
||||||
return <p>Loading</p>
|
if (comments) {
|
||||||
} else {
|
return comments.map(comment => {
|
||||||
const post = posts.entities[posts.currentId]
|
return (
|
||||||
return (
|
<div key={comment.id}>
|
||||||
<div className="item" key={post.id}>
|
<div className="item" style={{outlineOffset: 2, outline: '1px solid', outlineColor: '#DADADA'}}>
|
||||||
<div className="content">
|
<h5><b>Comment by <Link to={"/users/"+ comment.author}>{comment.author}</Link></b><br></br>
|
||||||
<div className="description">
|
Posted: {comment.time_published}
|
||||||
<h1><b><u><Link to={"/posts/" + post.slug}>{post.title}</Link></u></b></h1>
|
</h5>
|
||||||
<h3><b>By <Link to={"/users/"+ post.author}>{post.author}</Link></b></h3>
|
<hr></hr>
|
||||||
<h4>Posted {post.time_published}</h4>
|
<div className="content">
|
||||||
<div className="markdown-body">
|
<div className="markdown-body">
|
||||||
<ReactMarkdown source={post.content} />
|
<ReactMarkdown source={comment.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
<br></br>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPost() {
|
||||||
|
const {posts} = this.props
|
||||||
|
const post = posts.entities[posts.currentId]
|
||||||
|
return(
|
||||||
|
<div className="container">
|
||||||
|
<div className="item" key={post.id}>
|
||||||
|
<div className="content">
|
||||||
|
<div className="description">
|
||||||
|
<h4><b><u><Link to={"/posts/" + post.slug}>{post.title}</Link></u></b></h4>
|
||||||
|
<h3><b>By <Link to={"/users/"+ post.author}>{post.author}</Link></b></h3>
|
||||||
|
<h4>Posted {post.time_published}</h4>
|
||||||
|
<div className="markdown-body">
|
||||||
|
<ReactMarkdown source={post.content} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{ this.props.auth.verified &&
|
||||||
|
<Comment></Comment>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<h3>Comments:</h3>
|
||||||
|
{ this.renderComments(post.comments,post.id) }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {isLoading} = this.state
|
||||||
|
if (isLoading) {
|
||||||
|
return <p>Loading</p>
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ this.renderPost() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return {
|
return {
|
||||||
posts: state.posts,
|
posts: state.posts,
|
||||||
slug: state.slug,
|
slug: state.slug,
|
||||||
comments: state.comments
|
auth: state.auth
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
{ fetchPost }
|
{ fetchPost, newComment },
|
||||||
)(Post);
|
)(Post);
|
||||||
|
|
|
@ -7,52 +7,52 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
|
||||||
class PostList extends React.Component {
|
class PostList extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchPosts();
|
this.props.fetchPosts();
|
||||||
}
|
}
|
||||||
renderList() {
|
renderList() {
|
||||||
const {posts} = this.props
|
const {posts} = this.props
|
||||||
const postKeys = Object.keys(posts.entities)
|
const postKeys = Object.keys(posts.entities)
|
||||||
return postKeys.map(id => {
|
return postKeys.map(id => {
|
||||||
const post = posts.entities[id]
|
const post = posts.entities[id]
|
||||||
return (
|
return (
|
||||||
<div className="item" key={post.id}>
|
<div className="item" key={post.id}>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="description">
|
<div className="description">
|
||||||
<h1><b><u><Link to={"/posts/" + post.slug}>{post.title}</Link></u></b></h1>
|
<h1><b><u><Link to={"/posts/" + post.slug}>{post.title}</Link></u></b></h1>
|
||||||
<h3><b>By <Link to={"/users/"+ post.author}>{post.author}</Link></b></h3>
|
<h3><b>By <Link to={"/users/"+ post.author}>{post.author}</Link></b></h3>
|
||||||
<h4>Posted {post.time_published}</h4>
|
<h4>Posted {post.time_published}</h4>
|
||||||
<div className="markdown-body">
|
<div className="markdown-body">
|
||||||
<ReactMarkdown source={post.content} />
|
<ReactMarkdown source={post.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}).reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Blog Posts</h1>
|
|
||||||
<div className="ui relaxed divided list">
|
|
||||||
{this.renderList()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
</div>
|
||||||
|
);
|
||||||
|
}).reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Blog Posts</h1>
|
||||||
|
<div className="ui relaxed divided list">
|
||||||
|
{this.renderList()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return {
|
return {
|
||||||
posts: state.posts,
|
posts: state.posts,
|
||||||
slug: state.slug
|
slug: state.slug
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
{ fetchPosts }
|
{ fetchPosts }
|
||||||
)(PostList);
|
)(PostList);
|
||||||
|
|
|
@ -17,8 +17,8 @@ export default (state = initialState, action) => {
|
||||||
return {...state, ...{entities: mergedEntities}}
|
return {...state, ...{entities: mergedEntities}}
|
||||||
|
|
||||||
case 'FETCH_POST':
|
case 'FETCH_POST':
|
||||||
mergedEntities = normalizeEntities(state.entities, [action.payload])
|
mergedEntities = normalizeEntities(state.entities, [action.payload.post])
|
||||||
return {...state, ...{entities: mergedEntities, currentId: action.payload.id}}
|
return {...state, ...{entities: mergedEntities, currentId: action.payload.post.id}}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ The frontend for this blog is written using react-redux and can be [found here](
|
||||||
### Communication
|
### Communication
|
||||||
Matrix: `@Asara:devvul.com`
|
Matrix: `@Asara:devvul.com`
|
||||||
|
|
||||||
|
Fediverse: https://social.devvul.com/Asara
|
||||||
|
|
||||||
Email: `amarpreet@minhas.io`
|
Email: `amarpreet@minhas.io`
|
||||||
|
|
||||||
### Lightning Network
|
### Lightning Network
|
||||||
|
|
Reference in a new issue