mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 19:27:52 +01:00
enable deletion of post attachments
This commit is contained in:
@@ -398,5 +398,11 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.thumbnailToDelete {
|
||||
border: 2px solid red;
|
||||
|
||||
.thumbnailInner { filter: grayscale(100%); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@
|
||||
}
|
||||
|
||||
.postEditFormButtons {
|
||||
@extend .d-flex, .justify-content-end;
|
||||
@extend .d-flex, .justify-content-end, .mt-3;
|
||||
}
|
||||
|
||||
#selectPickerBoard { margin-right: 4px !important; }
|
||||
|
||||
@@ -101,7 +101,7 @@ class PostsController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
||||
format.json { render json: @post.as_json.merge(attachment_urls: @post.attachments.map { |attachment| attachment.blob.url }) }
|
||||
format.json { render json: @post.as_json.merge(attachment_urls: @post.attachments.order(:created_at).map { |attachment| attachment.blob.url }) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,6 +111,13 @@ class PostsController < ApplicationController
|
||||
|
||||
@post.assign_attributes(post_update_params)
|
||||
|
||||
# handle attachment deletion
|
||||
if params[:post][:attachments_to_delete].present? && @post.attachments.attached?
|
||||
@post.attachments.order(:created_at).each_with_index do |attachment, index|
|
||||
attachment.purge if params[:post][:attachments_to_delete].include?(index)
|
||||
end
|
||||
end
|
||||
|
||||
if @post.save
|
||||
if @post.post_status_id_previously_changed?
|
||||
ExecutePostStatusChangeLogicWorkflow.new(
|
||||
@@ -120,7 +127,7 @@ class PostsController < ApplicationController
|
||||
).run
|
||||
end
|
||||
|
||||
render json: @post
|
||||
render json: @post.as_json.merge(attachment_urls: @post.attachments.order(:created_at).map { |attachment| attachment.blob.url })
|
||||
else
|
||||
render json: {
|
||||
error: @post.errors.full_messages
|
||||
@@ -198,6 +205,10 @@ class PostsController < ApplicationController
|
||||
def post_update_params
|
||||
params
|
||||
.require(:post)
|
||||
.permit(policy(@post).permitted_attributes_for_update)
|
||||
.permit(
|
||||
policy(@post)
|
||||
.permitted_attributes_for_update
|
||||
.concat([{ additional_params: [:attachments_to_delete] }])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,6 +50,7 @@ export const updatePost = (
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
attachmentsToDelete: number[],
|
||||
authenticityToken: string,
|
||||
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||
dispatch(postUpdateStart());
|
||||
@@ -64,7 +65,8 @@ export const updatePost = (
|
||||
description,
|
||||
board_id: boardId,
|
||||
post_status_id: postStatusId,
|
||||
}
|
||||
attachments_to_delete: attachmentsToDelete,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
@@ -9,13 +9,14 @@ import IBoard from '../../interfaces/IBoard';
|
||||
import Button from '../common/Button';
|
||||
import Spinner from '../common/Spinner';
|
||||
import ActionLink from '../common/ActionLink';
|
||||
import { CancelIcon } from '../common/Icons';
|
||||
import { CancelIcon, DeleteIcon } from '../common/Icons';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
boardId: number;
|
||||
postStatusId?: number;
|
||||
attachmentUrls?: string[];
|
||||
|
||||
isUpdating: boolean;
|
||||
error: string;
|
||||
@@ -35,6 +36,7 @@ interface Props {
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
attachmentsToDelete: number[],
|
||||
): void;
|
||||
}
|
||||
|
||||
@@ -43,6 +45,7 @@ const PostEditForm = ({
|
||||
description,
|
||||
boardId,
|
||||
postStatusId,
|
||||
attachmentUrls,
|
||||
|
||||
isUpdating,
|
||||
error,
|
||||
@@ -58,53 +61,97 @@ const PostEditForm = ({
|
||||
|
||||
toggleEditMode,
|
||||
handleUpdatePost,
|
||||
}: Props) => (
|
||||
<div className="postEditForm">
|
||||
<div className="postHeader">
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={e => handleChangeTitle(e.target.value)}
|
||||
autoFocus
|
||||
className="postTitle form-control"
|
||||
}: Props) => {
|
||||
const [attachmentsToDelete, setAttachmentsToDelete] = React.useState<number[]>([]);
|
||||
|
||||
console.log('attachmentsToDelete', attachmentsToDelete);
|
||||
|
||||
|
||||
return (
|
||||
<div className="postEditForm">
|
||||
<div className="postHeader">
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={e => handleChangeTitle(e.target.value)}
|
||||
autoFocus
|
||||
className="postTitle 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="postDescription form-control"
|
||||
/>
|
||||
|
||||
{ /* Attachments */ }
|
||||
<div className="thumbnailsContainer">
|
||||
{
|
||||
attachmentUrls && attachmentUrls.map((attachmentUrl, i) => (
|
||||
<div className="thumbnailContainer" key={i}>
|
||||
<div className={`thumbnail${attachmentsToDelete.includes(i) ? ' thumbnailToDelete' : ''}`}>
|
||||
<div className="thumbnailInner">
|
||||
<img
|
||||
src={attachmentUrl}
|
||||
className="thumbnailImage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
attachmentsToDelete.includes(i) ?
|
||||
<ActionLink
|
||||
onClick={() => setAttachmentsToDelete(attachmentsToDelete.filter(index => index !== i))}
|
||||
icon={<CancelIcon />}
|
||||
customClass="removeThumbnail"
|
||||
>
|
||||
{I18n.t('common.buttons.cancel')}
|
||||
</ActionLink>
|
||||
:
|
||||
<ActionLink
|
||||
onClick={() => setAttachmentsToDelete([...attachmentsToDelete, i])}
|
||||
icon={<DeleteIcon />}
|
||||
customClass="removeThumbnail"
|
||||
>
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</ActionLink>
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="postEditFormButtons">
|
||||
<ActionLink onClick={toggleEditMode} icon={<CancelIcon />}>
|
||||
{I18n.t('common.buttons.cancel')}
|
||||
</ActionLink>
|
||||
|
||||
<Button onClick={() => handleUpdatePost(title, description, boardId, postStatusId, attachmentsToDelete)}>
|
||||
{ isUpdating ? <Spinner /> : I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
</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="postDescription form-control"
|
||||
/>
|
||||
|
||||
<div className="postEditFormButtons">
|
||||
<ActionLink onClick={toggleEditMode} icon={<CancelIcon />}>
|
||||
{I18n.t('common.buttons.cancel')}
|
||||
</ActionLink>
|
||||
|
||||
<Button onClick={() => handleUpdatePost(title, description, boardId, postStatusId)}>
|
||||
{ isUpdating ? <Spinner /> : I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default PostEditForm;
|
||||
@@ -59,6 +59,7 @@ interface Props {
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
attachmentsToDelete: number[],
|
||||
authenticityToken: string,
|
||||
): Promise<any>;
|
||||
|
||||
@@ -114,7 +115,7 @@ class PostP extends React.Component<Props, State> {
|
||||
this.props.requestPostStatusChanges(postId);
|
||||
}
|
||||
|
||||
_handleUpdatePost(title: string, description: string, boardId: number, postStatusId: number) {
|
||||
_handleUpdatePost(title: string, description: string, boardId: number, postStatusId: number, attachmentsToDelete: number[]) {
|
||||
const {
|
||||
postId,
|
||||
post,
|
||||
@@ -134,6 +135,7 @@ class PostP extends React.Component<Props, State> {
|
||||
description,
|
||||
boardId,
|
||||
postStatusId,
|
||||
attachmentsToDelete,
|
||||
authenticityToken,
|
||||
).then(res => {
|
||||
if (res?.status !== HttpStatus.OK) return;
|
||||
@@ -235,6 +237,7 @@ class PostP extends React.Component<Props, State> {
|
||||
editMode ?
|
||||
<PostEditForm
|
||||
{...editForm}
|
||||
attachmentUrls={postToShow.attachmentUrls}
|
||||
|
||||
handleChangeTitle={handleChangeEditFormTitle}
|
||||
handleChangeDescription={handleChangeEditFormDescription}
|
||||
|
||||
@@ -44,9 +44,10 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
description: string,
|
||||
boardId: number,
|
||||
postStatusId: number,
|
||||
attachmentsToDelete: number[],
|
||||
authenticityToken: string,
|
||||
) {
|
||||
return dispatch(updatePost(postId, title, description, boardId, postStatusId, authenticityToken));
|
||||
return dispatch(updatePost(postId, title, description, boardId, postStatusId, attachmentsToDelete, authenticityToken));
|
||||
},
|
||||
|
||||
toggleEditMode() {
|
||||
|
||||
@@ -63,6 +63,7 @@ const postReducer = (
|
||||
...state,
|
||||
title: action.post.title,
|
||||
description: action.post.description,
|
||||
attachmentUrls: action.post.attachment_urls,
|
||||
boardId: action.post.board_id,
|
||||
postStatusId: action.post.post_status_id,
|
||||
approvalStatus: action.post.approval_status,
|
||||
|
||||
Reference in New Issue
Block a user