mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 19:27:52 +01:00
Add edit and delete actions to posts and comments (#125)
This commit is contained in:
committed by
GitHub
parent
07ca2a304a
commit
bc15140512
@@ -4,12 +4,11 @@ import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import NewComment from './NewComment';
|
||||
import Separator from '../common/Separator';
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
|
||||
import { ReplyFormState } from '../../reducers/replyFormReducer';
|
||||
|
||||
import friendlyDate from '../../helpers/datetime';
|
||||
import CommentEditForm from './CommentEditForm';
|
||||
import CommentFooter from './CommentFooter';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
@@ -17,119 +16,147 @@ interface Props {
|
||||
isPostUpdate: boolean;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
replyForm: ReplyFormState;
|
||||
handleToggleCommentReply(): void;
|
||||
handleCommentReplyBodyChange(e: React.FormEvent): void;
|
||||
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
||||
|
||||
handleSubmitComment(body: string, parentId: number, isPostUpdate: boolean): void;
|
||||
handleUpdateComment(commentId: number, body: string, isPostUpdate: boolean, onSuccess: Function): void;
|
||||
handleDeleteComment(id: number): void;
|
||||
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
currentUserEmail: string;
|
||||
}
|
||||
|
||||
const Comment = ({
|
||||
id,
|
||||
body,
|
||||
isPostUpdate,
|
||||
userFullName,
|
||||
userEmail,
|
||||
updatedAt,
|
||||
interface State {
|
||||
editMode: boolean;
|
||||
}
|
||||
|
||||
replyForm,
|
||||
handleToggleCommentReply,
|
||||
handleCommentReplyBodyChange,
|
||||
handleToggleIsCommentUpdate,
|
||||
handleSubmitComment,
|
||||
class Comment extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
currentUserEmail,
|
||||
}: Props) => (
|
||||
<div className="comment">
|
||||
<div className="commentHeader">
|
||||
<Gravatar email={userEmail} size={28} className="gravatar" />
|
||||
<span className="commentAuthor">{userFullName}</span>
|
||||
{
|
||||
isPostUpdate ?
|
||||
<span className="postUpdateBadge">
|
||||
{I18n.t('post.comments.post_update_badge')}
|
||||
</span>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
this.state = {
|
||||
editMode: false,
|
||||
};
|
||||
|
||||
<ReactMarkdown
|
||||
className="commentBody"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{body}
|
||||
</ReactMarkdown>
|
||||
this.toggleEditMode = this.toggleEditMode.bind(this);
|
||||
this._handleUpdateComment = this._handleUpdateComment.bind(this);
|
||||
}
|
||||
|
||||
<div className="commentFooter">
|
||||
<a className="commentReplyButton commentLink" onClick={handleToggleCommentReply}>
|
||||
toggleEditMode() {
|
||||
this.setState({editMode: !this.state.editMode});
|
||||
}
|
||||
|
||||
_handleUpdateComment(body: string, isPostUpdate: boolean) {
|
||||
this.props.handleUpdateComment(
|
||||
this.props.id,
|
||||
body,
|
||||
isPostUpdate,
|
||||
this.toggleEditMode,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
body,
|
||||
isPostUpdate,
|
||||
userFullName,
|
||||
userEmail,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
|
||||
replyForm,
|
||||
handleToggleCommentReply,
|
||||
handleCommentReplyBodyChange,
|
||||
|
||||
handleSubmitComment,
|
||||
handleDeleteComment,
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
currentUserEmail,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="comment">
|
||||
<div className="commentHeader">
|
||||
<Gravatar email={userEmail} size={28} className="gravatar" />
|
||||
<span className="commentAuthor">{userFullName}</span>
|
||||
{
|
||||
isPostUpdate ?
|
||||
<span className="postUpdateBadge">
|
||||
{I18n.t('post.comments.post_update_badge')}
|
||||
</span>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
this.state.editMode ?
|
||||
<CommentEditForm
|
||||
id={id}
|
||||
initialBody={body}
|
||||
initialIsPostUpdate={isPostUpdate}
|
||||
|
||||
isPowerUser={isPowerUser}
|
||||
|
||||
handleUpdateComment={this._handleUpdateComment}
|
||||
toggleEditMode={this.toggleEditMode}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<ReactMarkdown
|
||||
className="commentBody"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{body}
|
||||
</ReactMarkdown>
|
||||
|
||||
<CommentFooter
|
||||
id={id}
|
||||
createdAt={createdAt}
|
||||
updatedAt={updatedAt}
|
||||
replyForm={replyForm}
|
||||
isPowerUser={isPowerUser}
|
||||
currentUserEmail={currentUserEmail}
|
||||
commentAuthorEmail={userEmail}
|
||||
|
||||
handleDeleteComment={handleDeleteComment}
|
||||
handleToggleCommentReply={handleToggleCommentReply}
|
||||
toggleEditMode={this.toggleEditMode}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{
|
||||
replyForm.isOpen ?
|
||||
I18n.t('common.buttons.cancel')
|
||||
:
|
||||
I18n.t('post.comments.reply_button')
|
||||
<NewComment
|
||||
body={replyForm.body}
|
||||
parentId={id}
|
||||
postUpdateFlagValue={replyForm.isPostUpdate}
|
||||
isSubmitting={replyForm.isSubmitting}
|
||||
error={replyForm.error}
|
||||
handleChange={handleCommentReplyBodyChange}
|
||||
handlePostUpdateFlag={() => null}
|
||||
handleSubmit={handleSubmitComment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userEmail={currentUserEmail}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</a>
|
||||
{
|
||||
isPowerUser ?
|
||||
<>
|
||||
<Separator />
|
||||
<a
|
||||
onClick={() => handleToggleIsCommentUpdate(id, isPostUpdate)}
|
||||
className="commentLink"
|
||||
>
|
||||
{ 'Post update: ' + (isPostUpdate ? 'yes' : 'no') }
|
||||
</a>
|
||||
<Separator />
|
||||
<a href={`/admin/comments/${id}/edit`} className="commentLink" data-turbolinks="false">
|
||||
{I18n.t('common.buttons.edit')}
|
||||
</a>
|
||||
<Separator />
|
||||
<a
|
||||
href={`/admin/comments/${id}`}
|
||||
className="commentLink"
|
||||
data-method="delete"
|
||||
data-confirm="Are you sure?"
|
||||
data-turbolinks="false">
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Separator />
|
||||
<MutedText>{friendlyDate(updatedAt)}</MutedText>
|
||||
</div>
|
||||
{
|
||||
replyForm.isOpen ?
|
||||
<NewComment
|
||||
body={replyForm.body}
|
||||
parentId={id}
|
||||
postUpdateFlagValue={replyForm.isPostUpdate}
|
||||
isSubmitting={replyForm.isSubmitting}
|
||||
error={replyForm.error}
|
||||
handleChange={handleCommentReplyBodyChange}
|
||||
handlePostUpdateFlag={() => null}
|
||||
handleSubmit={handleSubmitComment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userEmail={currentUserEmail}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
100
app/javascript/components/Comments/CommentEditForm.tsx
Normal file
100
app/javascript/components/Comments/CommentEditForm.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
import Button from '../common/Button';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
initialBody: string;
|
||||
initialIsPostUpdate: boolean;
|
||||
|
||||
isPowerUser: boolean;
|
||||
|
||||
handleUpdateComment(body: string, isPostUpdate: boolean): void;
|
||||
toggleEditMode(): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
body: string;
|
||||
isPostUpdate: boolean;
|
||||
}
|
||||
|
||||
class CommentEditForm extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
body: '',
|
||||
isPostUpdate: false,
|
||||
};
|
||||
|
||||
this.handleCommentBodyChange = this.handleCommentBodyChange.bind(this);
|
||||
this.handleCommentIsPostUpdateChange = this.handleCommentIsPostUpdateChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
body: this.props.initialBody,
|
||||
isPostUpdate: this.props.initialIsPostUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
handleCommentBodyChange(newCommentBody: string) {
|
||||
this.setState({ body: newCommentBody });
|
||||
}
|
||||
|
||||
handleCommentIsPostUpdateChange(newIsPostUpdate: boolean) {
|
||||
this.setState({ isPostUpdate: newIsPostUpdate });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, isPowerUser, handleUpdateComment, toggleEditMode } = this.props;
|
||||
const { body, isPostUpdate } = this.state;
|
||||
|
||||
return (
|
||||
<div className="editCommentForm">
|
||||
<textarea
|
||||
value={body}
|
||||
onChange={e => this.handleCommentBodyChange(e.target.value)}
|
||||
className="commentForm"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
{
|
||||
isPowerUser ?
|
||||
<>
|
||||
<input
|
||||
id={`isPostUpdateFlagComment${id}`}
|
||||
type="checkbox"
|
||||
onChange={e => this.handleCommentIsPostUpdateChange(e.target.checked)}
|
||||
checked={isPostUpdate || false}
|
||||
/>
|
||||
|
||||
<label htmlFor={`isPostUpdateFlagComment${id}`}>
|
||||
{I18n.t('post.new_comment.is_post_update')}
|
||||
</label>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a className="commentLink" onClick={toggleEditMode}>
|
||||
{ I18n.t('common.buttons.cancel') }
|
||||
</a>
|
||||
|
||||
<Button
|
||||
onClick={() => handleUpdateComment(body, isPostUpdate)}
|
||||
>
|
||||
{ I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CommentEditForm;
|
||||
78
app/javascript/components/Comments/CommentFooter.tsx
Normal file
78
app/javascript/components/Comments/CommentFooter.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import Separator from '../common/Separator';
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
import friendlyDate from '../../helpers/datetime';
|
||||
import { ReplyFormState } from '../../reducers/replyFormReducer';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
replyForm: ReplyFormState;
|
||||
isPowerUser: boolean;
|
||||
currentUserEmail: string;
|
||||
commentAuthorEmail: string;
|
||||
|
||||
handleDeleteComment(id: number): void;
|
||||
handleToggleCommentReply(): void;
|
||||
toggleEditMode(): void;
|
||||
}
|
||||
|
||||
const CommentFooter = ({
|
||||
id,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
replyForm,
|
||||
isPowerUser,
|
||||
currentUserEmail,
|
||||
commentAuthorEmail,
|
||||
|
||||
handleDeleteComment,
|
||||
handleToggleCommentReply,
|
||||
toggleEditMode,
|
||||
}: Props) => (
|
||||
<div className="commentFooter">
|
||||
<a className="commentReplyButton commentLink" onClick={handleToggleCommentReply}>
|
||||
{
|
||||
replyForm.isOpen ?
|
||||
I18n.t('common.buttons.cancel')
|
||||
:
|
||||
I18n.t('post.comments.reply_button')
|
||||
}
|
||||
</a>
|
||||
{
|
||||
isPowerUser || currentUserEmail === commentAuthorEmail ?
|
||||
<>
|
||||
<Separator />
|
||||
<a onClick={toggleEditMode} className="commentLink">
|
||||
{I18n.t('common.buttons.edit')}
|
||||
</a>
|
||||
|
||||
<Separator />
|
||||
<a
|
||||
onClick={() => confirm(I18n.t('common.confirmation')) && handleDeleteComment(id)}
|
||||
className="commentLink">
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Separator />
|
||||
<MutedText>{friendlyDate(createdAt)}</MutedText>
|
||||
|
||||
{
|
||||
createdAt !== updatedAt ?
|
||||
<>
|
||||
<Separator />
|
||||
<MutedText>{ I18n.t('common.edited').toLowerCase() }</MutedText>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CommentFooter;
|
||||
@@ -13,8 +13,10 @@ interface Props {
|
||||
|
||||
toggleCommentReply(commentId: number): void;
|
||||
setCommentReplyBody(commentId: number, body: string): void;
|
||||
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
||||
|
||||
handleSubmitComment(body: string, parentId: number, isPostUpdate: boolean): void;
|
||||
handleUpdateComment(commentId: number, body: string, isPostUpdate: boolean, onSuccess: Function): void;
|
||||
handleDeleteComment(id: number): void;
|
||||
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
@@ -29,8 +31,9 @@ const CommentList = ({
|
||||
|
||||
toggleCommentReply,
|
||||
setCommentReplyBody,
|
||||
handleToggleIsCommentUpdate,
|
||||
handleSubmitComment,
|
||||
handleUpdateComment,
|
||||
handleDeleteComment,
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
@@ -49,8 +52,11 @@ const CommentList = ({
|
||||
setCommentReplyBody(comment.id, (e.target as HTMLTextAreaElement).value)
|
||||
)
|
||||
}
|
||||
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
||||
|
||||
handleSubmitComment={handleSubmitComment}
|
||||
handleUpdateComment={handleUpdateComment}
|
||||
handleDeleteComment={handleDeleteComment}
|
||||
|
||||
{...comment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
@@ -66,8 +72,10 @@ const CommentList = ({
|
||||
|
||||
toggleCommentReply={toggleCommentReply}
|
||||
setCommentReplyBody={setCommentReplyBody}
|
||||
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
||||
|
||||
handleSubmitComment={handleSubmitComment}
|
||||
handleUpdateComment={handleUpdateComment}
|
||||
handleDeleteComment={handleDeleteComment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
|
||||
@@ -26,12 +26,7 @@ interface Props {
|
||||
toggleCommentReply(commentId: number): void;
|
||||
setCommentReplyBody(commentId: number, body: string): void;
|
||||
toggleCommentIsPostUpdateFlag(): void;
|
||||
toggleCommentIsPostUpdate(
|
||||
postId: number,
|
||||
commentId: number,
|
||||
currentIsPostUpdate: boolean,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
|
||||
submitComment(
|
||||
postId: number,
|
||||
body: string,
|
||||
@@ -39,6 +34,19 @@ interface Props {
|
||||
isPostUpdate: boolean,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
updateComment(
|
||||
postId: number,
|
||||
commentId: number,
|
||||
body: string,
|
||||
isPostUpdate: boolean,
|
||||
onSuccess: Function,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
deleteComment(
|
||||
postId: number,
|
||||
commentId: number,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
}
|
||||
|
||||
class CommentsP extends React.Component<Props> {
|
||||
@@ -46,15 +54,6 @@ class CommentsP extends React.Component<Props> {
|
||||
this.props.requestComments(this.props.postId);
|
||||
}
|
||||
|
||||
_handleToggleIsCommentUpdate = (commentId: number, currentIsPostUpdate: boolean) => {
|
||||
this.props.toggleCommentIsPostUpdate(
|
||||
this.props.postId,
|
||||
commentId,
|
||||
currentIsPostUpdate,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
_handleSubmitComment = (body: string, parentId: number, isPostUpdate: boolean) => {
|
||||
this.props.submitComment(
|
||||
this.props.postId,
|
||||
@@ -65,6 +64,25 @@ class CommentsP extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_handleUpdateComment = (commentId: number, body: string, isPostUpdate: boolean, onSuccess: Function) => {
|
||||
this.props.updateComment(
|
||||
this.props.postId,
|
||||
commentId,
|
||||
body,
|
||||
isPostUpdate,
|
||||
onSuccess,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
_handleDeleteComment = (commentId: number) => {
|
||||
this.props.deleteComment(
|
||||
this.props.postId,
|
||||
commentId,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isLoggedIn,
|
||||
@@ -118,8 +136,9 @@ class CommentsP extends React.Component<Props> {
|
||||
replyForms={replyForms}
|
||||
toggleCommentReply={toggleCommentReply}
|
||||
setCommentReplyBody={setCommentReplyBody}
|
||||
handleToggleIsCommentUpdate={this._handleToggleIsCommentUpdate}
|
||||
handleSubmitComment={this._handleSubmitComment}
|
||||
handleUpdateComment={this._handleUpdateComment}
|
||||
handleDeleteComment={this._handleDeleteComment}
|
||||
parentId={null}
|
||||
level={1}
|
||||
isLoggedIn={isLoggedIn}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import NewCommentUpdateSection from './NewCommentUpdateSection';
|
||||
|
||||
@@ -52,7 +52,7 @@ const NewComment = ({
|
||||
value={body}
|
||||
onChange={handleChange}
|
||||
placeholder={I18n.t('post.new_comment.body_placeholder')}
|
||||
className="newCommentBody"
|
||||
className="commentForm"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleSubmit(body, parentId, postUpdateFlagValue)}
|
||||
|
||||
107
app/javascript/components/Post/PostEditForm.tsx
Normal file
107
app/javascript/components/Post/PostEditForm.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import PostBoardSelect from './PostBoardSelect';
|
||||
import PostStatusSelect from './PostStatusSelect';
|
||||
|
||||
import IPostStatus from '../../interfaces/IPostStatus';
|
||||
import IBoard from '../../interfaces/IBoard';
|
||||
import Button from '../common/Button';
|
||||
import Spinner from '../common/Spinner';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
boardId: number;
|
||||
postStatusId?: number;
|
||||
|
||||
isUpdating: boolean;
|
||||
error: string;
|
||||
|
||||
handleChangeTitle(title: string): void;
|
||||
handleChangeDescription(description: string): void;
|
||||
handleChangeBoard(boardId: number): void;
|
||||
handleChangePostStatus(postStatusId: number): void;
|
||||
|
||||
isPowerUser: boolean;
|
||||
boards: Array<IBoard>;
|
||||
postStatuses: Array<IPostStatus>;
|
||||
|
||||
toggleEditMode(): void;
|
||||
handleUpdatePost(
|
||||
title: string,
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
): void;
|
||||
}
|
||||
|
||||
const PostEditForm = ({
|
||||
title,
|
||||
description,
|
||||
boardId,
|
||||
postStatusId,
|
||||
|
||||
isUpdating,
|
||||
error,
|
||||
|
||||
handleChangeTitle,
|
||||
handleChangeDescription,
|
||||
handleChangeBoard,
|
||||
handleChangePostStatus,
|
||||
|
||||
isPowerUser,
|
||||
boards,
|
||||
postStatuses,
|
||||
|
||||
toggleEditMode,
|
||||
handleUpdatePost,
|
||||
}: Props) => (
|
||||
<div className="postEditForm">
|
||||
<div className="postHeader">
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={e => handleChangeTitle(e.target.value)}
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
isPowerUser ?
|
||||
<div className="postSettings">
|
||||
<PostBoardSelect
|
||||
boards={boards}
|
||||
selectedBoardId={boardId}
|
||||
handleChange={newBoardId => handleChangeBoard(newBoardId)}
|
||||
/>
|
||||
<PostStatusSelect
|
||||
postStatuses={postStatuses}
|
||||
selectedPostStatusId={postStatusId}
|
||||
handleChange={newPostStatusId => handleChangePostStatus(newPostStatusId)}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={e => handleChangeDescription(e.target.value)}
|
||||
rows={5}
|
||||
className="form-control"
|
||||
/>
|
||||
|
||||
<div className="postEditFormButtons">
|
||||
<a onClick={toggleEditMode}>
|
||||
{ I18n.t('common.buttons.cancel') }
|
||||
</a>
|
||||
|
||||
<Button onClick={() => handleUpdatePost(title, description, boardId, postStatusId)}>
|
||||
{ isUpdating ? <Spinner /> : I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostEditForm;
|
||||
53
app/javascript/components/Post/PostFooter.tsx
Normal file
53
app/javascript/components/Post/PostFooter.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
import friendlyDate from '../../helpers/datetime';
|
||||
import Separator from '../common/Separator';
|
||||
|
||||
interface Props {
|
||||
createdAt: string;
|
||||
toggleEditMode(): void;
|
||||
handleDeletePost(): void;
|
||||
isPowerUser: boolean;
|
||||
authorEmail: string;
|
||||
authorFullName: string;
|
||||
currentUserEmail: string;
|
||||
}
|
||||
|
||||
const PostFooter = ({
|
||||
createdAt,
|
||||
toggleEditMode,
|
||||
handleDeletePost,
|
||||
isPowerUser,
|
||||
authorEmail,
|
||||
authorFullName,
|
||||
currentUserEmail,
|
||||
}: Props) => (
|
||||
<div className="postFooter">
|
||||
<div className="postAuthor">
|
||||
<span>{ I18n.t('post.published_by').toLowerCase() } </span>
|
||||
<Gravatar email={authorEmail} size={24} className="postAuthorAvatar" />
|
||||
{authorFullName}
|
||||
</div>
|
||||
{
|
||||
isPowerUser || authorEmail === currentUserEmail ?
|
||||
<>
|
||||
<a onClick={toggleEditMode}>
|
||||
{ I18n.t('common.buttons.edit') }
|
||||
</a>
|
||||
<Separator />
|
||||
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDeletePost()}>
|
||||
{ I18n.t('common.buttons.delete') }
|
||||
</a>
|
||||
<Separator />
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
<MutedText>{friendlyDate(createdAt)}</MutedText>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostFooter;
|
||||
@@ -1,32 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import IPost from '../../interfaces/IPost';
|
||||
import IPostStatus from '../../interfaces/IPostStatus';
|
||||
import IBoard from '../../interfaces/IBoard';
|
||||
|
||||
import PostUpdateList from './PostUpdateList';
|
||||
import PostEditForm from './PostEditForm';
|
||||
import PostFooter from './PostFooter';
|
||||
import LikeList from './LikeList';
|
||||
import ActionBox from './ActionBox';
|
||||
import LikeButton from '../../containers/LikeButton';
|
||||
import PostBoardSelect from './PostBoardSelect';
|
||||
import PostStatusSelect from './PostStatusSelect';
|
||||
import PostBoardLabel from '../common/PostBoardLabel';
|
||||
import PostStatusLabel from '../common/PostStatusLabel';
|
||||
import Comments from '../../containers/Comments';
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
import Sidebar from '../common/Sidebar';
|
||||
|
||||
import { LikesState } from '../../reducers/likesReducer';
|
||||
import { CommentsState } from '../../reducers/commentsReducer';
|
||||
import { PostStatusChangesState } from '../../reducers/postStatusChangesReducer';
|
||||
import { PostEditFormState } from '../../reducers/currentPostReducer';
|
||||
|
||||
import friendlyDate, { fromRailsStringToJavascriptDate } from '../../helpers/datetime';
|
||||
import { fromRailsStringToJavascriptDate } from '../../helpers/datetime';
|
||||
import HttpStatus from '../../constants/http_status';
|
||||
|
||||
interface Props {
|
||||
postId: number;
|
||||
post: IPost;
|
||||
editMode: boolean;
|
||||
editForm: PostEditFormState;
|
||||
likes: LikesState;
|
||||
followed: boolean;
|
||||
comments: CommentsState;
|
||||
@@ -35,26 +37,38 @@ interface Props {
|
||||
postStatuses: Array<IPostStatus>;
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
currentUserFullName: string;
|
||||
currentUserEmail: string;
|
||||
authenticityToken: string;
|
||||
|
||||
requestPost(postId: number): void;
|
||||
updatePost(
|
||||
postId: number,
|
||||
title: string,
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
authenticityToken: string,
|
||||
): Promise<any>;
|
||||
|
||||
requestLikes(postId: number): void;
|
||||
requestFollow(postId: number): void;
|
||||
requestPostStatusChanges(postId: number): void;
|
||||
changePostBoard(
|
||||
postId: number,
|
||||
newBoardId: number,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
changePostStatus(
|
||||
postId: number,
|
||||
|
||||
toggleEditMode(): void;
|
||||
handleChangeEditFormTitle(title: string): void;
|
||||
handleChangeEditFormDescription(description: string): void;
|
||||
handleChangeEditFormBoard(boardId: number): void;
|
||||
handleChangeEditFormPostStatus(postStatusId: number): void;
|
||||
|
||||
deletePost(postId: number, authenticityToken: string): Promise<any>;
|
||||
|
||||
postStatusChangeSubmitted(
|
||||
newPostStatusId: number,
|
||||
userFullName: string,
|
||||
userEmail: string,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
|
||||
submitFollow(
|
||||
postId: number,
|
||||
isFollow: boolean,
|
||||
@@ -63,8 +77,15 @@ interface Props {
|
||||
}
|
||||
|
||||
class PostP extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._handleUpdatePost = this._handleUpdatePost.bind(this);
|
||||
this._handleDeletePost = this._handleDeletePost.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {postId} = this.props;
|
||||
const { postId } = this.props;
|
||||
|
||||
this.props.requestPost(postId);
|
||||
this.props.requestLikes(postId);
|
||||
@@ -72,9 +93,51 @@ class PostP extends React.Component<Props> {
|
||||
this.props.requestPostStatusChanges(postId);
|
||||
}
|
||||
|
||||
_handleUpdatePost(title: string, description: string, boardId: number, postStatusId: number) {
|
||||
const {
|
||||
postId,
|
||||
post,
|
||||
currentUserFullName,
|
||||
currentUserEmail,
|
||||
authenticityToken,
|
||||
|
||||
updatePost,
|
||||
postStatusChangeSubmitted,
|
||||
} = this.props;
|
||||
|
||||
const oldPostStatusId = post.postStatusId;
|
||||
|
||||
updatePost(
|
||||
postId,
|
||||
title,
|
||||
description,
|
||||
boardId,
|
||||
postStatusId,
|
||||
authenticityToken,
|
||||
).then(res => {
|
||||
if (res?.status !== HttpStatus.OK) return;
|
||||
if (postStatusId === oldPostStatusId) return;
|
||||
|
||||
postStatusChangeSubmitted(
|
||||
postStatusId,
|
||||
currentUserFullName,
|
||||
currentUserEmail,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_handleDeletePost() {
|
||||
this.props.deletePost(
|
||||
this.props.postId,
|
||||
this.props.authenticityToken
|
||||
).then(() => window.location.href = `/boards/${this.props.post.boardId}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
post,
|
||||
editMode,
|
||||
editForm,
|
||||
likes,
|
||||
followed,
|
||||
comments,
|
||||
@@ -84,13 +147,15 @@ class PostP extends React.Component<Props> {
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
userFullName,
|
||||
userEmail,
|
||||
currentUserEmail,
|
||||
authenticityToken,
|
||||
|
||||
changePostBoard,
|
||||
changePostStatus,
|
||||
submitFollow,
|
||||
toggleEditMode,
|
||||
handleChangeEditFormTitle,
|
||||
handleChangeEditFormDescription,
|
||||
handleChangeEditFormBoard,
|
||||
handleChangeEditFormPostStatus,
|
||||
} = this.props;
|
||||
|
||||
const postUpdates = [
|
||||
@@ -98,7 +163,7 @@ class PostP extends React.Component<Props> {
|
||||
...postStatusChanges.items,
|
||||
].sort(
|
||||
(a, b) =>
|
||||
fromRailsStringToJavascriptDate(a.updatedAt) < fromRailsStringToJavascriptDate(b.updatedAt) ? 1 : -1
|
||||
fromRailsStringToJavascriptDate(a.createdAt) < fromRailsStringToJavascriptDate(b.createdAt) ? 1 : -1
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -126,71 +191,72 @@ class PostP extends React.Component<Props> {
|
||||
</Sidebar>
|
||||
|
||||
<div className="postAndCommentsContainer">
|
||||
<>
|
||||
<div className="postHeader">
|
||||
<LikeButton
|
||||
postId={post.id}
|
||||
likesCount={likes.items.length}
|
||||
liked={likes.items.find(like => like.email === userEmail) ? 1 : 0}
|
||||
isLoggedIn={isLoggedIn}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
<h2>{post.title}</h2>
|
||||
{
|
||||
isPowerUser && post ?
|
||||
<a href={`/admin/posts/${post.id}`} data-turbolinks="false">
|
||||
{I18n.t('post.edit_button')}
|
||||
</a>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
{
|
||||
isPowerUser && post ?
|
||||
<div className="postSettings">
|
||||
<PostBoardSelect
|
||||
boards={boards}
|
||||
selectedBoardId={post.boardId}
|
||||
handleChange={
|
||||
newBoardId => changePostBoard(post.id, newBoardId, authenticityToken)
|
||||
}
|
||||
/>
|
||||
<PostStatusSelect
|
||||
postStatuses={postStatuses}
|
||||
selectedPostStatusId={post.postStatusId}
|
||||
handleChange={
|
||||
newPostStatusId =>
|
||||
changePostStatus(post.id, newPostStatusId, userFullName, userEmail, authenticityToken)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
<div className="postInfo">
|
||||
<PostBoardLabel
|
||||
{...boards.find(board => board.id === post.boardId)}
|
||||
/>
|
||||
<PostStatusLabel
|
||||
{...postStatuses.find(postStatus => postStatus.id === post.postStatusId)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ReactMarkdown
|
||||
className="postDescription"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{post.description}
|
||||
</ReactMarkdown>
|
||||
|
||||
<MutedText>{friendlyDate(post.createdAt)}</MutedText>
|
||||
</>
|
||||
{
|
||||
editMode ?
|
||||
<PostEditForm
|
||||
{...editForm}
|
||||
|
||||
handleChangeTitle={handleChangeEditFormTitle}
|
||||
handleChangeDescription={handleChangeEditFormDescription}
|
||||
handleChangeBoard={handleChangeEditFormBoard}
|
||||
handleChangePostStatus={handleChangeEditFormPostStatus}
|
||||
|
||||
isPowerUser={isPowerUser}
|
||||
boards={boards}
|
||||
postStatuses={postStatuses}
|
||||
|
||||
toggleEditMode={toggleEditMode}
|
||||
handleUpdatePost={this._handleUpdatePost}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<div className="postHeader">
|
||||
<LikeButton
|
||||
postId={post.id}
|
||||
likesCount={likes.items.length}
|
||||
liked={likes.items.find(like => like.email === currentUserEmail) ? 1 : 0}
|
||||
isLoggedIn={isLoggedIn}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
|
||||
<h3>{post.title}</h3>
|
||||
</div>
|
||||
|
||||
<div className="postInfo">
|
||||
<PostBoardLabel
|
||||
{...boards.find(board => board.id === post.boardId)}
|
||||
/>
|
||||
<PostStatusLabel
|
||||
{...postStatuses.find(postStatus => postStatus.id === post.postStatusId)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ReactMarkdown
|
||||
className="postDescription"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{post.description}
|
||||
</ReactMarkdown>
|
||||
|
||||
<PostFooter
|
||||
createdAt={post.createdAt}
|
||||
handleDeletePost={this._handleDeletePost}
|
||||
toggleEditMode={toggleEditMode}
|
||||
|
||||
isPowerUser={isPowerUser}
|
||||
authorEmail={post.userEmail}
|
||||
authorFullName={post.userFullName}
|
||||
currentUserEmail={currentUserEmail}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
<Comments
|
||||
postId={this.props.postId}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userEmail={userEmail}
|
||||
userEmail={currentUserEmail}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@ const PostUpdateList = ({
|
||||
}
|
||||
</div>
|
||||
|
||||
<MutedText>{friendlyDate(postUpdate.updatedAt)}</MutedText>
|
||||
<MutedText>{friendlyDate(postUpdate.createdAt)}</MutedText>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ interface Props {
|
||||
postStatuses: Array<IPostStatus>;
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
currentUserFullName: string;
|
||||
currentUserEmail: string;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ class PostRoot extends React.Component<Props> {
|
||||
postStatuses,
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
userFullName,
|
||||
userEmail,
|
||||
currentUserFullName,
|
||||
currentUserEmail,
|
||||
authenticityToken
|
||||
} = this.props;
|
||||
|
||||
@@ -52,8 +52,8 @@ class PostRoot extends React.Component<Props> {
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userFullName={userFullName}
|
||||
userEmail={userEmail}
|
||||
currentUserFullName={currentUserFullName}
|
||||
currentUserEmail={currentUserEmail}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
</Provider>
|
||||
|
||||
@@ -87,10 +87,7 @@ class BoardsEditable extends React.Component<Props, State> {
|
||||
|
||||
<Separator />
|
||||
|
||||
<a
|
||||
onClick={() => handleDelete(id)}
|
||||
data-confirm="Are you sure?"
|
||||
>
|
||||
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDelete(id)}>
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -78,10 +78,7 @@ class PostStatusEditable extends React.Component<Props, State> {
|
||||
|
||||
<Separator />
|
||||
|
||||
<a
|
||||
onClick={() => handleDelete(id)}
|
||||
data-confirm="Are you sure?"
|
||||
>
|
||||
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDelete(id)}>
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Box = ({ customClass, children }: Props) => (
|
||||
<div className={`box ${customClass}`}>
|
||||
<div className={`box${customClass ? ' ' + customClass : ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const SidebarBox = ({ title, customClass, children }: Props) => (
|
||||
<div className={`sidebarBox ${customClass}`}>
|
||||
<div className={`sidebarBox${customClass ? ' ' + customClass : ''}`}>
|
||||
<BoxTitleText>{title}</BoxTitleText>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user