images for forum and forum comments
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import ForumImageUpload from './ForumImageUpload';
|
||||
import { IMAGE_LIMITS } from '../config/imageLimits';
|
||||
|
||||
interface CommentFormProps {
|
||||
onSubmit: (content: string, images: File[]) => Promise<void>;
|
||||
@@ -24,7 +25,7 @@ const CommentForm: React.FC<CommentFormProps> = ({
|
||||
|
||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(e.target.files || []);
|
||||
const remainingSlots = 3 - imageFiles.length;
|
||||
const remainingSlots = IMAGE_LIMITS.forum - imageFiles.length;
|
||||
const filesToAdd = files.slice(0, remainingSlots);
|
||||
|
||||
setImageFiles((prev) => [...prev, ...filesToAdd]);
|
||||
@@ -81,15 +82,13 @@ const CommentForm: React.FC<CommentFormProps> = ({
|
||||
/>
|
||||
{error && <div className="invalid-feedback">{error}</div>}
|
||||
</div>
|
||||
{!isReply && (
|
||||
<ForumImageUpload
|
||||
imageFiles={imageFiles}
|
||||
imagePreviews={imagePreviews}
|
||||
onImageChange={handleImageChange}
|
||||
onRemoveImage={handleRemoveImage}
|
||||
compact={true}
|
||||
/>
|
||||
)}
|
||||
<ForumImageUpload
|
||||
imageFiles={imageFiles}
|
||||
imagePreviews={imagePreviews}
|
||||
onImageChange={handleImageChange}
|
||||
onRemoveImage={handleRemoveImage}
|
||||
compact={true}
|
||||
/>
|
||||
<div className="d-flex gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React, { useState } from "react";
|
||||
import { ForumComment } from "../types";
|
||||
import CommentForm from "./CommentForm";
|
||||
import ForumImageUpload from "./ForumImageUpload";
|
||||
import { getPublicImageUrl } from "../services/uploadService";
|
||||
import { IMAGE_LIMITS } from "../config/imageLimits";
|
||||
|
||||
interface CommentThreadProps {
|
||||
comment: ForumComment;
|
||||
onReply: (commentId: string, content: string) => Promise<void>;
|
||||
onEdit?: (commentId: string, content: string) => Promise<void>;
|
||||
onReply: (commentId: string, content: string, images?: File[]) => Promise<void>;
|
||||
onEdit?: (commentId: string, content: string, existingImageKeys: string[], newImageFiles: File[]) => Promise<void>;
|
||||
onDelete?: (commentId: string) => Promise<void>;
|
||||
onMarkAsAnswer?: (commentId: string) => Promise<void>;
|
||||
currentUserId?: string;
|
||||
@@ -37,6 +39,11 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
const [editContent, setEditContent] = useState(comment.content);
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
|
||||
// Image editing state
|
||||
const [existingImageKeys, setExistingImageKeys] = useState<string[]>([]);
|
||||
const [editImageFiles, setEditImageFiles] = useState<File[]>([]);
|
||||
const [editImagePreviews, setEditImagePreviews] = useState<string[]>([]);
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
@@ -59,17 +66,67 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
};
|
||||
|
||||
const handleReply = async (content: string, images: File[]) => {
|
||||
// Replies don't support images, so we ignore the images parameter
|
||||
await onReply(comment.id, content);
|
||||
await onReply(comment.id, content, images);
|
||||
setShowReplyForm(false);
|
||||
};
|
||||
|
||||
const handleEdit = async () => {
|
||||
if (onEdit && editContent.trim() !== comment.content) {
|
||||
await onEdit(comment.id, editContent);
|
||||
setIsEditing(false);
|
||||
const startEditing = () => {
|
||||
setIsEditing(true);
|
||||
setEditContent(comment.content);
|
||||
const existingKeys = comment.imageFilenames || [];
|
||||
setExistingImageKeys(existingKeys);
|
||||
setEditImagePreviews(existingKeys.map((key) => getPublicImageUrl(key)));
|
||||
setEditImageFiles([]);
|
||||
};
|
||||
|
||||
const cancelEditing = () => {
|
||||
setIsEditing(false);
|
||||
setEditContent(comment.content);
|
||||
setExistingImageKeys([]);
|
||||
setEditImageFiles([]);
|
||||
setEditImagePreviews([]);
|
||||
};
|
||||
|
||||
const handleEditImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(e.target.files || []);
|
||||
const totalImages = existingImageKeys.length + editImageFiles.length;
|
||||
const remainingSlots = IMAGE_LIMITS.forum - totalImages;
|
||||
const filesToAdd = files.slice(0, remainingSlots);
|
||||
|
||||
setEditImageFiles((prev) => [...prev, ...filesToAdd]);
|
||||
|
||||
filesToAdd.forEach((file) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setEditImagePreviews((prev) => [...prev, reader.result as string]);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
e.target.value = "";
|
||||
};
|
||||
|
||||
const handleRemoveEditImage = (index: number) => {
|
||||
if (index < existingImageKeys.length) {
|
||||
// Removing an existing S3 image
|
||||
setExistingImageKeys((prev) => prev.filter((_, i) => i !== index));
|
||||
} else {
|
||||
// Removing a new upload
|
||||
const newFileIndex = index - existingImageKeys.length;
|
||||
setEditImageFiles((prev) => prev.filter((_, i) => i !== newFileIndex));
|
||||
}
|
||||
setEditImagePreviews((prev) => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEdit = async () => {
|
||||
if (onEdit && editContent.trim()) {
|
||||
await onEdit(comment.id, editContent, existingImageKeys, editImageFiles);
|
||||
setIsEditing(false);
|
||||
setExistingImageKeys([]);
|
||||
setEditImageFiles([]);
|
||||
setEditImagePreviews([]);
|
||||
} else {
|
||||
cancelEditing();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -188,7 +245,14 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
value={editContent}
|
||||
onChange={(e) => setEditContent(e.target.value)}
|
||||
/>
|
||||
<div className="d-flex gap-2">
|
||||
<ForumImageUpload
|
||||
imageFiles={editImageFiles}
|
||||
imagePreviews={editImagePreviews}
|
||||
onImageChange={handleEditImageChange}
|
||||
onRemoveImage={handleRemoveEditImage}
|
||||
compact={true}
|
||||
/>
|
||||
<div className="d-flex gap-2 mt-2">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={handleEdit}
|
||||
@@ -198,10 +262,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-secondary"
|
||||
onClick={() => {
|
||||
setIsEditing(false);
|
||||
setEditContent(comment.content);
|
||||
}}
|
||||
onClick={cancelEditing}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -222,8 +283,8 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
className="img-fluid rounded"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100px",
|
||||
objectFit: "cover",
|
||||
maxHeight: "300px",
|
||||
objectFit: "contain",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() =>
|
||||
@@ -267,7 +328,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
|
||||
{isAuthor && onEdit && !isEditing && (
|
||||
<button
|
||||
className="btn btn-sm btn-link text-decoration-none p-0"
|
||||
onClick={() => setIsEditing(true)}
|
||||
onClick={startEditing}
|
||||
>
|
||||
<i className="bi bi-pencil me-1"></i>
|
||||
Edit
|
||||
|
||||
Reference in New Issue
Block a user