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
|
||||
before_action :authenticate_user!, only: [:create, :update]
|
||||
before_action :authenticate_user!, only: [:create, :update, :destroy]
|
||||
|
||||
def index
|
||||
comments = Comment
|
||||
@@ -8,58 +8,80 @@ class CommentsController < ApplicationController
|
||||
:body,
|
||||
:parent_id,
|
||||
:is_post_update,
|
||||
:created_at,
|
||||
:updated_at,
|
||||
'users.full_name as user_full_name',
|
||||
'users.email as user_email',
|
||||
)
|
||||
.where(post_id: params[:post_id])
|
||||
.left_outer_joins(:user)
|
||||
.order(updated_at: :desc)
|
||||
.order(created_at: :desc)
|
||||
|
||||
render json: comments
|
||||
end
|
||||
|
||||
def create
|
||||
comment = Comment.new(comment_params)
|
||||
@comment = Comment.new
|
||||
@comment.assign_attributes(comment_create_params)
|
||||
|
||||
if comment.save
|
||||
SendNotificationForCommentWorkflow.new(comment: comment).run
|
||||
if @comment.save
|
||||
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 }
|
||||
), status: :created
|
||||
else
|
||||
render json: {
|
||||
error: comment.errors.full_messages
|
||||
error: @comment.errors.full_messages
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
comment = Comment.find(params[:id])
|
||||
authorize comment
|
||||
comment.assign_attributes(comment_params)
|
||||
@comment = Comment.find(params[:id])
|
||||
authorize @comment
|
||||
|
||||
if comment.save
|
||||
render json: comment.attributes.merge(
|
||||
{ user_full_name: current_user.full_name, user_email: current_user.email }
|
||||
if @comment.update(comment_update_params)
|
||||
render json: @comment.attributes.merge(
|
||||
{ user_full_name: @comment.user.full_name, user_email: @comment.user.email }
|
||||
)
|
||||
else
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def comment_params
|
||||
def comment_create_params
|
||||
params
|
||||
.require(:comment)
|
||||
.permit(:body, :parent_id, :is_post_update)
|
||||
.permit(policy(@comment).permitted_attributes_for_create)
|
||||
.merge(
|
||||
user_id: current_user.id,
|
||||
post_id: params[:post_id]
|
||||
)
|
||||
end
|
||||
|
||||
def comment_update_params
|
||||
params
|
||||
.require(:comment)
|
||||
.permit(policy(@comment).permitted_attributes_for_update)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,13 +3,13 @@ class PostStatusChangesController < ApplicationController
|
||||
post_status_changes = PostStatusChange
|
||||
.select(
|
||||
:post_status_id,
|
||||
:updated_at,
|
||||
:created_at,
|
||||
'users.full_name as user_full_name',
|
||||
'users.email as user_email',
|
||||
)
|
||||
.where(post_id: params[:post_id])
|
||||
.left_outer_joins(:user)
|
||||
.order(updated_at: :asc)
|
||||
.order(created_at: :asc)
|
||||
|
||||
render json: post_status_changes
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class PostsController < ApplicationController
|
||||
before_action :authenticate_user!, only: [:create, :update]
|
||||
before_action :authenticate_user!, only: [:create, :update, :destroy]
|
||||
|
||||
def index
|
||||
posts = Post
|
||||
@@ -25,21 +25,37 @@ class PostsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
post = Post.new(post_params)
|
||||
@post = Post.new
|
||||
@post.assign_attributes(post_create_params)
|
||||
|
||||
if post.save
|
||||
Follow.create(post_id: post.id, user_id: current_user.id)
|
||||
if @post.save
|
||||
Follow.create(post_id: @post.id, user_id: current_user.id)
|
||||
|
||||
render json: post, status: :created
|
||||
render json: @post, status: :created
|
||||
else
|
||||
render json: {
|
||||
error: post.errors.full_messages
|
||||
error: @post.errors.full_messages
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
@board = @post.board
|
||||
|
||||
@@ -51,35 +67,41 @@ class PostsController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
post = Post.find(params[:id])
|
||||
authorize post
|
||||
|
||||
post.board_id = params[:post][:board_id] if params[:post].has_key?(:board_id)
|
||||
@post = Post.find(params[:id])
|
||||
authorize @post
|
||||
|
||||
post_status_changed = false
|
||||
|
||||
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
|
||||
@post.assign_attributes(post_update_params)
|
||||
|
||||
if post.save
|
||||
if post_status_changed
|
||||
if @post.save
|
||||
if @post.post_status_id_previously_changed?
|
||||
PostStatusChange.create(
|
||||
user_id: current_user.id,
|
||||
post_id: post.id,
|
||||
post_status_id: post.post_status_id
|
||||
post_id: @post.id,
|
||||
post_status_id: @post.post_status_id
|
||||
)
|
||||
|
||||
send_notifications(post)
|
||||
|
||||
UserMailer.notify_followers_of_post_status_change(post: @post).deliver_later
|
||||
end
|
||||
|
||||
render json: post, status: :no_content
|
||||
render json: @post
|
||||
else
|
||||
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
|
||||
end
|
||||
end
|
||||
@@ -95,14 +117,16 @@ class PostsController < ApplicationController
|
||||
.except(:page, :search)
|
||||
end
|
||||
|
||||
def post_params
|
||||
def post_create_params
|
||||
params
|
||||
.require(:post)
|
||||
.permit(:title, :description, :board_id)
|
||||
.permit(policy(@post).permitted_attributes_for_create)
|
||||
.merge(user_id: current_user.id)
|
||||
end
|
||||
|
||||
def send_notifications(post)
|
||||
UserMailer.notify_followers_of_post_status_change(post: post).deliver_later
|
||||
def post_update_params
|
||||
params
|
||||
.require(:post)
|
||||
.permit(policy(@post).permitted_attributes_for_update)
|
||||
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 { ThunkAction } from "redux-thunk";
|
||||
|
||||
import HttpStatus from "../../constants/http_status";
|
||||
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 interface ToggleIsUpdateSuccessAction {
|
||||
type: typeof TOGGLE_COMMENT_IS_UPDATE_SUCCESS;
|
||||
commentId: number;
|
||||
export const COMMENT_UPDATE_START = 'COMMENT_UPDATE_START';
|
||||
interface CommentUpdateStartAction {
|
||||
type: typeof COMMENT_UPDATE_START;
|
||||
}
|
||||
|
||||
const toggleIsUpdateSuccess = (
|
||||
commentId: number,
|
||||
): ToggleIsUpdateSuccessAction => ({
|
||||
type: TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
||||
commentId,
|
||||
export const COMMENT_UPDATE_SUCCESS = 'COMMENT_UPDATE_SUCCESS';
|
||||
interface CommentUpdateSuccessAction {
|
||||
type: typeof COMMENT_UPDATE_SUCCESS;
|
||||
comment: ICommentJSON;
|
||||
}
|
||||
|
||||
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,
|
||||
commentId: number,
|
||||
currentIsPostUpdate: boolean,
|
||||
body: string,
|
||||
isPostUpdate: boolean,
|
||||
authenticityToken: string,
|
||||
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||
dispatch(commentUpdateStart());
|
||||
|
||||
try {
|
||||
const response = await fetch(`/posts/${postId}/comments/${commentId}`, {
|
||||
const res = await fetch(`/posts/${postId}/comments/${commentId}`, {
|
||||
method: 'PATCH',
|
||||
headers: buildRequestHeaders(authenticityToken),
|
||||
body: JSON.stringify({
|
||||
comment: {
|
||||
is_post_update: !currentIsPostUpdate,
|
||||
body,
|
||||
is_post_update: isPostUpdate,
|
||||
},
|
||||
})
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (response.status === 200) {
|
||||
dispatch(toggleIsUpdateSuccess(commentId));
|
||||
if (res.status === HttpStatus.OK) {
|
||||
dispatch(commentUpdateSuccess(json));
|
||||
} else {
|
||||
dispatch(commentUpdateFailure(json.error));
|
||||
}
|
||||
|
||||
return Promise.resolve(res);
|
||||
} 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 NewComment from './NewComment';
|
||||
import Separator from '../common/Separator';
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
|
||||
import { ReplyFormState } from '../../reducers/replyFormReducer';
|
||||
|
||||
import friendlyDate from '../../helpers/datetime';
|
||||
import CommentEditForm from './CommentEditForm';
|
||||
import CommentFooter from './CommentFooter';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
@@ -17,119 +16,147 @@ interface Props {
|
||||
isPostUpdate: boolean;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
replyForm: ReplyFormState;
|
||||
handleToggleCommentReply(): void;
|
||||
handleCommentReplyBodyChange(e: React.FormEvent): void;
|
||||
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
||||
|
||||
handleSubmitComment(body: string, parentId: number, isPostUpdate: boolean): void;
|
||||
handleUpdateComment(commentId: number, body: string, isPostUpdate: boolean, onSuccess: Function): void;
|
||||
handleDeleteComment(id: number): void;
|
||||
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
currentUserEmail: string;
|
||||
}
|
||||
|
||||
const Comment = ({
|
||||
id,
|
||||
body,
|
||||
isPostUpdate,
|
||||
userFullName,
|
||||
userEmail,
|
||||
updatedAt,
|
||||
interface State {
|
||||
editMode: boolean;
|
||||
}
|
||||
|
||||
replyForm,
|
||||
handleToggleCommentReply,
|
||||
handleCommentReplyBodyChange,
|
||||
handleToggleIsCommentUpdate,
|
||||
handleSubmitComment,
|
||||
class Comment extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
currentUserEmail,
|
||||
}: Props) => (
|
||||
<div className="comment">
|
||||
<div className="commentHeader">
|
||||
<Gravatar email={userEmail} size={28} className="gravatar" />
|
||||
<span className="commentAuthor">{userFullName}</span>
|
||||
{
|
||||
isPostUpdate ?
|
||||
<span className="postUpdateBadge">
|
||||
{I18n.t('post.comments.post_update_badge')}
|
||||
</span>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
this.state = {
|
||||
editMode: false,
|
||||
};
|
||||
|
||||
<ReactMarkdown
|
||||
className="commentBody"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{body}
|
||||
</ReactMarkdown>
|
||||
this.toggleEditMode = this.toggleEditMode.bind(this);
|
||||
this._handleUpdateComment = this._handleUpdateComment.bind(this);
|
||||
}
|
||||
|
||||
<div className="commentFooter">
|
||||
<a className="commentReplyButton commentLink" onClick={handleToggleCommentReply}>
|
||||
toggleEditMode() {
|
||||
this.setState({editMode: !this.state.editMode});
|
||||
}
|
||||
|
||||
_handleUpdateComment(body: string, isPostUpdate: boolean) {
|
||||
this.props.handleUpdateComment(
|
||||
this.props.id,
|
||||
body,
|
||||
isPostUpdate,
|
||||
this.toggleEditMode,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
body,
|
||||
isPostUpdate,
|
||||
userFullName,
|
||||
userEmail,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
|
||||
replyForm,
|
||||
handleToggleCommentReply,
|
||||
handleCommentReplyBodyChange,
|
||||
|
||||
handleSubmitComment,
|
||||
handleDeleteComment,
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
currentUserEmail,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="comment">
|
||||
<div className="commentHeader">
|
||||
<Gravatar email={userEmail} size={28} className="gravatar" />
|
||||
<span className="commentAuthor">{userFullName}</span>
|
||||
{
|
||||
isPostUpdate ?
|
||||
<span className="postUpdateBadge">
|
||||
{I18n.t('post.comments.post_update_badge')}
|
||||
</span>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
this.state.editMode ?
|
||||
<CommentEditForm
|
||||
id={id}
|
||||
initialBody={body}
|
||||
initialIsPostUpdate={isPostUpdate}
|
||||
|
||||
isPowerUser={isPowerUser}
|
||||
|
||||
handleUpdateComment={this._handleUpdateComment}
|
||||
toggleEditMode={this.toggleEditMode}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<ReactMarkdown
|
||||
className="commentBody"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{body}
|
||||
</ReactMarkdown>
|
||||
|
||||
<CommentFooter
|
||||
id={id}
|
||||
createdAt={createdAt}
|
||||
updatedAt={updatedAt}
|
||||
replyForm={replyForm}
|
||||
isPowerUser={isPowerUser}
|
||||
currentUserEmail={currentUserEmail}
|
||||
commentAuthorEmail={userEmail}
|
||||
|
||||
handleDeleteComment={handleDeleteComment}
|
||||
handleToggleCommentReply={handleToggleCommentReply}
|
||||
toggleEditMode={this.toggleEditMode}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{
|
||||
replyForm.isOpen ?
|
||||
I18n.t('common.buttons.cancel')
|
||||
:
|
||||
I18n.t('post.comments.reply_button')
|
||||
<NewComment
|
||||
body={replyForm.body}
|
||||
parentId={id}
|
||||
postUpdateFlagValue={replyForm.isPostUpdate}
|
||||
isSubmitting={replyForm.isSubmitting}
|
||||
error={replyForm.error}
|
||||
handleChange={handleCommentReplyBodyChange}
|
||||
handlePostUpdateFlag={() => null}
|
||||
handleSubmit={handleSubmitComment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userEmail={currentUserEmail}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</a>
|
||||
{
|
||||
isPowerUser ?
|
||||
<>
|
||||
<Separator />
|
||||
<a
|
||||
onClick={() => handleToggleIsCommentUpdate(id, isPostUpdate)}
|
||||
className="commentLink"
|
||||
>
|
||||
{ 'Post update: ' + (isPostUpdate ? 'yes' : 'no') }
|
||||
</a>
|
||||
<Separator />
|
||||
<a href={`/admin/comments/${id}/edit`} className="commentLink" data-turbolinks="false">
|
||||
{I18n.t('common.buttons.edit')}
|
||||
</a>
|
||||
<Separator />
|
||||
<a
|
||||
href={`/admin/comments/${id}`}
|
||||
className="commentLink"
|
||||
data-method="delete"
|
||||
data-confirm="Are you sure?"
|
||||
data-turbolinks="false">
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Separator />
|
||||
<MutedText>{friendlyDate(updatedAt)}</MutedText>
|
||||
</div>
|
||||
{
|
||||
replyForm.isOpen ?
|
||||
<NewComment
|
||||
body={replyForm.body}
|
||||
parentId={id}
|
||||
postUpdateFlagValue={replyForm.isPostUpdate}
|
||||
isSubmitting={replyForm.isSubmitting}
|
||||
error={replyForm.error}
|
||||
handleChange={handleCommentReplyBodyChange}
|
||||
handlePostUpdateFlag={() => null}
|
||||
handleSubmit={handleSubmitComment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userEmail={currentUserEmail}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
100
app/javascript/components/Comments/CommentEditForm.tsx
Normal file
100
app/javascript/components/Comments/CommentEditForm.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
import Button from '../common/Button';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
initialBody: string;
|
||||
initialIsPostUpdate: boolean;
|
||||
|
||||
isPowerUser: boolean;
|
||||
|
||||
handleUpdateComment(body: string, isPostUpdate: boolean): void;
|
||||
toggleEditMode(): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
body: string;
|
||||
isPostUpdate: boolean;
|
||||
}
|
||||
|
||||
class CommentEditForm extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
body: '',
|
||||
isPostUpdate: false,
|
||||
};
|
||||
|
||||
this.handleCommentBodyChange = this.handleCommentBodyChange.bind(this);
|
||||
this.handleCommentIsPostUpdateChange = this.handleCommentIsPostUpdateChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
body: this.props.initialBody,
|
||||
isPostUpdate: this.props.initialIsPostUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
handleCommentBodyChange(newCommentBody: string) {
|
||||
this.setState({ body: newCommentBody });
|
||||
}
|
||||
|
||||
handleCommentIsPostUpdateChange(newIsPostUpdate: boolean) {
|
||||
this.setState({ isPostUpdate: newIsPostUpdate });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, isPowerUser, handleUpdateComment, toggleEditMode } = this.props;
|
||||
const { body, isPostUpdate } = this.state;
|
||||
|
||||
return (
|
||||
<div className="editCommentForm">
|
||||
<textarea
|
||||
value={body}
|
||||
onChange={e => this.handleCommentBodyChange(e.target.value)}
|
||||
className="commentForm"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
{
|
||||
isPowerUser ?
|
||||
<>
|
||||
<input
|
||||
id={`isPostUpdateFlagComment${id}`}
|
||||
type="checkbox"
|
||||
onChange={e => this.handleCommentIsPostUpdateChange(e.target.checked)}
|
||||
checked={isPostUpdate || false}
|
||||
/>
|
||||
|
||||
<label htmlFor={`isPostUpdateFlagComment${id}`}>
|
||||
{I18n.t('post.new_comment.is_post_update')}
|
||||
</label>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a className="commentLink" onClick={toggleEditMode}>
|
||||
{ I18n.t('common.buttons.cancel') }
|
||||
</a>
|
||||
|
||||
<Button
|
||||
onClick={() => handleUpdateComment(body, isPostUpdate)}
|
||||
>
|
||||
{ I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CommentEditForm;
|
||||
78
app/javascript/components/Comments/CommentFooter.tsx
Normal file
78
app/javascript/components/Comments/CommentFooter.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import Separator from '../common/Separator';
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
import friendlyDate from '../../helpers/datetime';
|
||||
import { ReplyFormState } from '../../reducers/replyFormReducer';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
replyForm: ReplyFormState;
|
||||
isPowerUser: boolean;
|
||||
currentUserEmail: string;
|
||||
commentAuthorEmail: string;
|
||||
|
||||
handleDeleteComment(id: number): void;
|
||||
handleToggleCommentReply(): void;
|
||||
toggleEditMode(): void;
|
||||
}
|
||||
|
||||
const CommentFooter = ({
|
||||
id,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
replyForm,
|
||||
isPowerUser,
|
||||
currentUserEmail,
|
||||
commentAuthorEmail,
|
||||
|
||||
handleDeleteComment,
|
||||
handleToggleCommentReply,
|
||||
toggleEditMode,
|
||||
}: Props) => (
|
||||
<div className="commentFooter">
|
||||
<a className="commentReplyButton commentLink" onClick={handleToggleCommentReply}>
|
||||
{
|
||||
replyForm.isOpen ?
|
||||
I18n.t('common.buttons.cancel')
|
||||
:
|
||||
I18n.t('post.comments.reply_button')
|
||||
}
|
||||
</a>
|
||||
{
|
||||
isPowerUser || currentUserEmail === commentAuthorEmail ?
|
||||
<>
|
||||
<Separator />
|
||||
<a onClick={toggleEditMode} className="commentLink">
|
||||
{I18n.t('common.buttons.edit')}
|
||||
</a>
|
||||
|
||||
<Separator />
|
||||
<a
|
||||
onClick={() => confirm(I18n.t('common.confirmation')) && handleDeleteComment(id)}
|
||||
className="commentLink">
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Separator />
|
||||
<MutedText>{friendlyDate(createdAt)}</MutedText>
|
||||
|
||||
{
|
||||
createdAt !== updatedAt ?
|
||||
<>
|
||||
<Separator />
|
||||
<MutedText>{ I18n.t('common.edited').toLowerCase() }</MutedText>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CommentFooter;
|
||||
@@ -13,8 +13,10 @@ interface Props {
|
||||
|
||||
toggleCommentReply(commentId: number): void;
|
||||
setCommentReplyBody(commentId: number, body: string): void;
|
||||
handleToggleIsCommentUpdate(commentId: number, currentIsPostUpdate: boolean): void;
|
||||
|
||||
handleSubmitComment(body: string, parentId: number, isPostUpdate: boolean): void;
|
||||
handleUpdateComment(commentId: number, body: string, isPostUpdate: boolean, onSuccess: Function): void;
|
||||
handleDeleteComment(id: number): void;
|
||||
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
@@ -29,8 +31,9 @@ const CommentList = ({
|
||||
|
||||
toggleCommentReply,
|
||||
setCommentReplyBody,
|
||||
handleToggleIsCommentUpdate,
|
||||
handleSubmitComment,
|
||||
handleUpdateComment,
|
||||
handleDeleteComment,
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
@@ -49,8 +52,11 @@ const CommentList = ({
|
||||
setCommentReplyBody(comment.id, (e.target as HTMLTextAreaElement).value)
|
||||
)
|
||||
}
|
||||
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
||||
|
||||
handleSubmitComment={handleSubmitComment}
|
||||
handleUpdateComment={handleUpdateComment}
|
||||
handleDeleteComment={handleDeleteComment}
|
||||
|
||||
{...comment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
@@ -66,8 +72,10 @@ const CommentList = ({
|
||||
|
||||
toggleCommentReply={toggleCommentReply}
|
||||
setCommentReplyBody={setCommentReplyBody}
|
||||
handleToggleIsCommentUpdate={handleToggleIsCommentUpdate}
|
||||
|
||||
handleSubmitComment={handleSubmitComment}
|
||||
handleUpdateComment={handleUpdateComment}
|
||||
handleDeleteComment={handleDeleteComment}
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
|
||||
@@ -26,12 +26,7 @@ interface Props {
|
||||
toggleCommentReply(commentId: number): void;
|
||||
setCommentReplyBody(commentId: number, body: string): void;
|
||||
toggleCommentIsPostUpdateFlag(): void;
|
||||
toggleCommentIsPostUpdate(
|
||||
postId: number,
|
||||
commentId: number,
|
||||
currentIsPostUpdate: boolean,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
|
||||
submitComment(
|
||||
postId: number,
|
||||
body: string,
|
||||
@@ -39,6 +34,19 @@ interface Props {
|
||||
isPostUpdate: boolean,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
updateComment(
|
||||
postId: number,
|
||||
commentId: number,
|
||||
body: string,
|
||||
isPostUpdate: boolean,
|
||||
onSuccess: Function,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
deleteComment(
|
||||
postId: number,
|
||||
commentId: number,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
}
|
||||
|
||||
class CommentsP extends React.Component<Props> {
|
||||
@@ -46,15 +54,6 @@ class CommentsP extends React.Component<Props> {
|
||||
this.props.requestComments(this.props.postId);
|
||||
}
|
||||
|
||||
_handleToggleIsCommentUpdate = (commentId: number, currentIsPostUpdate: boolean) => {
|
||||
this.props.toggleCommentIsPostUpdate(
|
||||
this.props.postId,
|
||||
commentId,
|
||||
currentIsPostUpdate,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
_handleSubmitComment = (body: string, parentId: number, isPostUpdate: boolean) => {
|
||||
this.props.submitComment(
|
||||
this.props.postId,
|
||||
@@ -65,6 +64,25 @@ class CommentsP extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
_handleUpdateComment = (commentId: number, body: string, isPostUpdate: boolean, onSuccess: Function) => {
|
||||
this.props.updateComment(
|
||||
this.props.postId,
|
||||
commentId,
|
||||
body,
|
||||
isPostUpdate,
|
||||
onSuccess,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
_handleDeleteComment = (commentId: number) => {
|
||||
this.props.deleteComment(
|
||||
this.props.postId,
|
||||
commentId,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isLoggedIn,
|
||||
@@ -118,8 +136,9 @@ class CommentsP extends React.Component<Props> {
|
||||
replyForms={replyForms}
|
||||
toggleCommentReply={toggleCommentReply}
|
||||
setCommentReplyBody={setCommentReplyBody}
|
||||
handleToggleIsCommentUpdate={this._handleToggleIsCommentUpdate}
|
||||
handleSubmitComment={this._handleSubmitComment}
|
||||
handleUpdateComment={this._handleUpdateComment}
|
||||
handleDeleteComment={this._handleDeleteComment}
|
||||
parentId={null}
|
||||
level={1}
|
||||
isLoggedIn={isLoggedIn}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import NewCommentUpdateSection from './NewCommentUpdateSection';
|
||||
|
||||
@@ -52,7 +52,7 @@ const NewComment = ({
|
||||
value={body}
|
||||
onChange={handleChange}
|
||||
placeholder={I18n.t('post.new_comment.body_placeholder')}
|
||||
className="newCommentBody"
|
||||
className="commentForm"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleSubmit(body, parentId, postUpdateFlagValue)}
|
||||
|
||||
107
app/javascript/components/Post/PostEditForm.tsx
Normal file
107
app/javascript/components/Post/PostEditForm.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import PostBoardSelect from './PostBoardSelect';
|
||||
import PostStatusSelect from './PostStatusSelect';
|
||||
|
||||
import IPostStatus from '../../interfaces/IPostStatus';
|
||||
import IBoard from '../../interfaces/IBoard';
|
||||
import Button from '../common/Button';
|
||||
import Spinner from '../common/Spinner';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
boardId: number;
|
||||
postStatusId?: number;
|
||||
|
||||
isUpdating: boolean;
|
||||
error: string;
|
||||
|
||||
handleChangeTitle(title: string): void;
|
||||
handleChangeDescription(description: string): void;
|
||||
handleChangeBoard(boardId: number): void;
|
||||
handleChangePostStatus(postStatusId: number): void;
|
||||
|
||||
isPowerUser: boolean;
|
||||
boards: Array<IBoard>;
|
||||
postStatuses: Array<IPostStatus>;
|
||||
|
||||
toggleEditMode(): void;
|
||||
handleUpdatePost(
|
||||
title: string,
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
): void;
|
||||
}
|
||||
|
||||
const PostEditForm = ({
|
||||
title,
|
||||
description,
|
||||
boardId,
|
||||
postStatusId,
|
||||
|
||||
isUpdating,
|
||||
error,
|
||||
|
||||
handleChangeTitle,
|
||||
handleChangeDescription,
|
||||
handleChangeBoard,
|
||||
handleChangePostStatus,
|
||||
|
||||
isPowerUser,
|
||||
boards,
|
||||
postStatuses,
|
||||
|
||||
toggleEditMode,
|
||||
handleUpdatePost,
|
||||
}: Props) => (
|
||||
<div className="postEditForm">
|
||||
<div className="postHeader">
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={e => handleChangeTitle(e.target.value)}
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
isPowerUser ?
|
||||
<div className="postSettings">
|
||||
<PostBoardSelect
|
||||
boards={boards}
|
||||
selectedBoardId={boardId}
|
||||
handleChange={newBoardId => handleChangeBoard(newBoardId)}
|
||||
/>
|
||||
<PostStatusSelect
|
||||
postStatuses={postStatuses}
|
||||
selectedPostStatusId={postStatusId}
|
||||
handleChange={newPostStatusId => handleChangePostStatus(newPostStatusId)}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={e => handleChangeDescription(e.target.value)}
|
||||
rows={5}
|
||||
className="form-control"
|
||||
/>
|
||||
|
||||
<div className="postEditFormButtons">
|
||||
<a onClick={toggleEditMode}>
|
||||
{ I18n.t('common.buttons.cancel') }
|
||||
</a>
|
||||
|
||||
<Button onClick={() => handleUpdatePost(title, description, boardId, postStatusId)}>
|
||||
{ isUpdating ? <Spinner /> : I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostEditForm;
|
||||
53
app/javascript/components/Post/PostFooter.tsx
Normal file
53
app/javascript/components/Post/PostFooter.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
import friendlyDate from '../../helpers/datetime';
|
||||
import Separator from '../common/Separator';
|
||||
|
||||
interface Props {
|
||||
createdAt: string;
|
||||
toggleEditMode(): void;
|
||||
handleDeletePost(): void;
|
||||
isPowerUser: boolean;
|
||||
authorEmail: string;
|
||||
authorFullName: string;
|
||||
currentUserEmail: string;
|
||||
}
|
||||
|
||||
const PostFooter = ({
|
||||
createdAt,
|
||||
toggleEditMode,
|
||||
handleDeletePost,
|
||||
isPowerUser,
|
||||
authorEmail,
|
||||
authorFullName,
|
||||
currentUserEmail,
|
||||
}: Props) => (
|
||||
<div className="postFooter">
|
||||
<div className="postAuthor">
|
||||
<span>{ I18n.t('post.published_by').toLowerCase() } </span>
|
||||
<Gravatar email={authorEmail} size={24} className="postAuthorAvatar" />
|
||||
{authorFullName}
|
||||
</div>
|
||||
{
|
||||
isPowerUser || authorEmail === currentUserEmail ?
|
||||
<>
|
||||
<a onClick={toggleEditMode}>
|
||||
{ I18n.t('common.buttons.edit') }
|
||||
</a>
|
||||
<Separator />
|
||||
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDeletePost()}>
|
||||
{ I18n.t('common.buttons.delete') }
|
||||
</a>
|
||||
<Separator />
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
<MutedText>{friendlyDate(createdAt)}</MutedText>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostFooter;
|
||||
@@ -1,32 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import IPost from '../../interfaces/IPost';
|
||||
import IPostStatus from '../../interfaces/IPostStatus';
|
||||
import IBoard from '../../interfaces/IBoard';
|
||||
|
||||
import PostUpdateList from './PostUpdateList';
|
||||
import PostEditForm from './PostEditForm';
|
||||
import PostFooter from './PostFooter';
|
||||
import LikeList from './LikeList';
|
||||
import ActionBox from './ActionBox';
|
||||
import LikeButton from '../../containers/LikeButton';
|
||||
import PostBoardSelect from './PostBoardSelect';
|
||||
import PostStatusSelect from './PostStatusSelect';
|
||||
import PostBoardLabel from '../common/PostBoardLabel';
|
||||
import PostStatusLabel from '../common/PostStatusLabel';
|
||||
import Comments from '../../containers/Comments';
|
||||
import { MutedText } from '../common/CustomTexts';
|
||||
import Sidebar from '../common/Sidebar';
|
||||
|
||||
import { LikesState } from '../../reducers/likesReducer';
|
||||
import { CommentsState } from '../../reducers/commentsReducer';
|
||||
import { PostStatusChangesState } from '../../reducers/postStatusChangesReducer';
|
||||
import { PostEditFormState } from '../../reducers/currentPostReducer';
|
||||
|
||||
import friendlyDate, { fromRailsStringToJavascriptDate } from '../../helpers/datetime';
|
||||
import { fromRailsStringToJavascriptDate } from '../../helpers/datetime';
|
||||
import HttpStatus from '../../constants/http_status';
|
||||
|
||||
interface Props {
|
||||
postId: number;
|
||||
post: IPost;
|
||||
editMode: boolean;
|
||||
editForm: PostEditFormState;
|
||||
likes: LikesState;
|
||||
followed: boolean;
|
||||
comments: CommentsState;
|
||||
@@ -35,26 +37,38 @@ interface Props {
|
||||
postStatuses: Array<IPostStatus>;
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
currentUserFullName: string;
|
||||
currentUserEmail: string;
|
||||
authenticityToken: string;
|
||||
|
||||
requestPost(postId: number): void;
|
||||
updatePost(
|
||||
postId: number,
|
||||
title: string,
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
authenticityToken: string,
|
||||
): Promise<any>;
|
||||
|
||||
requestLikes(postId: number): void;
|
||||
requestFollow(postId: number): void;
|
||||
requestPostStatusChanges(postId: number): void;
|
||||
changePostBoard(
|
||||
postId: number,
|
||||
newBoardId: number,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
changePostStatus(
|
||||
postId: number,
|
||||
|
||||
toggleEditMode(): void;
|
||||
handleChangeEditFormTitle(title: string): void;
|
||||
handleChangeEditFormDescription(description: string): void;
|
||||
handleChangeEditFormBoard(boardId: number): void;
|
||||
handleChangeEditFormPostStatus(postStatusId: number): void;
|
||||
|
||||
deletePost(postId: number, authenticityToken: string): Promise<any>;
|
||||
|
||||
postStatusChangeSubmitted(
|
||||
newPostStatusId: number,
|
||||
userFullName: string,
|
||||
userEmail: string,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
|
||||
submitFollow(
|
||||
postId: number,
|
||||
isFollow: boolean,
|
||||
@@ -63,8 +77,15 @@ interface Props {
|
||||
}
|
||||
|
||||
class PostP extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._handleUpdatePost = this._handleUpdatePost.bind(this);
|
||||
this._handleDeletePost = this._handleDeletePost.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {postId} = this.props;
|
||||
const { postId } = this.props;
|
||||
|
||||
this.props.requestPost(postId);
|
||||
this.props.requestLikes(postId);
|
||||
@@ -72,9 +93,51 @@ class PostP extends React.Component<Props> {
|
||||
this.props.requestPostStatusChanges(postId);
|
||||
}
|
||||
|
||||
_handleUpdatePost(title: string, description: string, boardId: number, postStatusId: number) {
|
||||
const {
|
||||
postId,
|
||||
post,
|
||||
currentUserFullName,
|
||||
currentUserEmail,
|
||||
authenticityToken,
|
||||
|
||||
updatePost,
|
||||
postStatusChangeSubmitted,
|
||||
} = this.props;
|
||||
|
||||
const oldPostStatusId = post.postStatusId;
|
||||
|
||||
updatePost(
|
||||
postId,
|
||||
title,
|
||||
description,
|
||||
boardId,
|
||||
postStatusId,
|
||||
authenticityToken,
|
||||
).then(res => {
|
||||
if (res?.status !== HttpStatus.OK) return;
|
||||
if (postStatusId === oldPostStatusId) return;
|
||||
|
||||
postStatusChangeSubmitted(
|
||||
postStatusId,
|
||||
currentUserFullName,
|
||||
currentUserEmail,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_handleDeletePost() {
|
||||
this.props.deletePost(
|
||||
this.props.postId,
|
||||
this.props.authenticityToken
|
||||
).then(() => window.location.href = `/boards/${this.props.post.boardId}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
post,
|
||||
editMode,
|
||||
editForm,
|
||||
likes,
|
||||
followed,
|
||||
comments,
|
||||
@@ -84,13 +147,15 @@ class PostP extends React.Component<Props> {
|
||||
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
userFullName,
|
||||
userEmail,
|
||||
currentUserEmail,
|
||||
authenticityToken,
|
||||
|
||||
changePostBoard,
|
||||
changePostStatus,
|
||||
submitFollow,
|
||||
toggleEditMode,
|
||||
handleChangeEditFormTitle,
|
||||
handleChangeEditFormDescription,
|
||||
handleChangeEditFormBoard,
|
||||
handleChangeEditFormPostStatus,
|
||||
} = this.props;
|
||||
|
||||
const postUpdates = [
|
||||
@@ -98,7 +163,7 @@ class PostP extends React.Component<Props> {
|
||||
...postStatusChanges.items,
|
||||
].sort(
|
||||
(a, b) =>
|
||||
fromRailsStringToJavascriptDate(a.updatedAt) < fromRailsStringToJavascriptDate(b.updatedAt) ? 1 : -1
|
||||
fromRailsStringToJavascriptDate(a.createdAt) < fromRailsStringToJavascriptDate(b.createdAt) ? 1 : -1
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -126,71 +191,72 @@ class PostP extends React.Component<Props> {
|
||||
</Sidebar>
|
||||
|
||||
<div className="postAndCommentsContainer">
|
||||
<>
|
||||
<div className="postHeader">
|
||||
<LikeButton
|
||||
postId={post.id}
|
||||
likesCount={likes.items.length}
|
||||
liked={likes.items.find(like => like.email === userEmail) ? 1 : 0}
|
||||
isLoggedIn={isLoggedIn}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
<h2>{post.title}</h2>
|
||||
{
|
||||
isPowerUser && post ?
|
||||
<a href={`/admin/posts/${post.id}`} data-turbolinks="false">
|
||||
{I18n.t('post.edit_button')}
|
||||
</a>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
{
|
||||
isPowerUser && post ?
|
||||
<div className="postSettings">
|
||||
<PostBoardSelect
|
||||
boards={boards}
|
||||
selectedBoardId={post.boardId}
|
||||
handleChange={
|
||||
newBoardId => changePostBoard(post.id, newBoardId, authenticityToken)
|
||||
}
|
||||
/>
|
||||
<PostStatusSelect
|
||||
postStatuses={postStatuses}
|
||||
selectedPostStatusId={post.postStatusId}
|
||||
handleChange={
|
||||
newPostStatusId =>
|
||||
changePostStatus(post.id, newPostStatusId, userFullName, userEmail, authenticityToken)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
<div className="postInfo">
|
||||
<PostBoardLabel
|
||||
{...boards.find(board => board.id === post.boardId)}
|
||||
/>
|
||||
<PostStatusLabel
|
||||
{...postStatuses.find(postStatus => postStatus.id === post.postStatusId)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ReactMarkdown
|
||||
className="postDescription"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{post.description}
|
||||
</ReactMarkdown>
|
||||
|
||||
<MutedText>{friendlyDate(post.createdAt)}</MutedText>
|
||||
</>
|
||||
{
|
||||
editMode ?
|
||||
<PostEditForm
|
||||
{...editForm}
|
||||
|
||||
handleChangeTitle={handleChangeEditFormTitle}
|
||||
handleChangeDescription={handleChangeEditFormDescription}
|
||||
handleChangeBoard={handleChangeEditFormBoard}
|
||||
handleChangePostStatus={handleChangeEditFormPostStatus}
|
||||
|
||||
isPowerUser={isPowerUser}
|
||||
boards={boards}
|
||||
postStatuses={postStatuses}
|
||||
|
||||
toggleEditMode={toggleEditMode}
|
||||
handleUpdatePost={this._handleUpdatePost}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<div className="postHeader">
|
||||
<LikeButton
|
||||
postId={post.id}
|
||||
likesCount={likes.items.length}
|
||||
liked={likes.items.find(like => like.email === currentUserEmail) ? 1 : 0}
|
||||
isLoggedIn={isLoggedIn}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
|
||||
<h3>{post.title}</h3>
|
||||
</div>
|
||||
|
||||
<div className="postInfo">
|
||||
<PostBoardLabel
|
||||
{...boards.find(board => board.id === post.boardId)}
|
||||
/>
|
||||
<PostStatusLabel
|
||||
{...postStatuses.find(postStatus => postStatus.id === post.postStatusId)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ReactMarkdown
|
||||
className="postDescription"
|
||||
disallowedTypes={['heading', 'image', 'html']}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{post.description}
|
||||
</ReactMarkdown>
|
||||
|
||||
<PostFooter
|
||||
createdAt={post.createdAt}
|
||||
handleDeletePost={this._handleDeletePost}
|
||||
toggleEditMode={toggleEditMode}
|
||||
|
||||
isPowerUser={isPowerUser}
|
||||
authorEmail={post.userEmail}
|
||||
authorFullName={post.userFullName}
|
||||
currentUserEmail={currentUserEmail}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
<Comments
|
||||
postId={this.props.postId}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userEmail={userEmail}
|
||||
userEmail={currentUserEmail}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@ const PostUpdateList = ({
|
||||
}
|
||||
</div>
|
||||
|
||||
<MutedText>{friendlyDate(postUpdate.updatedAt)}</MutedText>
|
||||
<MutedText>{friendlyDate(postUpdate.createdAt)}</MutedText>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ interface Props {
|
||||
postStatuses: Array<IPostStatus>;
|
||||
isLoggedIn: boolean;
|
||||
isPowerUser: boolean;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
currentUserFullName: string;
|
||||
currentUserEmail: string;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ class PostRoot extends React.Component<Props> {
|
||||
postStatuses,
|
||||
isLoggedIn,
|
||||
isPowerUser,
|
||||
userFullName,
|
||||
userEmail,
|
||||
currentUserFullName,
|
||||
currentUserEmail,
|
||||
authenticityToken
|
||||
} = this.props;
|
||||
|
||||
@@ -52,8 +52,8 @@ class PostRoot extends React.Component<Props> {
|
||||
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPowerUser={isPowerUser}
|
||||
userFullName={userFullName}
|
||||
userEmail={userEmail}
|
||||
currentUserFullName={currentUserFullName}
|
||||
currentUserEmail={currentUserEmail}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
</Provider>
|
||||
|
||||
@@ -87,10 +87,7 @@ class BoardsEditable extends React.Component<Props, State> {
|
||||
|
||||
<Separator />
|
||||
|
||||
<a
|
||||
onClick={() => handleDelete(id)}
|
||||
data-confirm="Are you sure?"
|
||||
>
|
||||
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDelete(id)}>
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -78,10 +78,7 @@ class PostStatusEditable extends React.Component<Props, State> {
|
||||
|
||||
<Separator />
|
||||
|
||||
<a
|
||||
onClick={() => handleDelete(id)}
|
||||
data-confirm="Are you sure?"
|
||||
>
|
||||
<a onClick={() => confirm(I18n.t('common.confirmation')) && handleDelete(id)}>
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Box = ({ customClass, children }: Props) => (
|
||||
<div className={`box ${customClass}`}>
|
||||
<div className={`box${customClass ? ' ' + customClass : ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const SidebarBox = ({ title, customClass, children }: Props) => (
|
||||
<div className={`sidebarBox ${customClass}`}>
|
||||
<div className={`sidebarBox${customClass ? ' ' + customClass : ''}`}>
|
||||
<BoxTitleText>{title}</BoxTitleText>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
setCommentReplyBody,
|
||||
toggleCommentIsPostUpdateFlag,
|
||||
} from '../actions/Comment/handleCommentReplies';
|
||||
import { toggleCommentIsUpdate } from '../actions/Comment/updateComment';
|
||||
import { submitComment } from '../actions/Comment/submitComment';
|
||||
import { updateComment } from '../actions/Comment/updateComment';
|
||||
import { deleteComment } from '../actions/Comment/deleteComment';
|
||||
|
||||
import { State } from '../reducers/rootReducer';
|
||||
|
||||
import CommentsP from '../components/Comments/CommentsP';
|
||||
import HttpStatus from '../constants/http_status';
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
comments: state.currentPost.comments.items,
|
||||
@@ -37,15 +39,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(toggleCommentIsPostUpdateFlag(null));
|
||||
},
|
||||
|
||||
toggleCommentIsPostUpdate(
|
||||
postId: number,
|
||||
commentId: number,
|
||||
currentIsPostUpdate: boolean,
|
||||
authenticityToken: string,
|
||||
) {
|
||||
dispatch(toggleCommentIsUpdate(postId, commentId, currentIsPostUpdate, authenticityToken));
|
||||
},
|
||||
|
||||
submitComment(
|
||||
postId: number,
|
||||
body: string,
|
||||
@@ -55,6 +48,27 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
) {
|
||||
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(
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
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 { changePostBoard } from '../actions/Post/changePostBoard';
|
||||
import { changePostStatus } from '../actions/Post/changePostStatus';
|
||||
import { submitFollow } from '../actions/Follow/submitFollow';
|
||||
import { requestFollow } from '../actions/Follow/requestFollow';
|
||||
import { requestPostStatusChanges } from '../actions/PostStatusChange/requestPostStatusChanges';
|
||||
@@ -14,9 +21,12 @@ import { State } from '../reducers/rootReducer';
|
||||
import PostP from '../components/Post/PostP';
|
||||
|
||||
import { fromJavascriptDateToRailsString } from '../helpers/datetime';
|
||||
import { updatePost } from '../actions/Post/updatePost';
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
post: state.currentPost.item,
|
||||
editMode: state.currentPost.editMode,
|
||||
editForm: state.currentPost.editForm,
|
||||
likes: state.currentPost.likes,
|
||||
followed: state.currentPost.followed,
|
||||
comments: state.currentPost.comments,
|
||||
@@ -28,6 +38,37 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
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) {
|
||||
dispatch(requestLikes(postId));
|
||||
},
|
||||
@@ -40,29 +81,21 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(requestPostStatusChanges(postId));
|
||||
},
|
||||
|
||||
changePostBoard(postId: number, newBoardId: number, authenticityToken: string) {
|
||||
dispatch(changePostBoard(postId, newBoardId, authenticityToken));
|
||||
deletePost(postId: number, authenticityToken: string) {
|
||||
return dispatch(deletePost(postId, authenticityToken));
|
||||
},
|
||||
|
||||
changePostStatus(
|
||||
postId: number,
|
||||
postStatusChangeSubmitted(
|
||||
newPostStatusId: number,
|
||||
userFullName: string,
|
||||
userEmail: string,
|
||||
authenticityToken: string
|
||||
) {
|
||||
if (isNaN(newPostStatusId)) newPostStatusId = null;
|
||||
|
||||
dispatch(changePostStatus(postId, newPostStatusId, authenticityToken)).then(res => {
|
||||
if (res && res.status !== 204) return;
|
||||
|
||||
dispatch(postStatusChangeSubmitted({
|
||||
postStatusId: newPostStatusId,
|
||||
userFullName,
|
||||
userEmail,
|
||||
updatedAt: fromJavascriptDateToRailsString(new Date()),
|
||||
}));
|
||||
});
|
||||
dispatch(postStatusChangeSubmitted({
|
||||
postStatusId: newPostStatusId,
|
||||
userFullName,
|
||||
userEmail,
|
||||
createdAt: fromJavascriptDateToRailsString(new Date()),
|
||||
}));
|
||||
},
|
||||
|
||||
submitFollow(postId: number, isFollow: boolean, authenticityToken: string) {
|
||||
|
||||
@@ -5,6 +5,7 @@ interface IComment {
|
||||
isPostUpdate: boolean;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ interface IPost {
|
||||
commentsCount: number;
|
||||
hotness: number;
|
||||
userId: number;
|
||||
userEmail: string;
|
||||
userFullName: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ interface IPostStatusChange {
|
||||
postStatusId: number;
|
||||
userFullName: string;
|
||||
userEmail: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default IPostStatusChange;
|
||||
@@ -5,6 +5,7 @@ interface ICommentJSON {
|
||||
is_post_update: boolean;
|
||||
user_full_name: string;
|
||||
user_email: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ interface IPostJSON {
|
||||
comments_count: number;
|
||||
hotness: number;
|
||||
user_id: number;
|
||||
user_email: string;
|
||||
user_full_name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ interface IPostStatusChangeJSON {
|
||||
post_status_id: number;
|
||||
user_full_name: string;
|
||||
user_email: string;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export default IPostStatusChangeJSON;
|
||||
@@ -3,6 +3,11 @@ import {
|
||||
COMMENT_REQUEST_SUCCESS,
|
||||
} from '../actions/Comment/requestComment';
|
||||
|
||||
import {
|
||||
CommentUpdateActionTypes,
|
||||
COMMENT_UPDATE_SUCCESS,
|
||||
} from '../actions/Comment/updateComment';
|
||||
|
||||
import IComment from '../interfaces/IComment';
|
||||
|
||||
const initialState: IComment = {
|
||||
@@ -12,15 +17,17 @@ const initialState: IComment = {
|
||||
isPostUpdate: false,
|
||||
userFullName: '<Unknown user>',
|
||||
userEmail: 'example@example.com',
|
||||
createdAt: undefined,
|
||||
updatedAt: undefined,
|
||||
};
|
||||
|
||||
const commentReducer = (
|
||||
state = initialState,
|
||||
action: CommentRequestSuccessAction,
|
||||
action: CommentRequestSuccessAction | CommentUpdateActionTypes,
|
||||
): IComment => {
|
||||
switch (action.type) {
|
||||
case COMMENT_REQUEST_SUCCESS:
|
||||
case COMMENT_UPDATE_SUCCESS:
|
||||
return {
|
||||
id: action.comment.id,
|
||||
body: action.comment.body,
|
||||
@@ -28,6 +35,7 @@ const commentReducer = (
|
||||
isPostUpdate: action.comment.is_post_update,
|
||||
userFullName: action.comment.user_full_name,
|
||||
userEmail: action.comment.user_email,
|
||||
createdAt: action.comment.created_at,
|
||||
updatedAt: action.comment.updated_at,
|
||||
};
|
||||
|
||||
|
||||
@@ -22,8 +22,13 @@ import {
|
||||
} from '../actions/Comment/submitComment';
|
||||
|
||||
import {
|
||||
ToggleIsUpdateSuccessAction,
|
||||
TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
||||
CommentDeleteActionTypes,
|
||||
COMMENT_DELETE_SUCCESS,
|
||||
} from '../actions/Comment/deleteComment';
|
||||
|
||||
import {
|
||||
CommentUpdateActionTypes,
|
||||
COMMENT_UPDATE_SUCCESS,
|
||||
} from '../actions/Comment/updateComment';
|
||||
|
||||
import commentReducer from './commentReducer';
|
||||
@@ -54,7 +59,8 @@ const commentsReducer = (
|
||||
CommentsRequestActionTypes |
|
||||
HandleCommentRepliesType |
|
||||
CommentSubmitActionTypes |
|
||||
ToggleIsUpdateSuccessAction
|
||||
CommentUpdateActionTypes |
|
||||
CommentDeleteActionTypes
|
||||
): CommentsState => {
|
||||
switch (action.type) {
|
||||
case COMMENTS_REQUEST_START:
|
||||
@@ -103,17 +109,19 @@ const commentsReducer = (
|
||||
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 {
|
||||
...state,
|
||||
items:
|
||||
state.items.map(comment => {
|
||||
if (comment.id === action.commentId) {
|
||||
comment.isPostUpdate = !comment.isPostUpdate;
|
||||
return comment;
|
||||
} else return comment;
|
||||
})
|
||||
}
|
||||
items: state.items.map(comment => (
|
||||
comment.id === action.comment.id ? commentReducer(comment, action) : comment
|
||||
)),
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -6,14 +6,21 @@ import {
|
||||
} from '../actions/Post/requestPost';
|
||||
|
||||
import {
|
||||
ChangePostBoardSuccessAction,
|
||||
CHANGE_POST_BOARD_SUCCESS,
|
||||
} from '../actions/Post/changePostBoard';
|
||||
PostUpdateActionTypes,
|
||||
POST_UPDATE_START,
|
||||
POST_UPDATE_SUCCESS,
|
||||
POST_UPDATE_FAILURE,
|
||||
} from '../actions/Post/updatePost';
|
||||
|
||||
import { POST_TOGGLE_EDIT_MODE, PostToggleEditMode } from '../actions/Post/togglePostEditMode';
|
||||
|
||||
import {
|
||||
ChangePostStatusSuccessAction,
|
||||
CHANGE_POST_STATUS_SUCCESS,
|
||||
} from '../actions/Post/changePostStatus';
|
||||
ChangePostEditFormActionTypes,
|
||||
POST_CHANGE_EDIT_FORM_TITLE,
|
||||
POST_CHANGE_EDIT_FORM_DESCRIPTION,
|
||||
POST_CHANGE_EDIT_FORM_BOARD,
|
||||
POST_CHANGE_EDIT_FORM_POST_STATUS,
|
||||
} from '../actions/Post/changePostEditForm';
|
||||
|
||||
import {
|
||||
LikesRequestActionTypes,
|
||||
@@ -49,8 +56,13 @@ import {
|
||||
} from '../actions/Comment/submitComment';
|
||||
|
||||
import {
|
||||
ToggleIsUpdateSuccessAction,
|
||||
TOGGLE_COMMENT_IS_UPDATE_SUCCESS,
|
||||
CommentDeleteActionTypes,
|
||||
COMMENT_DELETE_SUCCESS,
|
||||
} from '../actions/Comment/deleteComment';
|
||||
|
||||
import {
|
||||
CommentUpdateActionTypes,
|
||||
COMMENT_UPDATE_SUCCESS,
|
||||
} from '../actions/Comment/updateComment';
|
||||
|
||||
import { FollowActionTypes, FOLLOW_SUBMIT_SUCCESS } from '../actions/Follow/submitFollow';
|
||||
@@ -78,11 +90,22 @@ import postStatusChangesReducer, { PostStatusChangesState } from './postStatusCh
|
||||
|
||||
import IPost from '../interfaces/IPost';
|
||||
|
||||
export interface PostEditFormState {
|
||||
title: string;
|
||||
description?: string;
|
||||
boardId: number;
|
||||
postStatusId?: number;
|
||||
|
||||
isUpdating: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface CurrentPostState {
|
||||
item: IPost;
|
||||
isLoading: boolean;
|
||||
error: string;
|
||||
editMode: boolean;
|
||||
editForm: PostEditFormState;
|
||||
likes: LikesState;
|
||||
followed: boolean;
|
||||
comments: CommentsState;
|
||||
@@ -93,6 +116,16 @@ const initialState: CurrentPostState = {
|
||||
item: postReducer(undefined, {} as PostRequestActionTypes),
|
||||
isLoading: false,
|
||||
error: '',
|
||||
editMode: false,
|
||||
editForm: {
|
||||
title: '',
|
||||
description: '',
|
||||
boardId: 1,
|
||||
postStatusId: 1,
|
||||
|
||||
isUpdating: false,
|
||||
error: '',
|
||||
},
|
||||
likes: likesReducer(undefined, {} as LikesRequestActionTypes),
|
||||
followed: false,
|
||||
comments: commentsReducer(undefined, {} as CommentsRequestActionTypes),
|
||||
@@ -103,14 +136,16 @@ const currentPostReducer = (
|
||||
state = initialState,
|
||||
action:
|
||||
PostRequestActionTypes |
|
||||
ChangePostBoardSuccessAction |
|
||||
ChangePostStatusSuccessAction |
|
||||
PostUpdateActionTypes |
|
||||
PostToggleEditMode |
|
||||
ChangePostEditFormActionTypes |
|
||||
LikesRequestActionTypes |
|
||||
LikeActionTypes |
|
||||
CommentsRequestActionTypes |
|
||||
HandleCommentRepliesType |
|
||||
CommentSubmitActionTypes |
|
||||
ToggleIsUpdateSuccessAction |
|
||||
CommentUpdateActionTypes |
|
||||
CommentDeleteActionTypes |
|
||||
FollowActionTypes |
|
||||
FollowRequestActionTypes |
|
||||
PostStatusChangesRequestActionTypes |
|
||||
@@ -138,11 +173,67 @@ const currentPostReducer = (
|
||||
error: action.error,
|
||||
};
|
||||
|
||||
case CHANGE_POST_BOARD_SUCCESS:
|
||||
case CHANGE_POST_STATUS_SUCCESS:
|
||||
case POST_UPDATE_START:
|
||||
return {
|
||||
...state, editForm: { ...state.editForm, isUpdating: true, error: '' }
|
||||
};
|
||||
|
||||
case POST_UPDATE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
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:
|
||||
@@ -162,7 +253,8 @@ const currentPostReducer = (
|
||||
case COMMENT_SUBMIT_START:
|
||||
case COMMENT_SUBMIT_SUCCESS:
|
||||
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:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -4,14 +4,9 @@ import {
|
||||
} from '../actions/Post/requestPost';
|
||||
|
||||
import {
|
||||
ChangePostBoardSuccessAction,
|
||||
CHANGE_POST_BOARD_SUCCESS,
|
||||
} from '../actions/Post/changePostBoard';
|
||||
|
||||
import {
|
||||
ChangePostStatusSuccessAction,
|
||||
CHANGE_POST_STATUS_SUCCESS,
|
||||
} from '../actions/Post/changePostStatus';
|
||||
PostUpdateActionTypes,
|
||||
POST_UPDATE_SUCCESS,
|
||||
} from '../actions/Post/updatePost';
|
||||
|
||||
import IPost from '../interfaces/IPost';
|
||||
|
||||
@@ -26,6 +21,8 @@ const initialState: IPost = {
|
||||
commentsCount: 0,
|
||||
hotness: 0,
|
||||
userId: 0,
|
||||
userEmail: '',
|
||||
userFullName: '',
|
||||
createdAt: '',
|
||||
};
|
||||
|
||||
@@ -33,8 +30,7 @@ const postReducer = (
|
||||
state = initialState,
|
||||
action:
|
||||
PostRequestActionTypes |
|
||||
ChangePostBoardSuccessAction |
|
||||
ChangePostStatusSuccessAction,
|
||||
PostUpdateActionTypes
|
||||
): IPost => {
|
||||
switch (action.type) {
|
||||
case POST_REQUEST_SUCCESS:
|
||||
@@ -49,19 +45,18 @@ const postReducer = (
|
||||
commentsCount: action.post.comments_count,
|
||||
hotness: action.post.hotness,
|
||||
userId: action.post.user_id,
|
||||
userEmail: action.post.user_email,
|
||||
userFullName: action.post.user_full_name,
|
||||
createdAt: action.post.created_at,
|
||||
};
|
||||
|
||||
case CHANGE_POST_BOARD_SUCCESS:
|
||||
case POST_UPDATE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
boardId: action.newBoardId,
|
||||
};
|
||||
|
||||
case CHANGE_POST_STATUS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
postStatusId: action.newPostStatusId,
|
||||
title: action.post.title,
|
||||
description: action.post.description,
|
||||
boardId: action.post.board_id,
|
||||
postStatusId: action.post.post_status_id,
|
||||
};
|
||||
|
||||
default:
|
||||
|
||||
@@ -44,7 +44,7 @@ const postStatusChangesReducer = (
|
||||
postStatusId: postStatusChange.post_status_id,
|
||||
userFullName: postStatusChange.user_full_name,
|
||||
userEmail: postStatusChange.user_email,
|
||||
updatedAt: postStatusChange.updated_at,
|
||||
createdAt: postStatusChange.created_at,
|
||||
})),
|
||||
areLoading: false,
|
||||
error: '',
|
||||
|
||||
@@ -14,6 +14,11 @@ import {
|
||||
POSTS_REQUEST_FAILURE,
|
||||
} from '../actions/Post/requestPosts';
|
||||
|
||||
import {
|
||||
PostDeleteActionTypes,
|
||||
POST_DELETE_SUCCESS,
|
||||
} from '../actions/Post/deletePost';
|
||||
|
||||
import { postRequestSuccess } from '../actions/Post/requestPost';
|
||||
|
||||
import {
|
||||
@@ -49,6 +54,7 @@ const postsReducer = (
|
||||
state = initialState,
|
||||
action:
|
||||
PostsRequestActionTypes |
|
||||
PostDeleteActionTypes |
|
||||
ChangeFiltersActionTypes |
|
||||
LikeActionTypes,
|
||||
): PostsState => {
|
||||
@@ -79,6 +85,12 @@ const postsReducer = (
|
||||
error: action.error,
|
||||
};
|
||||
|
||||
case POST_DELETE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.filter(post => post.id !== action.postId),
|
||||
};
|
||||
|
||||
case SET_SEARCH_FILTER:
|
||||
case SET_POST_STATUS_FILTER:
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
.commentsContainer {
|
||||
@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 {
|
||||
@extend
|
||||
.d-flex,
|
||||
@@ -27,21 +51,6 @@
|
||||
.mr-2;
|
||||
}
|
||||
|
||||
.newCommentBody {
|
||||
@extend
|
||||
.form-control,
|
||||
.w-100,
|
||||
.p-2,
|
||||
.mr-2;
|
||||
|
||||
height: 80px;
|
||||
|
||||
border: thin solid grey;
|
||||
border-radius: 4px;
|
||||
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.submitCommentButton {
|
||||
@extend
|
||||
.align-self-end;
|
||||
@@ -52,6 +61,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.editCommentForm {
|
||||
textarea {
|
||||
@extend .my-2;
|
||||
}
|
||||
|
||||
& > div {
|
||||
@extend
|
||||
.d-flex,
|
||||
.justify-content-between;
|
||||
}
|
||||
}
|
||||
|
||||
.commentsTitle {
|
||||
@extend
|
||||
.text-secondary,
|
||||
@@ -93,15 +114,6 @@
|
||||
|
||||
.commentFooter {
|
||||
font-size: 14px;
|
||||
|
||||
.commentLink {
|
||||
color: $primary-color;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@
|
||||
@extend
|
||||
.d-flex,
|
||||
.flex-column,
|
||||
.mr-3,
|
||||
.mt-2;
|
||||
.mr-3;
|
||||
|
||||
$like_button_size: 11px;
|
||||
|
||||
|
||||
@@ -104,7 +104,8 @@
|
||||
|
||||
.postHeader {
|
||||
@extend
|
||||
.d-flex;
|
||||
.d-flex,
|
||||
.mb-3;
|
||||
|
||||
a {
|
||||
@extend
|
||||
@@ -128,9 +129,47 @@
|
||||
|
||||
.postDescription {
|
||||
@extend
|
||||
.my-3;
|
||||
.my-4;
|
||||
|
||||
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
|
||||
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?
|
||||
user == record.user or user.power_user?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user == record.user or user.power_user?
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,21 @@
|
||||
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?
|
||||
user == record.user or user.power_user?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user == record.user or user.power_user?
|
||||
end
|
||||
end
|
||||
@@ -7,8 +7,8 @@
|
||||
postStatuses: @post_statuses,
|
||||
isLoggedIn: user_signed_in?,
|
||||
isPowerUser: user_signed_in? ? current_user.power_user? : false,
|
||||
userFullName: user_signed_in? ? current_user.full_name : nil,
|
||||
userEmail: user_signed_in? ? current_user.email : nil,
|
||||
currentUserFullName: user_signed_in? ? current_user.full_name : nil,
|
||||
currentUserEmail: user_signed_in? ? current_user.email : nil,
|
||||
authenticityToken: form_authenticity_token,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -30,6 +30,8 @@ en:
|
||||
other: '%{count} comments'
|
||||
no_status: 'No status'
|
||||
loading: 'Loading...'
|
||||
confirmation: 'Are you sure?'
|
||||
edited: 'Edited'
|
||||
buttons:
|
||||
edit: 'Edit'
|
||||
delete: 'Delete'
|
||||
@@ -74,6 +76,7 @@ en:
|
||||
empty: 'There are no posts'
|
||||
post:
|
||||
edit_button: 'Edit'
|
||||
published_by: 'Published by'
|
||||
post_status_select:
|
||||
no_post_status: 'None'
|
||||
updates_box:
|
||||
@@ -124,7 +127,7 @@ en:
|
||||
title: 'Roadmap'
|
||||
title2: 'Not in roadmap'
|
||||
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:
|
||||
opening_greeting: 'Hello!'
|
||||
closing_greeting: 'Have a great day!'
|
||||
|
||||
@@ -30,6 +30,8 @@ it:
|
||||
other: '%{count} commenti'
|
||||
no_status: 'Nessuno stato'
|
||||
loading: 'Caricamento...'
|
||||
confirmation: 'Sei sicuro?'
|
||||
edited: 'Modificato'
|
||||
buttons:
|
||||
edit: 'Modifica'
|
||||
delete: 'Elimina'
|
||||
@@ -74,6 +76,7 @@ it:
|
||||
empty: 'Non ci sono post'
|
||||
post:
|
||||
edit_button: 'Modifica'
|
||||
published_by: 'Pubblicato da'
|
||||
post_status_select:
|
||||
no_post_status: 'Nessuno'
|
||||
updates_box:
|
||||
@@ -124,7 +127,7 @@ it:
|
||||
title: 'Roadmap'
|
||||
title2: 'Non mostrati in roadmap'
|
||||
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:
|
||||
opening_greeting: 'Ciao!'
|
||||
closing_greeting: 'Buona giornata!'
|
||||
|
||||
@@ -14,12 +14,12 @@ Rails.application.routes.draw do
|
||||
|
||||
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]
|
||||
resources :follows, only: [:index]
|
||||
resource :likes, only: [:create, :destroy]
|
||||
resources :likes, only: [:index]
|
||||
resources :comments, only: [:index, :create, :update]
|
||||
resources :comments, only: [:index, :create, :update, :destroy]
|
||||
resources :post_status_changes, only: [:index]
|
||||
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(
|
||||
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/new').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
|
||||
@@ -14,11 +14,13 @@ RSpec.describe 'posts routing', :aggregate_failures, type: :routing do
|
||||
expect(patch: '/posts/1').to route_to(
|
||||
controller: 'posts', action: 'update', id: '1'
|
||||
)
|
||||
|
||||
expect(get: '/posts/new').not_to route_to(
|
||||
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(delete: '/posts/1').not_to be_routable
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user