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
@@ -1,5 +1,5 @@
|
|||||||
class CommentsController < ApplicationController
|
class CommentsController < ApplicationController
|
||||||
before_action :authenticate_user!, only: [:create, :update]
|
before_action :authenticate_user!, only: [:create, :update, :destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
comments = Comment
|
comments = Comment
|
||||||
@@ -8,58 +8,80 @@ class CommentsController < ApplicationController
|
|||||||
:body,
|
:body,
|
||||||
:parent_id,
|
:parent_id,
|
||||||
:is_post_update,
|
:is_post_update,
|
||||||
|
:created_at,
|
||||||
:updated_at,
|
:updated_at,
|
||||||
'users.full_name as user_full_name',
|
'users.full_name as user_full_name',
|
||||||
'users.email as user_email',
|
'users.email as user_email',
|
||||||
)
|
)
|
||||||
.where(post_id: params[:post_id])
|
.where(post_id: params[:post_id])
|
||||||
.left_outer_joins(:user)
|
.left_outer_joins(:user)
|
||||||
.order(updated_at: :desc)
|
.order(created_at: :desc)
|
||||||
|
|
||||||
render json: comments
|
render json: comments
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
comment = Comment.new(comment_params)
|
@comment = Comment.new
|
||||||
|
@comment.assign_attributes(comment_create_params)
|
||||||
|
|
||||||
if comment.save
|
if @comment.save
|
||||||
SendNotificationForCommentWorkflow.new(comment: comment).run
|
SendNotificationForCommentWorkflow.new(comment: @comment).run
|
||||||
|
|
||||||
render json: comment.attributes.merge(
|
render json: @comment.attributes.merge(
|
||||||
{ user_full_name: current_user.full_name, user_email: current_user.email }
|
{ user_full_name: current_user.full_name, user_email: current_user.email }
|
||||||
), status: :created
|
), status: :created
|
||||||
else
|
else
|
||||||
render json: {
|
render json: {
|
||||||
error: comment.errors.full_messages
|
error: @comment.errors.full_messages
|
||||||
}, status: :unprocessable_entity
|
}, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
comment = Comment.find(params[:id])
|
@comment = Comment.find(params[:id])
|
||||||
authorize comment
|
authorize @comment
|
||||||
comment.assign_attributes(comment_params)
|
|
||||||
|
|
||||||
if comment.save
|
if @comment.update(comment_update_params)
|
||||||
render json: comment.attributes.merge(
|
render json: @comment.attributes.merge(
|
||||||
{ user_full_name: current_user.full_name, user_email: current_user.email }
|
{ user_full_name: @comment.user.full_name, user_email: @comment.user.email }
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
render json: {
|
render json: {
|
||||||
error: comment.errors.full_messages
|
error: @comment.errors.full_messages
|
||||||
|
}, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@comment = Comment.find(params[:id])
|
||||||
|
authorize @comment
|
||||||
|
|
||||||
|
if @comment.destroy
|
||||||
|
render json: {
|
||||||
|
id: @comment.id,
|
||||||
|
}, status: :accepted
|
||||||
|
else
|
||||||
|
render json: {
|
||||||
|
error: @comment.errors.full_messages
|
||||||
}, status: :unprocessable_entity
|
}, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def comment_params
|
def comment_create_params
|
||||||
params
|
params
|
||||||
.require(:comment)
|
.require(:comment)
|
||||||
.permit(:body, :parent_id, :is_post_update)
|
.permit(policy(@comment).permitted_attributes_for_create)
|
||||||
.merge(
|
.merge(
|
||||||
user_id: current_user.id,
|
user_id: current_user.id,
|
||||||
post_id: params[:post_id]
|
post_id: params[:post_id]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def comment_update_params
|
||||||
|
params
|
||||||
|
.require(:comment)
|
||||||
|
.permit(policy(@comment).permitted_attributes_for_update)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ class PostStatusChangesController < ApplicationController
|
|||||||
post_status_changes = PostStatusChange
|
post_status_changes = PostStatusChange
|
||||||
.select(
|
.select(
|
||||||
:post_status_id,
|
:post_status_id,
|
||||||
:updated_at,
|
:created_at,
|
||||||
'users.full_name as user_full_name',
|
'users.full_name as user_full_name',
|
||||||
'users.email as user_email',
|
'users.email as user_email',
|
||||||
)
|
)
|
||||||
.where(post_id: params[:post_id])
|
.where(post_id: params[:post_id])
|
||||||
.left_outer_joins(:user)
|
.left_outer_joins(:user)
|
||||||
.order(updated_at: :asc)
|
.order(created_at: :asc)
|
||||||
|
|
||||||
render json: post_status_changes
|
render json: post_status_changes
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class PostsController < ApplicationController
|
class PostsController < ApplicationController
|
||||||
before_action :authenticate_user!, only: [:create, :update]
|
before_action :authenticate_user!, only: [:create, :update, :destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
posts = Post
|
posts = Post
|
||||||
@@ -25,21 +25,37 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
post = Post.new(post_params)
|
@post = Post.new
|
||||||
|
@post.assign_attributes(post_create_params)
|
||||||
|
|
||||||
if post.save
|
if @post.save
|
||||||
Follow.create(post_id: post.id, user_id: current_user.id)
|
Follow.create(post_id: @post.id, user_id: current_user.id)
|
||||||
|
|
||||||
render json: post, status: :created
|
render json: @post, status: :created
|
||||||
else
|
else
|
||||||
render json: {
|
render json: {
|
||||||
error: post.errors.full_messages
|
error: @post.errors.full_messages
|
||||||
}, status: :unprocessable_entity
|
}, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@post = Post.find(params[:id])
|
@post = Post
|
||||||
|
.select(
|
||||||
|
:id,
|
||||||
|
:title,
|
||||||
|
:description,
|
||||||
|
:board_id,
|
||||||
|
:user_id,
|
||||||
|
:post_status_id,
|
||||||
|
:created_at,
|
||||||
|
:updated_at,
|
||||||
|
'users.email as user_email',
|
||||||
|
'users.full_name as user_full_name'
|
||||||
|
)
|
||||||
|
.joins(:user)
|
||||||
|
.find(params[:id])
|
||||||
|
|
||||||
@post_statuses = PostStatus.select(:id, :name, :color).order(order: :asc)
|
@post_statuses = PostStatus.select(:id, :name, :color).order(order: :asc)
|
||||||
@board = @post.board
|
@board = @post.board
|
||||||
|
|
||||||
@@ -51,35 +67,41 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
post = Post.find(params[:id])
|
@post = Post.find(params[:id])
|
||||||
authorize post
|
authorize @post
|
||||||
|
|
||||||
post.board_id = params[:post][:board_id] if params[:post].has_key?(:board_id)
|
|
||||||
|
|
||||||
post_status_changed = false
|
@post.assign_attributes(post_update_params)
|
||||||
|
|
||||||
if params[:post].has_key?(:post_status_id) and
|
|
||||||
params[:post][:post_status_id] != post.post_status_id
|
|
||||||
|
|
||||||
post_status_changed = true
|
|
||||||
post.post_status_id = params[:post][:post_status_id]
|
|
||||||
end
|
|
||||||
|
|
||||||
if post.save
|
if @post.save
|
||||||
if post_status_changed
|
if @post.post_status_id_previously_changed?
|
||||||
PostStatusChange.create(
|
PostStatusChange.create(
|
||||||
user_id: current_user.id,
|
user_id: current_user.id,
|
||||||
post_id: post.id,
|
post_id: @post.id,
|
||||||
post_status_id: post.post_status_id
|
post_status_id: @post.post_status_id
|
||||||
)
|
)
|
||||||
|
|
||||||
send_notifications(post)
|
UserMailer.notify_followers_of_post_status_change(post: @post).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: post, status: :no_content
|
render json: @post
|
||||||
else
|
else
|
||||||
render json: {
|
render json: {
|
||||||
error: post.errors.full_messages
|
error: @post.errors.full_messages
|
||||||
|
}, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@post = Post.find(params[:id])
|
||||||
|
authorize @post
|
||||||
|
|
||||||
|
if @post.destroy
|
||||||
|
render json: {
|
||||||
|
id: @post.id,
|
||||||
|
}, status: :accepted
|
||||||
|
else
|
||||||
|
render json: {
|
||||||
|
error: @post.errors.full_messages
|
||||||
}, status: :unprocessable_entity
|
}, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -95,14 +117,16 @@ class PostsController < ApplicationController
|
|||||||
.except(:page, :search)
|
.except(:page, :search)
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_params
|
def post_create_params
|
||||||
params
|
params
|
||||||
.require(:post)
|
.require(:post)
|
||||||
.permit(:title, :description, :board_id)
|
.permit(policy(@post).permitted_attributes_for_create)
|
||||||
.merge(user_id: current_user.id)
|
.merge(user_id: current_user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_notifications(post)
|
def post_update_params
|
||||||
UserMailer.notify_followers_of_post_status_change(post: post).deliver_later
|
params
|
||||||
|
.require(:post)
|
||||||
|
.permit(policy(@post).permitted_attributes_for_update)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
72
app/javascript/actions/Comment/deleteComment.ts
Normal file
72
app/javascript/actions/Comment/deleteComment.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { Action } from "redux";
|
||||||
|
import { ThunkAction } from "redux-thunk";
|
||||||
|
import HttpStatus from "../../constants/http_status";
|
||||||
|
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
||||||
|
import { State } from "../../reducers/rootReducer";
|
||||||
|
|
||||||
|
export const COMMENT_DELETE_START = 'COMMENT_DELETE_START';
|
||||||
|
interface CommentDeleteStartAction {
|
||||||
|
type: typeof COMMENT_DELETE_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const COMMENT_DELETE_SUCCESS = 'COMMENT_DELETE_SUCCESS';
|
||||||
|
interface CommentDeleteSuccessAction {
|
||||||
|
type: typeof COMMENT_DELETE_SUCCESS;
|
||||||
|
postId: number;
|
||||||
|
commentId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const COMMENT_DELETE_FAILURE = 'COMMENT_DELETE_FAILURE';
|
||||||
|
interface CommentDeleteFailureAction {
|
||||||
|
type: typeof COMMENT_DELETE_FAILURE;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommentDeleteActionTypes =
|
||||||
|
CommentDeleteStartAction |
|
||||||
|
CommentDeleteSuccessAction |
|
||||||
|
CommentDeleteFailureAction;
|
||||||
|
|
||||||
|
const commentDeleteStart = (): CommentDeleteStartAction => ({
|
||||||
|
type: COMMENT_DELETE_START,
|
||||||
|
});
|
||||||
|
|
||||||
|
const commentDeleteSuccess = (
|
||||||
|
postId: number,
|
||||||
|
commentId: number,
|
||||||
|
): CommentDeleteSuccessAction => ({
|
||||||
|
type: COMMENT_DELETE_SUCCESS,
|
||||||
|
postId,
|
||||||
|
commentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const commentDeleteFailure = (error: string): CommentDeleteFailureAction => ({
|
||||||
|
type: COMMENT_DELETE_FAILURE,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteComment = (
|
||||||
|
postId: number,
|
||||||
|
commentId: number,
|
||||||
|
authenticityToken: string,
|
||||||
|
): ThunkAction<void, State, null, Action<string>> => (
|
||||||
|
async (dispatch) => {
|
||||||
|
dispatch(commentDeleteStart());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/posts/${postId}/comments/${commentId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: buildRequestHeaders(authenticityToken),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (res.status === HttpStatus.Accepted) {
|
||||||
|
dispatch(commentDeleteSuccess(postId, commentId));
|
||||||
|
} else {
|
||||||
|
dispatch(commentDeleteFailure(json.error));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
dispatch(commentDeleteFailure(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,43 +1,81 @@
|
|||||||
import { ThunkAction } from "redux-thunk";
|
|
||||||
import { State } from "../../reducers/rootReducer";
|
|
||||||
import { Action } from "redux";
|
import { Action } from "redux";
|
||||||
|
import { ThunkAction } from "redux-thunk";
|
||||||
|
|
||||||
|
import HttpStatus from "../../constants/http_status";
|
||||||
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
||||||
|
import ICommentJSON from "../../interfaces/json/IComment";
|
||||||
|
import { State } from "../../reducers/rootReducer";
|
||||||
|
|
||||||
export const TOGGLE_COMMENT_IS_UPDATE_SUCCESS = 'TOGGLE_COMMENT_IS_UPDATE_SUCCESS';
|
export const COMMENT_UPDATE_START = 'COMMENT_UPDATE_START';
|
||||||
export interface ToggleIsUpdateSuccessAction {
|
interface CommentUpdateStartAction {
|
||||||
type: typeof TOGGLE_COMMENT_IS_UPDATE_SUCCESS;
|
type: typeof COMMENT_UPDATE_START;
|
||||||
commentId: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleIsUpdateSuccess = (
|
export const COMMENT_UPDATE_SUCCESS = 'COMMENT_UPDATE_SUCCESS';
|
||||||
commentId: number,
|
interface CommentUpdateSuccessAction {
|
||||||
): ToggleIsUpdateSuccessAction => ({
|
type: typeof COMMENT_UPDATE_SUCCESS;
|
||||||
type: TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
comment: ICommentJSON;
|
||||||
commentId,
|
}
|
||||||
|
|
||||||
|
export const COMMENT_UPDATE_FAILURE = 'COMMENT_UPDATE_FAILURE';
|
||||||
|
interface CommentUpdateFailureAction {
|
||||||
|
type: typeof COMMENT_UPDATE_FAILURE;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommentUpdateActionTypes =
|
||||||
|
CommentUpdateStartAction |
|
||||||
|
CommentUpdateSuccessAction |
|
||||||
|
CommentUpdateFailureAction;
|
||||||
|
|
||||||
|
const commentUpdateStart = (): CommentUpdateStartAction => ({
|
||||||
|
type: COMMENT_UPDATE_START,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const toggleCommentIsUpdate = (
|
const commentUpdateSuccess = (
|
||||||
|
commentJSON: ICommentJSON,
|
||||||
|
): CommentUpdateSuccessAction => ({
|
||||||
|
type: COMMENT_UPDATE_SUCCESS,
|
||||||
|
comment: commentJSON,
|
||||||
|
});
|
||||||
|
|
||||||
|
const commentUpdateFailure = (error: string): CommentUpdateFailureAction => ({
|
||||||
|
type: COMMENT_UPDATE_FAILURE,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateComment = (
|
||||||
postId: number,
|
postId: number,
|
||||||
commentId: number,
|
commentId: number,
|
||||||
currentIsPostUpdate: boolean,
|
body: string,
|
||||||
|
isPostUpdate: boolean,
|
||||||
authenticityToken: string,
|
authenticityToken: string,
|
||||||
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||||
|
dispatch(commentUpdateStart());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/posts/${postId}/comments/${commentId}`, {
|
const res = await fetch(`/posts/${postId}/comments/${commentId}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: buildRequestHeaders(authenticityToken),
|
headers: buildRequestHeaders(authenticityToken),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
comment: {
|
comment: {
|
||||||
is_post_update: !currentIsPostUpdate,
|
body,
|
||||||
|
is_post_update: isPostUpdate,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (res.status === HttpStatus.OK) {
|
||||||
dispatch(toggleIsUpdateSuccess(commentId));
|
dispatch(commentUpdateSuccess(json));
|
||||||
|
} else {
|
||||||
|
dispatch(commentUpdateFailure(json.error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
dispatch(commentUpdateFailure(e));
|
||||||
|
|
||||||
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Action } from 'redux';
|
|
||||||
import { ThunkAction } from 'redux-thunk';
|
|
||||||
import { State } from '../../reducers/rootReducer';
|
|
||||||
|
|
||||||
import buildRequestHeaders from '../../helpers/buildRequestHeaders';
|
|
||||||
|
|
||||||
export const CHANGE_POST_BOARD_SUCCESS = 'CHANGE_POST_BOARD_SUCCESS';
|
|
||||||
export interface ChangePostBoardSuccessAction {
|
|
||||||
type: typeof CHANGE_POST_BOARD_SUCCESS;
|
|
||||||
newBoardId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const changePostBoardSuccess = (newBoardId: number): ChangePostBoardSuccessAction => ({
|
|
||||||
type: CHANGE_POST_BOARD_SUCCESS,
|
|
||||||
newBoardId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const changePostBoard = (
|
|
||||||
postId: number,
|
|
||||||
newBoardId: number,
|
|
||||||
authenticityToken: string,
|
|
||||||
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/posts/${postId}`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: buildRequestHeaders(authenticityToken),
|
|
||||||
body: JSON.stringify({
|
|
||||||
post: {
|
|
||||||
board_id: newBoardId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status === 204) {
|
|
||||||
dispatch(changePostBoardSuccess(newBoardId));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
61
app/javascript/actions/Post/changePostEditForm.ts
Normal file
61
app/javascript/actions/Post/changePostEditForm.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export const POST_CHANGE_EDIT_FORM_TITLE = 'POST_CHANGE_EDIT_FORM_TITLE';
|
||||||
|
|
||||||
|
interface PostChangeEditFormTitle {
|
||||||
|
type: typeof POST_CHANGE_EDIT_FORM_TITLE,
|
||||||
|
title: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changePostEditFormTitle = (
|
||||||
|
title: string
|
||||||
|
): PostChangeEditFormTitle => ({
|
||||||
|
type: POST_CHANGE_EDIT_FORM_TITLE,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const POST_CHANGE_EDIT_FORM_DESCRIPTION = 'POST_CHANGE_EDIT_FORM_DESCRIPTION';
|
||||||
|
|
||||||
|
interface PostChangeEditFormDescription {
|
||||||
|
type: typeof POST_CHANGE_EDIT_FORM_DESCRIPTION,
|
||||||
|
description: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changePostEditFormDescription = (
|
||||||
|
description: string
|
||||||
|
): PostChangeEditFormDescription => ({
|
||||||
|
type: POST_CHANGE_EDIT_FORM_DESCRIPTION,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const POST_CHANGE_EDIT_FORM_BOARD = 'POST_CHANGE_EDIT_FORM_BOARD';
|
||||||
|
|
||||||
|
interface PostChangeEditFormBoard {
|
||||||
|
type: typeof POST_CHANGE_EDIT_FORM_BOARD,
|
||||||
|
boardId: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changePostEditFormBoard = (
|
||||||
|
boardId: number
|
||||||
|
): PostChangeEditFormBoard => ({
|
||||||
|
type: POST_CHANGE_EDIT_FORM_BOARD,
|
||||||
|
boardId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const POST_CHANGE_EDIT_FORM_POST_STATUS = 'POST_CHANGE_EDIT_FORM_POST_STATUS';
|
||||||
|
|
||||||
|
interface PostChangeEditFormPostStatus {
|
||||||
|
type: typeof POST_CHANGE_EDIT_FORM_POST_STATUS,
|
||||||
|
postStatusId: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changePostEditFormPostStatus = (
|
||||||
|
postStatusId: number
|
||||||
|
): PostChangeEditFormPostStatus => ({
|
||||||
|
type: POST_CHANGE_EDIT_FORM_POST_STATUS,
|
||||||
|
postStatusId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ChangePostEditFormActionTypes =
|
||||||
|
PostChangeEditFormTitle |
|
||||||
|
PostChangeEditFormDescription |
|
||||||
|
PostChangeEditFormBoard |
|
||||||
|
PostChangeEditFormPostStatus;
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Action } from 'redux';
|
|
||||||
import { ThunkAction } from 'redux-thunk';
|
|
||||||
import { State } from '../../reducers/rootReducer';
|
|
||||||
|
|
||||||
import buildRequestHeaders from '../../helpers/buildRequestHeaders';
|
|
||||||
|
|
||||||
export const CHANGE_POST_STATUS_SUCCESS = 'CHANGE_POST_STATUS_SUCCESS';
|
|
||||||
export interface ChangePostStatusSuccessAction {
|
|
||||||
type: typeof CHANGE_POST_STATUS_SUCCESS;
|
|
||||||
newPostStatusId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const changePostStatusSuccess = (newPostStatusId: number): ChangePostStatusSuccessAction => ({
|
|
||||||
type: CHANGE_POST_STATUS_SUCCESS,
|
|
||||||
newPostStatusId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const changePostStatus = (
|
|
||||||
postId: number,
|
|
||||||
newPostStatusId: number,
|
|
||||||
authenticityToken: string,
|
|
||||||
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/posts/${postId}`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: buildRequestHeaders(authenticityToken),
|
|
||||||
body: JSON.stringify({
|
|
||||||
post: {
|
|
||||||
post_status_id: newPostStatusId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status === 204) {
|
|
||||||
dispatch(changePostStatusSuccess(newPostStatusId));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
69
app/javascript/actions/Post/deletePost.ts
Normal file
69
app/javascript/actions/Post/deletePost.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { Action } from "redux";
|
||||||
|
import { ThunkAction } from "redux-thunk";
|
||||||
|
|
||||||
|
import HttpStatus from "../../constants/http_status";
|
||||||
|
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
||||||
|
import { State } from "../../reducers/rootReducer";
|
||||||
|
|
||||||
|
export const POST_DELETE_START = 'POST_DELETE_START';
|
||||||
|
interface PostDeleteStartAction {
|
||||||
|
type: typeof POST_DELETE_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST_DELETE_SUCCESS = 'POST_DELETE_SUCCESS';
|
||||||
|
interface PostDeleteSuccessAction {
|
||||||
|
type: typeof POST_DELETE_SUCCESS;
|
||||||
|
postId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST_DELETE_FAILURE = 'POST_DELETE_FAILURE';
|
||||||
|
interface PostDeleteFailureAction {
|
||||||
|
type: typeof POST_DELETE_FAILURE;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostDeleteActionTypes =
|
||||||
|
PostDeleteStartAction |
|
||||||
|
PostDeleteSuccessAction |
|
||||||
|
PostDeleteFailureAction;
|
||||||
|
|
||||||
|
const postDeleteStart = (): PostDeleteStartAction => ({
|
||||||
|
type: POST_DELETE_START,
|
||||||
|
});
|
||||||
|
|
||||||
|
const postDeleteSuccess = (
|
||||||
|
postId: number,
|
||||||
|
): PostDeleteSuccessAction => ({
|
||||||
|
type: POST_DELETE_SUCCESS,
|
||||||
|
postId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const postDeleteFailure = (error: string): PostDeleteFailureAction => ({
|
||||||
|
type: POST_DELETE_FAILURE,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deletePost = (
|
||||||
|
postId: number,
|
||||||
|
authenticityToken: string,
|
||||||
|
): ThunkAction<void, State, null, Action<string>> => (
|
||||||
|
async (dispatch) => {
|
||||||
|
dispatch(postDeleteStart());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/posts/${postId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: buildRequestHeaders(authenticityToken),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (res.status === HttpStatus.Accepted) {
|
||||||
|
dispatch(postDeleteSuccess(postId));
|
||||||
|
} else {
|
||||||
|
dispatch(postDeleteFailure(json.error));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
dispatch(postDeleteFailure(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
9
app/javascript/actions/Post/togglePostEditMode.ts
Normal file
9
app/javascript/actions/Post/togglePostEditMode.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const POST_TOGGLE_EDIT_MODE = 'POST_TOGGLE_EDIT_MODE';
|
||||||
|
|
||||||
|
export interface PostToggleEditMode {
|
||||||
|
type: typeof POST_TOGGLE_EDIT_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const togglePostEditMode = (): PostToggleEditMode => ({
|
||||||
|
type: POST_TOGGLE_EDIT_MODE,
|
||||||
|
});
|
||||||
83
app/javascript/actions/Post/updatePost.ts
Normal file
83
app/javascript/actions/Post/updatePost.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Action } from "redux";
|
||||||
|
import { ThunkAction } from "redux-thunk";
|
||||||
|
import HttpStatus from "../../constants/http_status";
|
||||||
|
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
||||||
|
import IPostJSON from "../../interfaces/json/IPost";
|
||||||
|
import { State } from "../../reducers/rootReducer";
|
||||||
|
|
||||||
|
export const POST_UPDATE_START = 'POST_UPDATE_START';
|
||||||
|
interface PostUpdateStartAction {
|
||||||
|
type: typeof POST_UPDATE_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST_UPDATE_SUCCESS = 'POST_UPDATE_SUCCESS';
|
||||||
|
interface PostUpdateSuccessAction {
|
||||||
|
type: typeof POST_UPDATE_SUCCESS;
|
||||||
|
post: IPostJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST_UPDATE_FAILURE = 'POST_UPDATE_FAILURE';
|
||||||
|
interface PostUpdateFailureAction {
|
||||||
|
type: typeof POST_UPDATE_FAILURE;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostUpdateActionTypes =
|
||||||
|
PostUpdateStartAction |
|
||||||
|
PostUpdateSuccessAction |
|
||||||
|
PostUpdateFailureAction;
|
||||||
|
|
||||||
|
const postUpdateStart = (): PostUpdateStartAction => ({
|
||||||
|
type: POST_UPDATE_START,
|
||||||
|
});
|
||||||
|
|
||||||
|
const postUpdateSuccess = (
|
||||||
|
postJSON: IPostJSON,
|
||||||
|
): PostUpdateSuccessAction => ({
|
||||||
|
type: POST_UPDATE_SUCCESS,
|
||||||
|
post: postJSON,
|
||||||
|
});
|
||||||
|
|
||||||
|
const postUpdateFailure = (error: string): PostUpdateFailureAction => ({
|
||||||
|
type: POST_UPDATE_FAILURE,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updatePost = (
|
||||||
|
id: number,
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
boardId: number,
|
||||||
|
postStatusId: number,
|
||||||
|
authenticityToken: string,
|
||||||
|
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||||
|
dispatch(postUpdateStart());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/posts/${id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: buildRequestHeaders(authenticityToken),
|
||||||
|
body: JSON.stringify({
|
||||||
|
post: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
board_id: boardId,
|
||||||
|
post_status_id: postStatusId,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (res.status === HttpStatus.OK) {
|
||||||
|
dispatch(postUpdateSuccess(json));
|
||||||
|
} else {
|
||||||
|
dispatch(postUpdateFailure(json.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(res);
|
||||||
|
} catch (e) {
|
||||||
|
dispatch(postUpdateFailure(e));
|
||||||
|
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4,12 +4,11 @@ import Gravatar from 'react-gravatar';
|
|||||||
import I18n from 'i18n-js';
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
import NewComment from './NewComment';
|
import NewComment from './NewComment';
|
||||||
import Separator from '../common/Separator';
|
|
||||||
import { MutedText } from '../common/CustomTexts';
|
|
||||||
|
|
||||||
import { ReplyFormState } from '../../reducers/replyFormReducer';
|
import { ReplyFormState } from '../../reducers/replyFormReducer';
|
||||||
|
|
||||||
import friendlyDate from '../../helpers/datetime';
|
import CommentEditForm from './CommentEditForm';
|
||||||
|
import CommentFooter from './CommentFooter';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -17,119 +16,147 @@ interface Props {
|
|||||||
isPostUpdate: boolean;
|
isPostUpdate: boolean;
|
||||||
userFullName: string;
|
userFullName: string;
|
||||||
userEmail: string;
|
userEmail: string;
|
||||||
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|
||||||
replyForm: ReplyFormState;
|
replyForm: ReplyFormState;
|
||||||
handleToggleCommentReply(): void;
|
handleToggleCommentReply(): void;
|
||||||
handleCommentReplyBodyChange(e: React.FormEvent): void;
|
handleCommentReplyBodyChange(e: React.FormEvent): void;
|
||||||
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
|
||||||
handleSubmitComment(body: string, parentId: number, isPostUpdate: 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;
|
isLoggedIn: boolean;
|
||||||
isPowerUser: boolean;
|
isPowerUser: boolean;
|
||||||
currentUserEmail: string;
|
currentUserEmail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Comment = ({
|
interface State {
|
||||||
id,
|
editMode: boolean;
|
||||||
body,
|
}
|
||||||
isPostUpdate,
|
|
||||||
userFullName,
|
|
||||||
userEmail,
|
|
||||||
updatedAt,
|
|
||||||
|
|
||||||
replyForm,
|
class Comment extends React.Component<Props, State> {
|
||||||
handleToggleCommentReply,
|
constructor(props: Props) {
|
||||||
handleCommentReplyBodyChange,
|
super(props);
|
||||||
handleToggleIsCommentUpdate,
|
|
||||||
handleSubmitComment,
|
|
||||||
|
|
||||||
isLoggedIn,
|
this.state = {
|
||||||
isPowerUser,
|
editMode: false,
|
||||||
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>
|
|
||||||
|
|
||||||
<ReactMarkdown
|
this.toggleEditMode = this.toggleEditMode.bind(this);
|
||||||
className="commentBody"
|
this._handleUpdateComment = this._handleUpdateComment.bind(this);
|
||||||
disallowedTypes={['heading', 'image', 'html']}
|
}
|
||||||
unwrapDisallowed
|
|
||||||
>
|
|
||||||
{body}
|
|
||||||
</ReactMarkdown>
|
|
||||||
|
|
||||||
<div className="commentFooter">
|
toggleEditMode() {
|
||||||
<a className="commentReplyButton commentLink" onClick={handleToggleCommentReply}>
|
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 ?
|
replyForm.isOpen ?
|
||||||
I18n.t('common.buttons.cancel')
|
<NewComment
|
||||||
:
|
body={replyForm.body}
|
||||||
I18n.t('post.comments.reply_button')
|
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>
|
</div>
|
||||||
{
|
);
|
||||||
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>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Comment;
|
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;
|
toggleCommentReply(commentId: number): void;
|
||||||
setCommentReplyBody(commentId: number, body: string): void;
|
setCommentReplyBody(commentId: number, body: string): void;
|
||||||
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
|
||||||
handleSubmitComment(body: string, parentId: number, isPostUpdate: 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;
|
isLoggedIn: boolean;
|
||||||
isPowerUser: boolean;
|
isPowerUser: boolean;
|
||||||
@@ -29,8 +31,9 @@ const CommentList = ({
|
|||||||
|
|
||||||
toggleCommentReply,
|
toggleCommentReply,
|
||||||
setCommentReplyBody,
|
setCommentReplyBody,
|
||||||
handleToggleIsCommentUpdate,
|
|
||||||
handleSubmitComment,
|
handleSubmitComment,
|
||||||
|
handleUpdateComment,
|
||||||
|
handleDeleteComment,
|
||||||
|
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
isPowerUser,
|
isPowerUser,
|
||||||
@@ -49,8 +52,11 @@ const CommentList = ({
|
|||||||
setCommentReplyBody(comment.id, (e.target as HTMLTextAreaElement).value)
|
setCommentReplyBody(comment.id, (e.target as HTMLTextAreaElement).value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
|
||||||
handleSubmitComment={handleSubmitComment}
|
handleSubmitComment={handleSubmitComment}
|
||||||
|
handleUpdateComment={handleUpdateComment}
|
||||||
|
handleDeleteComment={handleDeleteComment}
|
||||||
|
|
||||||
{...comment}
|
{...comment}
|
||||||
|
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
@@ -66,8 +72,10 @@ const CommentList = ({
|
|||||||
|
|
||||||
toggleCommentReply={toggleCommentReply}
|
toggleCommentReply={toggleCommentReply}
|
||||||
setCommentReplyBody={setCommentReplyBody}
|
setCommentReplyBody={setCommentReplyBody}
|
||||||
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
|
||||||
handleSubmitComment={handleSubmitComment}
|
handleSubmitComment={handleSubmitComment}
|
||||||
|
handleUpdateComment={handleUpdateComment}
|
||||||
|
handleDeleteComment={handleDeleteComment}
|
||||||
|
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
isPowerUser={isPowerUser}
|
isPowerUser={isPowerUser}
|
||||||
|
|||||||
@@ -26,12 +26,7 @@ interface Props {
|
|||||||
toggleCommentReply(commentId: number): void;
|
toggleCommentReply(commentId: number): void;
|
||||||
setCommentReplyBody(commentId: number, body: string): void;
|
setCommentReplyBody(commentId: number, body: string): void;
|
||||||
toggleCommentIsPostUpdateFlag(): void;
|
toggleCommentIsPostUpdateFlag(): void;
|
||||||
toggleCommentIsPostUpdate(
|
|
||||||
postId: number,
|
|
||||||
commentId: number,
|
|
||||||
currentIsPostUpdate: boolean,
|
|
||||||
authenticityToken: string,
|
|
||||||
): void;
|
|
||||||
submitComment(
|
submitComment(
|
||||||
postId: number,
|
postId: number,
|
||||||
body: string,
|
body: string,
|
||||||
@@ -39,6 +34,19 @@ interface Props {
|
|||||||
isPostUpdate: boolean,
|
isPostUpdate: boolean,
|
||||||
authenticityToken: string,
|
authenticityToken: string,
|
||||||
): void;
|
): 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> {
|
class CommentsP extends React.Component<Props> {
|
||||||
@@ -46,15 +54,6 @@ class CommentsP extends React.Component<Props> {
|
|||||||
this.props.requestComments(this.props.postId);
|
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) => {
|
_handleSubmitComment = (body: string, parentId: number, isPostUpdate: boolean) => {
|
||||||
this.props.submitComment(
|
this.props.submitComment(
|
||||||
this.props.postId,
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@@ -118,8 +136,9 @@ class CommentsP extends React.Component<Props> {
|
|||||||
replyForms={replyForms}
|
replyForms={replyForms}
|
||||||
toggleCommentReply={toggleCommentReply}
|
toggleCommentReply={toggleCommentReply}
|
||||||
setCommentReplyBody={setCommentReplyBody}
|
setCommentReplyBody={setCommentReplyBody}
|
||||||
handleToggleIsCommentUpdate={this._handleToggleIsCommentUpdate}
|
|
||||||
handleSubmitComment={this._handleSubmitComment}
|
handleSubmitComment={this._handleSubmitComment}
|
||||||
|
handleUpdateComment={this._handleUpdateComment}
|
||||||
|
handleDeleteComment={this._handleDeleteComment}
|
||||||
parentId={null}
|
parentId={null}
|
||||||
level={1}
|
level={1}
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import I18n from 'i18n-js';
|
|
||||||
import Gravatar from 'react-gravatar';
|
import Gravatar from 'react-gravatar';
|
||||||
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
import NewCommentUpdateSection from './NewCommentUpdateSection';
|
import NewCommentUpdateSection from './NewCommentUpdateSection';
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ const NewComment = ({
|
|||||||
value={body}
|
value={body}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={I18n.t('post.new_comment.body_placeholder')}
|
placeholder={I18n.t('post.new_comment.body_placeholder')}
|
||||||
className="newCommentBody"
|
className="commentForm"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleSubmit(body, parentId, postUpdateFlagValue)}
|
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 * as React from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import I18n from 'i18n-js';
|
|
||||||
|
|
||||||
import IPost from '../../interfaces/IPost';
|
import IPost from '../../interfaces/IPost';
|
||||||
import IPostStatus from '../../interfaces/IPostStatus';
|
import IPostStatus from '../../interfaces/IPostStatus';
|
||||||
import IBoard from '../../interfaces/IBoard';
|
import IBoard from '../../interfaces/IBoard';
|
||||||
|
|
||||||
import PostUpdateList from './PostUpdateList';
|
import PostUpdateList from './PostUpdateList';
|
||||||
|
import PostEditForm from './PostEditForm';
|
||||||
|
import PostFooter from './PostFooter';
|
||||||
import LikeList from './LikeList';
|
import LikeList from './LikeList';
|
||||||
import ActionBox from './ActionBox';
|
import ActionBox from './ActionBox';
|
||||||
import LikeButton from '../../containers/LikeButton';
|
import LikeButton from '../../containers/LikeButton';
|
||||||
import PostBoardSelect from './PostBoardSelect';
|
|
||||||
import PostStatusSelect from './PostStatusSelect';
|
|
||||||
import PostBoardLabel from '../common/PostBoardLabel';
|
import PostBoardLabel from '../common/PostBoardLabel';
|
||||||
import PostStatusLabel from '../common/PostStatusLabel';
|
import PostStatusLabel from '../common/PostStatusLabel';
|
||||||
import Comments from '../../containers/Comments';
|
import Comments from '../../containers/Comments';
|
||||||
import { MutedText } from '../common/CustomTexts';
|
|
||||||
import Sidebar from '../common/Sidebar';
|
import Sidebar from '../common/Sidebar';
|
||||||
|
|
||||||
import { LikesState } from '../../reducers/likesReducer';
|
import { LikesState } from '../../reducers/likesReducer';
|
||||||
import { CommentsState } from '../../reducers/commentsReducer';
|
import { CommentsState } from '../../reducers/commentsReducer';
|
||||||
import { PostStatusChangesState } from '../../reducers/postStatusChangesReducer';
|
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 {
|
interface Props {
|
||||||
postId: number;
|
postId: number;
|
||||||
post: IPost;
|
post: IPost;
|
||||||
|
editMode: boolean;
|
||||||
|
editForm: PostEditFormState;
|
||||||
likes: LikesState;
|
likes: LikesState;
|
||||||
followed: boolean;
|
followed: boolean;
|
||||||
comments: CommentsState;
|
comments: CommentsState;
|
||||||
@@ -35,26 +37,38 @@ interface Props {
|
|||||||
postStatuses: Array<IPostStatus>;
|
postStatuses: Array<IPostStatus>;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
isPowerUser: boolean;
|
isPowerUser: boolean;
|
||||||
userFullName: string;
|
currentUserFullName: string;
|
||||||
userEmail: string;
|
currentUserEmail: string;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
|
|
||||||
requestPost(postId: number): void;
|
requestPost(postId: number): void;
|
||||||
|
updatePost(
|
||||||
|
postId: number,
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
boardId: number,
|
||||||
|
postStatusId: number,
|
||||||
|
authenticityToken: string,
|
||||||
|
): Promise<any>;
|
||||||
|
|
||||||
requestLikes(postId: number): void;
|
requestLikes(postId: number): void;
|
||||||
requestFollow(postId: number): void;
|
requestFollow(postId: number): void;
|
||||||
requestPostStatusChanges(postId: number): void;
|
requestPostStatusChanges(postId: number): void;
|
||||||
changePostBoard(
|
|
||||||
postId: number,
|
toggleEditMode(): void;
|
||||||
newBoardId: number,
|
handleChangeEditFormTitle(title: string): void;
|
||||||
authenticityToken: string,
|
handleChangeEditFormDescription(description: string): void;
|
||||||
): void;
|
handleChangeEditFormBoard(boardId: number): void;
|
||||||
changePostStatus(
|
handleChangeEditFormPostStatus(postStatusId: number): void;
|
||||||
postId: number,
|
|
||||||
|
deletePost(postId: number, authenticityToken: string): Promise<any>;
|
||||||
|
|
||||||
|
postStatusChangeSubmitted(
|
||||||
newPostStatusId: number,
|
newPostStatusId: number,
|
||||||
userFullName: string,
|
userFullName: string,
|
||||||
userEmail: string,
|
userEmail: string,
|
||||||
authenticityToken: string,
|
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
submitFollow(
|
submitFollow(
|
||||||
postId: number,
|
postId: number,
|
||||||
isFollow: boolean,
|
isFollow: boolean,
|
||||||
@@ -63,8 +77,15 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PostP extends React.Component<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() {
|
componentDidMount() {
|
||||||
const {postId} = this.props;
|
const { postId } = this.props;
|
||||||
|
|
||||||
this.props.requestPost(postId);
|
this.props.requestPost(postId);
|
||||||
this.props.requestLikes(postId);
|
this.props.requestLikes(postId);
|
||||||
@@ -72,9 +93,51 @@ class PostP extends React.Component<Props> {
|
|||||||
this.props.requestPostStatusChanges(postId);
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
post,
|
post,
|
||||||
|
editMode,
|
||||||
|
editForm,
|
||||||
likes,
|
likes,
|
||||||
followed,
|
followed,
|
||||||
comments,
|
comments,
|
||||||
@@ -84,13 +147,15 @@ class PostP extends React.Component<Props> {
|
|||||||
|
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
isPowerUser,
|
isPowerUser,
|
||||||
userFullName,
|
currentUserEmail,
|
||||||
userEmail,
|
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
|
|
||||||
changePostBoard,
|
|
||||||
changePostStatus,
|
|
||||||
submitFollow,
|
submitFollow,
|
||||||
|
toggleEditMode,
|
||||||
|
handleChangeEditFormTitle,
|
||||||
|
handleChangeEditFormDescription,
|
||||||
|
handleChangeEditFormBoard,
|
||||||
|
handleChangeEditFormPostStatus,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const postUpdates = [
|
const postUpdates = [
|
||||||
@@ -98,7 +163,7 @@ class PostP extends React.Component<Props> {
|
|||||||
...postStatusChanges.items,
|
...postStatusChanges.items,
|
||||||
].sort(
|
].sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
fromRailsStringToJavascriptDate(a.updatedAt) < fromRailsStringToJavascriptDate(b.updatedAt) ? 1 : -1
|
fromRailsStringToJavascriptDate(a.createdAt) < fromRailsStringToJavascriptDate(b.createdAt) ? 1 : -1
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -126,71 +191,72 @@ class PostP extends React.Component<Props> {
|
|||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
<div className="postAndCommentsContainer">
|
<div className="postAndCommentsContainer">
|
||||||
<>
|
{
|
||||||
<div className="postHeader">
|
editMode ?
|
||||||
<LikeButton
|
<PostEditForm
|
||||||
postId={post.id}
|
{...editForm}
|
||||||
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>
|
|
||||||
</>
|
|
||||||
|
|
||||||
|
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
|
<Comments
|
||||||
postId={this.props.postId}
|
postId={this.props.postId}
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
isPowerUser={isPowerUser}
|
isPowerUser={isPowerUser}
|
||||||
userEmail={userEmail}
|
userEmail={currentUserEmail}
|
||||||
authenticityToken={authenticityToken}
|
authenticityToken={authenticityToken}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const PostUpdateList = ({
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MutedText>{friendlyDate(postUpdate.updatedAt)}</MutedText>
|
<MutedText>{friendlyDate(postUpdate.createdAt)}</MutedText>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ interface Props {
|
|||||||
postStatuses: Array<IPostStatus>;
|
postStatuses: Array<IPostStatus>;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
isPowerUser: boolean;
|
isPowerUser: boolean;
|
||||||
userFullName: string;
|
currentUserFullName: string;
|
||||||
userEmail: string;
|
currentUserEmail: string;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ class PostRoot extends React.Component<Props> {
|
|||||||
postStatuses,
|
postStatuses,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
isPowerUser,
|
isPowerUser,
|
||||||
userFullName,
|
currentUserFullName,
|
||||||
userEmail,
|
currentUserEmail,
|
||||||
authenticityToken
|
authenticityToken
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -52,8 +52,8 @@ class PostRoot extends React.Component<Props> {
|
|||||||
|
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
isPowerUser={isPowerUser}
|
isPowerUser={isPowerUser}
|
||||||
userFullName={userFullName}
|
currentUserFullName={currentUserFullName}
|
||||||
userEmail={userEmail}
|
currentUserEmail={currentUserEmail}
|
||||||
authenticityToken={authenticityToken}
|
authenticityToken={authenticityToken}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|||||||
@@ -87,10 +87,7 @@ class BoardsEditable extends React.Component<Props, State> {
|
|||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<a
|
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDelete(id)}>
|
||||||
onClick={() => handleDelete(id)}
|
|
||||||
data-confirm="Are you sure?"
|
|
||||||
>
|
|
||||||
{I18n.t('common.buttons.delete')}
|
{I18n.t('common.buttons.delete')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,10 +78,7 @@ class PostStatusEditable extends React.Component<Props, State> {
|
|||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<a
|
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDelete(id)}>
|
||||||
onClick={() => handleDelete(id)}
|
|
||||||
data-confirm="Are you sure?"
|
|
||||||
>
|
|
||||||
{I18n.t('common.buttons.delete')}
|
{I18n.t('common.buttons.delete')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Box = ({ customClass, children }: Props) => (
|
const Box = ({ customClass, children }: Props) => (
|
||||||
<div className={`box ${customClass}`}>
|
<div className={`box${customClass ? ' ' + customClass : ''}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SidebarBox = ({ title, customClass, children }: Props) => (
|
const SidebarBox = ({ title, customClass, children }: Props) => (
|
||||||
<div className={`sidebarBox ${customClass}`}>
|
<div className={`sidebarBox${customClass ? ' ' + customClass : ''}`}>
|
||||||
<BoxTitleText>{title}</BoxTitleText>
|
<BoxTitleText>{title}</BoxTitleText>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import {
|
|||||||
setCommentReplyBody,
|
setCommentReplyBody,
|
||||||
toggleCommentIsPostUpdateFlag,
|
toggleCommentIsPostUpdateFlag,
|
||||||
} from '../actions/Comment/handleCommentReplies';
|
} from '../actions/Comment/handleCommentReplies';
|
||||||
import { toggleCommentIsUpdate } from '../actions/Comment/updateComment';
|
|
||||||
import { submitComment } from '../actions/Comment/submitComment';
|
import { submitComment } from '../actions/Comment/submitComment';
|
||||||
|
import { updateComment } from '../actions/Comment/updateComment';
|
||||||
|
import { deleteComment } from '../actions/Comment/deleteComment';
|
||||||
|
|
||||||
import { State } from '../reducers/rootReducer';
|
import { State } from '../reducers/rootReducer';
|
||||||
|
|
||||||
import CommentsP from '../components/Comments/CommentsP';
|
import CommentsP from '../components/Comments/CommentsP';
|
||||||
|
import HttpStatus from '../constants/http_status';
|
||||||
|
|
||||||
const mapStateToProps = (state: State) => ({
|
const mapStateToProps = (state: State) => ({
|
||||||
comments: state.currentPost.comments.items,
|
comments: state.currentPost.comments.items,
|
||||||
@@ -37,15 +39,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(toggleCommentIsPostUpdateFlag(null));
|
dispatch(toggleCommentIsPostUpdateFlag(null));
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleCommentIsPostUpdate(
|
|
||||||
postId: number,
|
|
||||||
commentId: number,
|
|
||||||
currentIsPostUpdate: boolean,
|
|
||||||
authenticityToken: string,
|
|
||||||
) {
|
|
||||||
dispatch(toggleCommentIsUpdate(postId, commentId, currentIsPostUpdate, authenticityToken));
|
|
||||||
},
|
|
||||||
|
|
||||||
submitComment(
|
submitComment(
|
||||||
postId: number,
|
postId: number,
|
||||||
body: string,
|
body: string,
|
||||||
@@ -55,6 +48,27 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
) {
|
) {
|
||||||
dispatch(submitComment(postId, body, parentId, isPostUpdate, authenticityToken));
|
dispatch(submitComment(postId, body, parentId, isPostUpdate, authenticityToken));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateComment(
|
||||||
|
postId: number,
|
||||||
|
commentId: number,
|
||||||
|
body: string,
|
||||||
|
isPostUpdate: boolean,
|
||||||
|
onSuccess: Function,
|
||||||
|
authenticityToken: string,
|
||||||
|
) {
|
||||||
|
dispatch(updateComment(postId, commentId, body, isPostUpdate, authenticityToken)).then(res => {
|
||||||
|
if (res && res.status === HttpStatus.OK) onSuccess();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteComment(
|
||||||
|
postId: number,
|
||||||
|
commentId: number,
|
||||||
|
authenticityToken: string,
|
||||||
|
) {
|
||||||
|
dispatch(deleteComment(postId, commentId, authenticityToken));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { requestPost } from '../actions/Post/requestPost';
|
import { requestPost } from '../actions/Post/requestPost';
|
||||||
|
import { deletePost } from '../actions/Post/deletePost';
|
||||||
|
import { togglePostEditMode } from '../actions/Post/togglePostEditMode';
|
||||||
|
import {
|
||||||
|
changePostEditFormBoard,
|
||||||
|
changePostEditFormDescription,
|
||||||
|
changePostEditFormPostStatus,
|
||||||
|
changePostEditFormTitle
|
||||||
|
} from '../actions/Post/changePostEditForm';
|
||||||
|
|
||||||
import { requestLikes } from '../actions/Like/requestLikes';
|
import { requestLikes } from '../actions/Like/requestLikes';
|
||||||
import { changePostBoard } from '../actions/Post/changePostBoard';
|
|
||||||
import { changePostStatus } from '../actions/Post/changePostStatus';
|
|
||||||
import { submitFollow } from '../actions/Follow/submitFollow';
|
import { submitFollow } from '../actions/Follow/submitFollow';
|
||||||
import { requestFollow } from '../actions/Follow/requestFollow';
|
import { requestFollow } from '../actions/Follow/requestFollow';
|
||||||
import { requestPostStatusChanges } from '../actions/PostStatusChange/requestPostStatusChanges';
|
import { requestPostStatusChanges } from '../actions/PostStatusChange/requestPostStatusChanges';
|
||||||
@@ -14,9 +21,12 @@ import { State } from '../reducers/rootReducer';
|
|||||||
import PostP from '../components/Post/PostP';
|
import PostP from '../components/Post/PostP';
|
||||||
|
|
||||||
import { fromJavascriptDateToRailsString } from '../helpers/datetime';
|
import { fromJavascriptDateToRailsString } from '../helpers/datetime';
|
||||||
|
import { updatePost } from '../actions/Post/updatePost';
|
||||||
|
|
||||||
const mapStateToProps = (state: State) => ({
|
const mapStateToProps = (state: State) => ({
|
||||||
post: state.currentPost.item,
|
post: state.currentPost.item,
|
||||||
|
editMode: state.currentPost.editMode,
|
||||||
|
editForm: state.currentPost.editForm,
|
||||||
likes: state.currentPost.likes,
|
likes: state.currentPost.likes,
|
||||||
followed: state.currentPost.followed,
|
followed: state.currentPost.followed,
|
||||||
comments: state.currentPost.comments,
|
comments: state.currentPost.comments,
|
||||||
@@ -28,6 +38,37 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(requestPost(postId));
|
dispatch(requestPost(postId));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updatePost(
|
||||||
|
postId: number,
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
boardId: number,
|
||||||
|
postStatusId: number,
|
||||||
|
authenticityToken: string,
|
||||||
|
) {
|
||||||
|
return dispatch(updatePost(postId, title, description, boardId, postStatusId, authenticityToken));
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleEditMode() {
|
||||||
|
dispatch(togglePostEditMode());
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChangeEditFormTitle(title: string) {
|
||||||
|
dispatch(changePostEditFormTitle(title));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChangeEditFormDescription(description: string) {
|
||||||
|
dispatch(changePostEditFormDescription(description));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChangeEditFormBoard(boardId: number) {
|
||||||
|
dispatch(changePostEditFormBoard(boardId));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChangeEditFormPostStatus(postStatusId: number) {
|
||||||
|
dispatch(changePostEditFormPostStatus(postStatusId));
|
||||||
|
},
|
||||||
|
|
||||||
requestLikes(postId: number) {
|
requestLikes(postId: number) {
|
||||||
dispatch(requestLikes(postId));
|
dispatch(requestLikes(postId));
|
||||||
},
|
},
|
||||||
@@ -40,29 +81,21 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(requestPostStatusChanges(postId));
|
dispatch(requestPostStatusChanges(postId));
|
||||||
},
|
},
|
||||||
|
|
||||||
changePostBoard(postId: number, newBoardId: number, authenticityToken: string) {
|
deletePost(postId: number, authenticityToken: string) {
|
||||||
dispatch(changePostBoard(postId, newBoardId, authenticityToken));
|
return dispatch(deletePost(postId, authenticityToken));
|
||||||
},
|
},
|
||||||
|
|
||||||
changePostStatus(
|
postStatusChangeSubmitted(
|
||||||
postId: number,
|
|
||||||
newPostStatusId: number,
|
newPostStatusId: number,
|
||||||
userFullName: string,
|
userFullName: string,
|
||||||
userEmail: string,
|
userEmail: string,
|
||||||
authenticityToken: string
|
|
||||||
) {
|
) {
|
||||||
if (isNaN(newPostStatusId)) newPostStatusId = null;
|
dispatch(postStatusChangeSubmitted({
|
||||||
|
postStatusId: newPostStatusId,
|
||||||
dispatch(changePostStatus(postId, newPostStatusId, authenticityToken)).then(res => {
|
userFullName,
|
||||||
if (res && res.status !== 204) return;
|
userEmail,
|
||||||
|
createdAt: fromJavascriptDateToRailsString(new Date()),
|
||||||
dispatch(postStatusChangeSubmitted({
|
}));
|
||||||
postStatusId: newPostStatusId,
|
|
||||||
userFullName,
|
|
||||||
userEmail,
|
|
||||||
updatedAt: fromJavascriptDateToRailsString(new Date()),
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
submitFollow(postId: number, isFollow: boolean, authenticityToken: string) {
|
submitFollow(postId: number, isFollow: boolean, authenticityToken: string) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ interface IComment {
|
|||||||
isPostUpdate: boolean;
|
isPostUpdate: boolean;
|
||||||
userFullName: string;
|
userFullName: string;
|
||||||
userEmail: string;
|
userEmail: string;
|
||||||
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ interface IPost {
|
|||||||
commentsCount: number;
|
commentsCount: number;
|
||||||
hotness: number;
|
hotness: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
userEmail: string;
|
||||||
|
userFullName: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ interface IPostStatusChange {
|
|||||||
postStatusId: number;
|
postStatusId: number;
|
||||||
userFullName: string;
|
userFullName: string;
|
||||||
userEmail: string;
|
userEmail: string;
|
||||||
updatedAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IPostStatusChange;
|
export default IPostStatusChange;
|
||||||
@@ -5,6 +5,7 @@ interface ICommentJSON {
|
|||||||
is_post_update: boolean;
|
is_post_update: boolean;
|
||||||
user_full_name: string;
|
user_full_name: string;
|
||||||
user_email: string;
|
user_email: string;
|
||||||
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ interface IPostJSON {
|
|||||||
comments_count: number;
|
comments_count: number;
|
||||||
hotness: number;
|
hotness: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
|
user_email: string;
|
||||||
|
user_full_name: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ interface IPostStatusChangeJSON {
|
|||||||
post_status_id: number;
|
post_status_id: number;
|
||||||
user_full_name: string;
|
user_full_name: string;
|
||||||
user_email: string;
|
user_email: string;
|
||||||
updated_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IPostStatusChangeJSON;
|
export default IPostStatusChangeJSON;
|
||||||
@@ -3,6 +3,11 @@ import {
|
|||||||
COMMENT_REQUEST_SUCCESS,
|
COMMENT_REQUEST_SUCCESS,
|
||||||
} from '../actions/Comment/requestComment';
|
} from '../actions/Comment/requestComment';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CommentUpdateActionTypes,
|
||||||
|
COMMENT_UPDATE_SUCCESS,
|
||||||
|
} from '../actions/Comment/updateComment';
|
||||||
|
|
||||||
import IComment from '../interfaces/IComment';
|
import IComment from '../interfaces/IComment';
|
||||||
|
|
||||||
const initialState: IComment = {
|
const initialState: IComment = {
|
||||||
@@ -12,15 +17,17 @@ const initialState: IComment = {
|
|||||||
isPostUpdate: false,
|
isPostUpdate: false,
|
||||||
userFullName: '<Unknown user>',
|
userFullName: '<Unknown user>',
|
||||||
userEmail: 'example@example.com',
|
userEmail: 'example@example.com',
|
||||||
|
createdAt: undefined,
|
||||||
updatedAt: undefined,
|
updatedAt: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const commentReducer = (
|
const commentReducer = (
|
||||||
state = initialState,
|
state = initialState,
|
||||||
action: CommentRequestSuccessAction,
|
action: CommentRequestSuccessAction | CommentUpdateActionTypes,
|
||||||
): IComment => {
|
): IComment => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case COMMENT_REQUEST_SUCCESS:
|
case COMMENT_REQUEST_SUCCESS:
|
||||||
|
case COMMENT_UPDATE_SUCCESS:
|
||||||
return {
|
return {
|
||||||
id: action.comment.id,
|
id: action.comment.id,
|
||||||
body: action.comment.body,
|
body: action.comment.body,
|
||||||
@@ -28,6 +35,7 @@ const commentReducer = (
|
|||||||
isPostUpdate: action.comment.is_post_update,
|
isPostUpdate: action.comment.is_post_update,
|
||||||
userFullName: action.comment.user_full_name,
|
userFullName: action.comment.user_full_name,
|
||||||
userEmail: action.comment.user_email,
|
userEmail: action.comment.user_email,
|
||||||
|
createdAt: action.comment.created_at,
|
||||||
updatedAt: action.comment.updated_at,
|
updatedAt: action.comment.updated_at,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,13 @@ import {
|
|||||||
} from '../actions/Comment/submitComment';
|
} from '../actions/Comment/submitComment';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ToggleIsUpdateSuccessAction,
|
CommentDeleteActionTypes,
|
||||||
TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
COMMENT_DELETE_SUCCESS,
|
||||||
|
} from '../actions/Comment/deleteComment';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CommentUpdateActionTypes,
|
||||||
|
COMMENT_UPDATE_SUCCESS,
|
||||||
} from '../actions/Comment/updateComment';
|
} from '../actions/Comment/updateComment';
|
||||||
|
|
||||||
import commentReducer from './commentReducer';
|
import commentReducer from './commentReducer';
|
||||||
@@ -54,7 +59,8 @@ const commentsReducer = (
|
|||||||
CommentsRequestActionTypes |
|
CommentsRequestActionTypes |
|
||||||
HandleCommentRepliesType |
|
HandleCommentRepliesType |
|
||||||
CommentSubmitActionTypes |
|
CommentSubmitActionTypes |
|
||||||
ToggleIsUpdateSuccessAction
|
CommentUpdateActionTypes |
|
||||||
|
CommentDeleteActionTypes
|
||||||
): CommentsState => {
|
): CommentsState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case COMMENTS_REQUEST_START:
|
case COMMENTS_REQUEST_START:
|
||||||
@@ -103,17 +109,19 @@ const commentsReducer = (
|
|||||||
replyForms: replyFormsReducer(state.replyForms, action),
|
replyForms: replyFormsReducer(state.replyForms, action),
|
||||||
};
|
};
|
||||||
|
|
||||||
case TOGGLE_COMMENT_IS_UPDATE_SUCCESS:
|
case COMMENT_DELETE_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
items: state.items.filter(comment => comment.id !== action.commentId),
|
||||||
|
};
|
||||||
|
|
||||||
|
case COMMENT_UPDATE_SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
items:
|
items: state.items.map(comment => (
|
||||||
state.items.map(comment => {
|
comment.id === action.comment.id ? commentReducer(comment, action) : comment
|
||||||
if (comment.id === action.commentId) {
|
)),
|
||||||
comment.isPostUpdate = !comment.isPostUpdate;
|
};
|
||||||
return comment;
|
|
||||||
} else return comment;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -6,14 +6,21 @@ import {
|
|||||||
} from '../actions/Post/requestPost';
|
} from '../actions/Post/requestPost';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangePostBoardSuccessAction,
|
PostUpdateActionTypes,
|
||||||
CHANGE_POST_BOARD_SUCCESS,
|
POST_UPDATE_START,
|
||||||
} from '../actions/Post/changePostBoard';
|
POST_UPDATE_SUCCESS,
|
||||||
|
POST_UPDATE_FAILURE,
|
||||||
|
} from '../actions/Post/updatePost';
|
||||||
|
|
||||||
|
import { POST_TOGGLE_EDIT_MODE, PostToggleEditMode } from '../actions/Post/togglePostEditMode';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangePostStatusSuccessAction,
|
ChangePostEditFormActionTypes,
|
||||||
CHANGE_POST_STATUS_SUCCESS,
|
POST_CHANGE_EDIT_FORM_TITLE,
|
||||||
} from '../actions/Post/changePostStatus';
|
POST_CHANGE_EDIT_FORM_DESCRIPTION,
|
||||||
|
POST_CHANGE_EDIT_FORM_BOARD,
|
||||||
|
POST_CHANGE_EDIT_FORM_POST_STATUS,
|
||||||
|
} from '../actions/Post/changePostEditForm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LikesRequestActionTypes,
|
LikesRequestActionTypes,
|
||||||
@@ -49,8 +56,13 @@ import {
|
|||||||
} from '../actions/Comment/submitComment';
|
} from '../actions/Comment/submitComment';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ToggleIsUpdateSuccessAction,
|
CommentDeleteActionTypes,
|
||||||
TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
COMMENT_DELETE_SUCCESS,
|
||||||
|
} from '../actions/Comment/deleteComment';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CommentUpdateActionTypes,
|
||||||
|
COMMENT_UPDATE_SUCCESS,
|
||||||
} from '../actions/Comment/updateComment';
|
} from '../actions/Comment/updateComment';
|
||||||
|
|
||||||
import { FollowActionTypes, FOLLOW_SUBMIT_SUCCESS } from '../actions/Follow/submitFollow';
|
import { FollowActionTypes, FOLLOW_SUBMIT_SUCCESS } from '../actions/Follow/submitFollow';
|
||||||
@@ -78,11 +90,22 @@ import postStatusChangesReducer, { PostStatusChangesState } from './postStatusCh
|
|||||||
|
|
||||||
import IPost from '../interfaces/IPost';
|
import IPost from '../interfaces/IPost';
|
||||||
|
|
||||||
|
export interface PostEditFormState {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
boardId: number;
|
||||||
|
postStatusId?: number;
|
||||||
|
|
||||||
|
isUpdating: boolean;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface CurrentPostState {
|
interface CurrentPostState {
|
||||||
item: IPost;
|
item: IPost;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
|
editMode: boolean;
|
||||||
|
editForm: PostEditFormState;
|
||||||
likes: LikesState;
|
likes: LikesState;
|
||||||
followed: boolean;
|
followed: boolean;
|
||||||
comments: CommentsState;
|
comments: CommentsState;
|
||||||
@@ -93,6 +116,16 @@ const initialState: CurrentPostState = {
|
|||||||
item: postReducer(undefined, {} as PostRequestActionTypes),
|
item: postReducer(undefined, {} as PostRequestActionTypes),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: '',
|
error: '',
|
||||||
|
editMode: false,
|
||||||
|
editForm: {
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
boardId: 1,
|
||||||
|
postStatusId: 1,
|
||||||
|
|
||||||
|
isUpdating: false,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
likes: likesReducer(undefined, {} as LikesRequestActionTypes),
|
likes: likesReducer(undefined, {} as LikesRequestActionTypes),
|
||||||
followed: false,
|
followed: false,
|
||||||
comments: commentsReducer(undefined, {} as CommentsRequestActionTypes),
|
comments: commentsReducer(undefined, {} as CommentsRequestActionTypes),
|
||||||
@@ -103,14 +136,16 @@ const currentPostReducer = (
|
|||||||
state = initialState,
|
state = initialState,
|
||||||
action:
|
action:
|
||||||
PostRequestActionTypes |
|
PostRequestActionTypes |
|
||||||
ChangePostBoardSuccessAction |
|
PostUpdateActionTypes |
|
||||||
ChangePostStatusSuccessAction |
|
PostToggleEditMode |
|
||||||
|
ChangePostEditFormActionTypes |
|
||||||
LikesRequestActionTypes |
|
LikesRequestActionTypes |
|
||||||
LikeActionTypes |
|
LikeActionTypes |
|
||||||
CommentsRequestActionTypes |
|
CommentsRequestActionTypes |
|
||||||
HandleCommentRepliesType |
|
HandleCommentRepliesType |
|
||||||
CommentSubmitActionTypes |
|
CommentSubmitActionTypes |
|
||||||
ToggleIsUpdateSuccessAction |
|
CommentUpdateActionTypes |
|
||||||
|
CommentDeleteActionTypes |
|
||||||
FollowActionTypes |
|
FollowActionTypes |
|
||||||
FollowRequestActionTypes |
|
FollowRequestActionTypes |
|
||||||
PostStatusChangesRequestActionTypes |
|
PostStatusChangesRequestActionTypes |
|
||||||
@@ -138,11 +173,67 @@ const currentPostReducer = (
|
|||||||
error: action.error,
|
error: action.error,
|
||||||
};
|
};
|
||||||
|
|
||||||
case CHANGE_POST_BOARD_SUCCESS:
|
case POST_UPDATE_START:
|
||||||
case CHANGE_POST_STATUS_SUCCESS:
|
return {
|
||||||
|
...state, editForm: { ...state.editForm, isUpdating: true, error: '' }
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_UPDATE_SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
item: postReducer(state.item, action),
|
item: postReducer(state.item, action),
|
||||||
|
editForm: {
|
||||||
|
...state.editForm,
|
||||||
|
isUpdating: false,
|
||||||
|
},
|
||||||
|
editMode: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_UPDATE_FAILURE:
|
||||||
|
return {
|
||||||
|
...state, editForm: { ...state.editForm, isUpdating: false, error: action.error }
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_UPDATE_START:
|
||||||
|
return {
|
||||||
|
...state, editForm: { ...state.editForm, isUpdating: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_TOGGLE_EDIT_MODE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editMode: !state.editMode,
|
||||||
|
editForm: {
|
||||||
|
...state.editForm,
|
||||||
|
title: state.item.title,
|
||||||
|
description: state.item.description,
|
||||||
|
boardId: state.item.boardId,
|
||||||
|
postStatusId: state.item.postStatusId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_CHANGE_EDIT_FORM_TITLE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editForm: { ...state.editForm, title: action.title },
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_CHANGE_EDIT_FORM_DESCRIPTION:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editForm: { ...state.editForm, description: action.description },
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_CHANGE_EDIT_FORM_BOARD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editForm: { ...state.editForm, boardId: action.boardId },
|
||||||
|
};
|
||||||
|
|
||||||
|
case POST_CHANGE_EDIT_FORM_POST_STATUS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editForm: { ...state.editForm, postStatusId: action.postStatusId },
|
||||||
};
|
};
|
||||||
|
|
||||||
case LIKES_REQUEST_START:
|
case LIKES_REQUEST_START:
|
||||||
@@ -162,7 +253,8 @@ const currentPostReducer = (
|
|||||||
case COMMENT_SUBMIT_START:
|
case COMMENT_SUBMIT_START:
|
||||||
case COMMENT_SUBMIT_SUCCESS:
|
case COMMENT_SUBMIT_SUCCESS:
|
||||||
case COMMENT_SUBMIT_FAILURE:
|
case COMMENT_SUBMIT_FAILURE:
|
||||||
case TOGGLE_COMMENT_IS_UPDATE_SUCCESS:
|
case COMMENT_UPDATE_SUCCESS:
|
||||||
|
case COMMENT_DELETE_SUCCESS:
|
||||||
case TOGGLE_COMMENT_IS_POST_UPDATE_FLAG:
|
case TOGGLE_COMMENT_IS_POST_UPDATE_FLAG:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ import {
|
|||||||
} from '../actions/Post/requestPost';
|
} from '../actions/Post/requestPost';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangePostBoardSuccessAction,
|
PostUpdateActionTypes,
|
||||||
CHANGE_POST_BOARD_SUCCESS,
|
POST_UPDATE_SUCCESS,
|
||||||
} from '../actions/Post/changePostBoard';
|
} from '../actions/Post/updatePost';
|
||||||
|
|
||||||
import {
|
|
||||||
ChangePostStatusSuccessAction,
|
|
||||||
CHANGE_POST_STATUS_SUCCESS,
|
|
||||||
} from '../actions/Post/changePostStatus';
|
|
||||||
|
|
||||||
import IPost from '../interfaces/IPost';
|
import IPost from '../interfaces/IPost';
|
||||||
|
|
||||||
@@ -26,6 +21,8 @@ const initialState: IPost = {
|
|||||||
commentsCount: 0,
|
commentsCount: 0,
|
||||||
hotness: 0,
|
hotness: 0,
|
||||||
userId: 0,
|
userId: 0,
|
||||||
|
userEmail: '',
|
||||||
|
userFullName: '',
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,8 +30,7 @@ const postReducer = (
|
|||||||
state = initialState,
|
state = initialState,
|
||||||
action:
|
action:
|
||||||
PostRequestActionTypes |
|
PostRequestActionTypes |
|
||||||
ChangePostBoardSuccessAction |
|
PostUpdateActionTypes
|
||||||
ChangePostStatusSuccessAction,
|
|
||||||
): IPost => {
|
): IPost => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case POST_REQUEST_SUCCESS:
|
case POST_REQUEST_SUCCESS:
|
||||||
@@ -49,19 +45,18 @@ const postReducer = (
|
|||||||
commentsCount: action.post.comments_count,
|
commentsCount: action.post.comments_count,
|
||||||
hotness: action.post.hotness,
|
hotness: action.post.hotness,
|
||||||
userId: action.post.user_id,
|
userId: action.post.user_id,
|
||||||
|
userEmail: action.post.user_email,
|
||||||
|
userFullName: action.post.user_full_name,
|
||||||
createdAt: action.post.created_at,
|
createdAt: action.post.created_at,
|
||||||
};
|
};
|
||||||
|
|
||||||
case CHANGE_POST_BOARD_SUCCESS:
|
case POST_UPDATE_SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
boardId: action.newBoardId,
|
title: action.post.title,
|
||||||
};
|
description: action.post.description,
|
||||||
|
boardId: action.post.board_id,
|
||||||
case CHANGE_POST_STATUS_SUCCESS:
|
postStatusId: action.post.post_status_id,
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
postStatusId: action.newPostStatusId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const postStatusChangesReducer = (
|
|||||||
postStatusId: postStatusChange.post_status_id,
|
postStatusId: postStatusChange.post_status_id,
|
||||||
userFullName: postStatusChange.user_full_name,
|
userFullName: postStatusChange.user_full_name,
|
||||||
userEmail: postStatusChange.user_email,
|
userEmail: postStatusChange.user_email,
|
||||||
updatedAt: postStatusChange.updated_at,
|
createdAt: postStatusChange.created_at,
|
||||||
})),
|
})),
|
||||||
areLoading: false,
|
areLoading: false,
|
||||||
error: '',
|
error: '',
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ import {
|
|||||||
POSTS_REQUEST_FAILURE,
|
POSTS_REQUEST_FAILURE,
|
||||||
} from '../actions/Post/requestPosts';
|
} from '../actions/Post/requestPosts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PostDeleteActionTypes,
|
||||||
|
POST_DELETE_SUCCESS,
|
||||||
|
} from '../actions/Post/deletePost';
|
||||||
|
|
||||||
import { postRequestSuccess } from '../actions/Post/requestPost';
|
import { postRequestSuccess } from '../actions/Post/requestPost';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -49,6 +54,7 @@ const postsReducer = (
|
|||||||
state = initialState,
|
state = initialState,
|
||||||
action:
|
action:
|
||||||
PostsRequestActionTypes |
|
PostsRequestActionTypes |
|
||||||
|
PostDeleteActionTypes |
|
||||||
ChangeFiltersActionTypes |
|
ChangeFiltersActionTypes |
|
||||||
LikeActionTypes,
|
LikeActionTypes,
|
||||||
): PostsState => {
|
): PostsState => {
|
||||||
@@ -79,6 +85,12 @@ const postsReducer = (
|
|||||||
error: action.error,
|
error: action.error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case POST_DELETE_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
items: state.items.filter(post => post.id !== action.postId),
|
||||||
|
};
|
||||||
|
|
||||||
case SET_SEARCH_FILTER:
|
case SET_SEARCH_FILTER:
|
||||||
case SET_POST_STATUS_FILTER:
|
case SET_POST_STATUS_FILTER:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
.commentsContainer {
|
.commentsContainer {
|
||||||
@extend .my-3;
|
@extend .my-3;
|
||||||
|
|
||||||
|
a.commentLink {
|
||||||
|
color: $primary-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentForm {
|
||||||
|
@extend
|
||||||
|
.form-control,
|
||||||
|
.w-100,
|
||||||
|
.p-2,
|
||||||
|
.mr-2;
|
||||||
|
|
||||||
|
height: 80px;
|
||||||
|
|
||||||
|
border: thin solid grey;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
.newCommentForm {
|
.newCommentForm {
|
||||||
@extend
|
@extend
|
||||||
.d-flex,
|
.d-flex,
|
||||||
@@ -27,21 +51,6 @@
|
|||||||
.mr-2;
|
.mr-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.newCommentBody {
|
|
||||||
@extend
|
|
||||||
.form-control,
|
|
||||||
.w-100,
|
|
||||||
.p-2,
|
|
||||||
.mr-2;
|
|
||||||
|
|
||||||
height: 80px;
|
|
||||||
|
|
||||||
border: thin solid grey;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submitCommentButton {
|
.submitCommentButton {
|
||||||
@extend
|
@extend
|
||||||
.align-self-end;
|
.align-self-end;
|
||||||
@@ -52,6 +61,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editCommentForm {
|
||||||
|
textarea {
|
||||||
|
@extend .my-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
@extend
|
||||||
|
.d-flex,
|
||||||
|
.justify-content-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.commentsTitle {
|
.commentsTitle {
|
||||||
@extend
|
@extend
|
||||||
.text-secondary,
|
.text-secondary,
|
||||||
@@ -93,15 +114,6 @@
|
|||||||
|
|
||||||
.commentFooter {
|
.commentFooter {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
.commentLink {
|
|
||||||
color: $primary-color;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
@extend
|
@extend
|
||||||
.d-flex,
|
.d-flex,
|
||||||
.flex-column,
|
.flex-column,
|
||||||
.mr-3,
|
.mr-3;
|
||||||
.mt-2;
|
|
||||||
|
|
||||||
$like_button_size: 11px;
|
$like_button_size: 11px;
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,8 @@
|
|||||||
|
|
||||||
.postHeader {
|
.postHeader {
|
||||||
@extend
|
@extend
|
||||||
.d-flex;
|
.d-flex,
|
||||||
|
.mb-3;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@extend
|
@extend
|
||||||
@@ -128,9 +129,47 @@
|
|||||||
|
|
||||||
.postDescription {
|
.postDescription {
|
||||||
@extend
|
@extend
|
||||||
.my-3;
|
.my-4;
|
||||||
|
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
@extend .mb-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.postFooter {
|
||||||
|
.postAuthor {
|
||||||
|
@extend
|
||||||
|
.mutedText,
|
||||||
|
.mb-2;
|
||||||
|
|
||||||
|
.postAuthorAvatar { @extend .gravatar; }
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.postEditForm {
|
||||||
|
.form-control {
|
||||||
|
@extend .my-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.postEditFormButtons {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
a {
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,25 @@
|
|||||||
class CommentPolicy < ApplicationPolicy
|
class CommentPolicy < ApplicationPolicy
|
||||||
|
def permitted_attributes_for_create
|
||||||
|
if user.power_user?
|
||||||
|
[:body, :parent_id, :is_post_update]
|
||||||
|
else
|
||||||
|
[:body, :parent_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_attributes_for_update
|
||||||
|
if user.power_user?
|
||||||
|
[:body, :is_post_update]
|
||||||
|
else
|
||||||
|
[:body]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
user == record.user or user.power_user?
|
user == record.user or user.power_user?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
user == record.user or user.power_user?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -1,5 +1,21 @@
|
|||||||
class PostPolicy < ApplicationPolicy
|
class PostPolicy < ApplicationPolicy
|
||||||
|
def permitted_attributes_for_create
|
||||||
|
[:title, :description, :board_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_attributes_for_update
|
||||||
|
if user.power_user?
|
||||||
|
[:title, :description, :board_id, :post_status_id]
|
||||||
|
else
|
||||||
|
[:title, :description]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
user == record.user or user.power_user?
|
user == record.user or user.power_user?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
user == record.user or user.power_user?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
postStatuses: @post_statuses,
|
postStatuses: @post_statuses,
|
||||||
isLoggedIn: user_signed_in?,
|
isLoggedIn: user_signed_in?,
|
||||||
isPowerUser: user_signed_in? ? current_user.power_user? : false,
|
isPowerUser: user_signed_in? ? current_user.power_user? : false,
|
||||||
userFullName: user_signed_in? ? current_user.full_name : nil,
|
currentUserFullName: user_signed_in? ? current_user.full_name : nil,
|
||||||
userEmail: user_signed_in? ? current_user.email : nil,
|
currentUserEmail: user_signed_in? ? current_user.email : nil,
|
||||||
authenticityToken: form_authenticity_token,
|
authenticityToken: form_authenticity_token,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ en:
|
|||||||
other: '%{count} comments'
|
other: '%{count} comments'
|
||||||
no_status: 'No status'
|
no_status: 'No status'
|
||||||
loading: 'Loading...'
|
loading: 'Loading...'
|
||||||
|
confirmation: 'Are you sure?'
|
||||||
|
edited: 'Edited'
|
||||||
buttons:
|
buttons:
|
||||||
edit: 'Edit'
|
edit: 'Edit'
|
||||||
delete: 'Delete'
|
delete: 'Delete'
|
||||||
@@ -74,6 +76,7 @@ en:
|
|||||||
empty: 'There are no posts'
|
empty: 'There are no posts'
|
||||||
post:
|
post:
|
||||||
edit_button: 'Edit'
|
edit_button: 'Edit'
|
||||||
|
published_by: 'Published by'
|
||||||
post_status_select:
|
post_status_select:
|
||||||
no_post_status: 'None'
|
no_post_status: 'None'
|
||||||
updates_box:
|
updates_box:
|
||||||
@@ -124,7 +127,7 @@ en:
|
|||||||
title: 'Roadmap'
|
title: 'Roadmap'
|
||||||
title2: 'Not in roadmap'
|
title2: 'Not in roadmap'
|
||||||
empty: 'The roadmap is empty.'
|
empty: 'The roadmap is empty.'
|
||||||
help: 'You can add new statuses to the roadmap by dragging them from the section below. If you want to add a new status or change their order, go to Site settings -> Statuses.'
|
help: 'You can add statuses to the roadmap by dragging them from the section below. If you instead want to create a new status or change their order, go to Site settings -> Statuses.'
|
||||||
user_mailer:
|
user_mailer:
|
||||||
opening_greeting: 'Hello!'
|
opening_greeting: 'Hello!'
|
||||||
closing_greeting: 'Have a great day!'
|
closing_greeting: 'Have a great day!'
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ it:
|
|||||||
other: '%{count} commenti'
|
other: '%{count} commenti'
|
||||||
no_status: 'Nessuno stato'
|
no_status: 'Nessuno stato'
|
||||||
loading: 'Caricamento...'
|
loading: 'Caricamento...'
|
||||||
|
confirmation: 'Sei sicuro?'
|
||||||
|
edited: 'Modificato'
|
||||||
buttons:
|
buttons:
|
||||||
edit: 'Modifica'
|
edit: 'Modifica'
|
||||||
delete: 'Elimina'
|
delete: 'Elimina'
|
||||||
@@ -74,6 +76,7 @@ it:
|
|||||||
empty: 'Non ci sono post'
|
empty: 'Non ci sono post'
|
||||||
post:
|
post:
|
||||||
edit_button: 'Modifica'
|
edit_button: 'Modifica'
|
||||||
|
published_by: 'Pubblicato da'
|
||||||
post_status_select:
|
post_status_select:
|
||||||
no_post_status: 'Nessuno'
|
no_post_status: 'Nessuno'
|
||||||
updates_box:
|
updates_box:
|
||||||
@@ -124,7 +127,7 @@ it:
|
|||||||
title: 'Roadmap'
|
title: 'Roadmap'
|
||||||
title2: 'Non mostrati in roadmap'
|
title2: 'Non mostrati in roadmap'
|
||||||
empty: 'La roadmap è vuota.'
|
empty: 'La roadmap è vuota.'
|
||||||
help: "Puoi aggiungere nuovi stati alla roadmap trascinandoli dalla sezione sottostante. Se vuoi aggiungere un nuovo stato o cambiarne l'ordine, vai in Impostazioni sito -> Stati."
|
help: "Puoi aggiungere stati alla roadmap trascinandoli dalla sezione sottostante. Se invece vuoi creare un nuovo stato o cambiarne l'ordine, vai in Impostazioni sito -> Stati."
|
||||||
user_mailer:
|
user_mailer:
|
||||||
opening_greeting: 'Ciao!'
|
opening_greeting: 'Ciao!'
|
||||||
closing_greeting: 'Buona giornata!'
|
closing_greeting: 'Buona giornata!'
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
devise_for :users
|
devise_for :users
|
||||||
|
|
||||||
resources :posts, only: [:index, :create, :show, :update] do
|
resources :posts, only: [:index, :create, :show, :update, :destroy] do
|
||||||
resource :follows, only: [:create, :destroy]
|
resource :follows, only: [:create, :destroy]
|
||||||
resources :follows, only: [:index]
|
resources :follows, only: [:index]
|
||||||
resource :likes, only: [:create, :destroy]
|
resource :likes, only: [:create, :destroy]
|
||||||
resources :likes, only: [:index]
|
resources :likes, only: [:index]
|
||||||
resources :comments, only: [:index, :create, :update]
|
resources :comments, only: [:index, :create, :update, :destroy]
|
||||||
resources :post_status_changes, only: [:index]
|
resources :post_status_changes, only: [:index]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
0
script/rspec-no-system-specs.sh
Normal file → Executable file
0
script/rspec-no-system-specs.sh
Normal file → Executable file
@@ -11,10 +11,12 @@ RSpec.describe 'comments routing', :aggregate_failures, type: :routing do
|
|||||||
expect(patch: '/posts/1/comments/1').to route_to(
|
expect(patch: '/posts/1/comments/1').to route_to(
|
||||||
controller: 'comments', action: 'update', post_id: "1", id: "1"
|
controller: 'comments', action: 'update', post_id: "1", id: "1"
|
||||||
)
|
)
|
||||||
|
expect(delete: '/posts/1/comments/1').to route_to(
|
||||||
|
controller: 'comments', action: 'destroy', post_id: "1", id: "1"
|
||||||
|
)
|
||||||
|
|
||||||
expect(get: '/posts/1/comments/1').not_to be_routable
|
expect(get: '/posts/1/comments/1').not_to be_routable
|
||||||
expect(get: '/posts/1/comments/new').not_to be_routable
|
expect(get: '/posts/1/comments/new').not_to be_routable
|
||||||
expect(get: '/posts/1/comments/1/edit').not_to be_routable
|
expect(get: '/posts/1/comments/1/edit').not_to be_routable
|
||||||
expect(delete: '/posts/1/comments/1').not_to be_routable
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -14,11 +14,13 @@ RSpec.describe 'posts routing', :aggregate_failures, type: :routing do
|
|||||||
expect(patch: '/posts/1').to route_to(
|
expect(patch: '/posts/1').to route_to(
|
||||||
controller: 'posts', action: 'update', id: '1'
|
controller: 'posts', action: 'update', id: '1'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(get: '/posts/new').not_to route_to(
|
expect(get: '/posts/new').not_to route_to(
|
||||||
controller: 'posts', action: 'new'
|
controller: 'posts', action: 'new'
|
||||||
)
|
)
|
||||||
|
expect(delete: '/posts/1').to route_to(
|
||||||
|
controller: 'posts', action: 'destroy', id: '1'
|
||||||
|
)
|
||||||
|
|
||||||
expect(get: '/posts/1/edit').not_to be_routable
|
expect(get: '/posts/1/edit').not_to be_routable
|
||||||
expect(delete: '/posts/1').not_to be_routable
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Reference in New Issue
Block a user