Skip to content

Commit c0ce7ab

Browse files
committed
feat: implement inline editing for comments
1 parent 13135ec commit c0ce7ab

File tree

5 files changed

+73
-15
lines changed

5 files changed

+73
-15
lines changed

surfsense_web/components/chat-comments/comment-composer/comment-composer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ export function CommentComposer({
8686
onSubmit,
8787
onCancel,
8888
autoFocus = false,
89+
initialValue = "",
8990
}: CommentComposerProps) {
90-
const [displayContent, setDisplayContent] = useState("");
91+
const [displayContent, setDisplayContent] = useState(initialValue);
9192
const [insertedMentions, setInsertedMentions] = useState<InsertedMention[]>([]);
9293
const [mentionState, setMentionState] = useState<MentionState>({
9394
isActive: false,

surfsense_web/components/chat-comments/comment-composer/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface CommentComposerProps {
99
onSubmit: (content: string) => void;
1010
onCancel?: () => void;
1111
autoFocus?: boolean;
12+
initialValue?: string;
1213
}
1314

1415
export interface MentionState {

surfsense_web/components/chat-comments/comment-item/comment-item.tsx

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { currentUserAtom } from "@/atoms/user/user-query.atoms";
66
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
77
import { Button } from "@/components/ui/button";
88
import { cn } from "@/lib/utils";
9+
import { CommentComposer } from "../comment-composer/comment-composer";
910
import { CommentActions } from "./comment-actions";
1011
import type { CommentItemProps } from "./types";
1112

@@ -99,9 +100,14 @@ function renderMentions(content: string): React.ReactNode {
99100
export function CommentItem({
100101
comment,
101102
onEdit,
103+
onEditSubmit,
104+
onEditCancel,
102105
onDelete,
103106
onReply,
104107
isReply = false,
108+
isEditing = false,
109+
members = [],
110+
membersLoading = false,
105111
}: CommentItemProps) {
106112
const [{ data: currentUser }] = useAtom(currentUserAtom);
107113

@@ -111,6 +117,10 @@ export function CommentItem({
111117
: comment.author?.displayName || comment.author?.email.split("@")[0] || "Unknown";
112118
const email = comment.author?.email || "";
113119

120+
const handleEditSubmit = (content: string) => {
121+
onEditSubmit?.(comment.id, content);
122+
};
123+
114124
return (
115125
<div className={cn("group flex gap-3")}>
116126
<Avatar className="size-8 shrink-0">
@@ -131,21 +141,38 @@ export function CommentItem({
131141
{comment.isEdited && (
132142
<span className="shrink-0 text-xs text-muted-foreground">(edited)</span>
133143
)}
134-
<div className="ml-auto">
135-
<CommentActions
136-
canEdit={comment.canEdit}
137-
canDelete={comment.canDelete}
138-
onEdit={() => onEdit?.(comment.id)}
139-
onDelete={() => onDelete?.(comment.id)}
140-
/>
141-
</div>
144+
{!isEditing && (
145+
<div className="ml-auto">
146+
<CommentActions
147+
canEdit={comment.canEdit}
148+
canDelete={comment.canDelete}
149+
onEdit={() => onEdit?.(comment.id)}
150+
onDelete={() => onDelete?.(comment.id)}
151+
/>
152+
</div>
153+
)}
142154
</div>
143155

144-
<div className="mt-1 text-sm text-foreground whitespace-pre-wrap wrap-break-word">
145-
{renderMentions(comment.contentRendered)}
146-
</div>
156+
{isEditing ? (
157+
<div className="mt-1">
158+
<CommentComposer
159+
members={members}
160+
membersLoading={membersLoading}
161+
placeholder="Edit your comment..."
162+
submitLabel="Save"
163+
onSubmit={handleEditSubmit}
164+
onCancel={onEditCancel}
165+
initialValue={comment.content}
166+
autoFocus
167+
/>
168+
</div>
169+
) : (
170+
<div className="mt-1 text-sm text-foreground whitespace-pre-wrap wrap-break-word">
171+
{renderMentions(comment.contentRendered)}
172+
</div>
173+
)}
147174

148-
{!isReply && onReply && (
175+
{!isReply && onReply && !isEditing && (
149176
<Button
150177
variant="ghost"
151178
size="sm"

surfsense_web/components/chat-comments/comment-item/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ export interface CommentData {
2020
export interface CommentItemProps {
2121
comment: CommentData;
2222
onEdit?: (commentId: number) => void;
23+
onEditSubmit?: (commentId: number, content: string) => void;
24+
onEditCancel?: () => void;
2325
onDelete?: (commentId: number) => void;
2426
onReply?: (commentId: number) => void;
2527
isReply?: boolean;
28+
isEditing?: boolean;
29+
members?: Array<{ id: string; displayName: string | null; email: string; avatarUrl?: string | null }>;
30+
membersLoading?: boolean;
2631
}
2732

2833
export interface CommentActionsProps {

surfsense_web/components/chat-comments/comment-thread/comment-thread.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function CommentThread({
1818
}: CommentThreadProps) {
1919
const [isRepliesExpanded, setIsRepliesExpanded] = useState(true);
2020
const [isReplyComposerOpen, setIsReplyComposerOpen] = useState(false);
21+
const [editingCommentId, setEditingCommentId] = useState<number | null>(null);
2122

2223
const parentComment = {
2324
id: thread.id,
@@ -45,6 +46,19 @@ export function CommentThread({
4546
setIsReplyComposerOpen(false);
4647
};
4748

49+
const handleEditStart = (commentId: number) => {
50+
setEditingCommentId(commentId);
51+
};
52+
53+
const handleEditSubmit = (commentId: number, content: string) => {
54+
onEditComment(commentId, content);
55+
setEditingCommentId(null);
56+
};
57+
58+
const handleEditCancel = () => {
59+
setEditingCommentId(null);
60+
};
61+
4862
const hasReplies = thread.replies.length > 0;
4963
const showReplies = thread.replies.length === 1 || isRepliesExpanded;
5064

@@ -53,8 +67,13 @@ export function CommentThread({
5367
{/* Parent comment */}
5468
<CommentItem
5569
comment={parentComment}
56-
onEdit={(id) => onEditComment(id, "")}
70+
onEdit={handleEditStart}
71+
onEditSubmit={handleEditSubmit}
72+
onEditCancel={handleEditCancel}
5773
onDelete={onDeleteComment}
74+
isEditing={editingCommentId === parentComment.id}
75+
members={members}
76+
membersLoading={membersLoading}
5877
/>
5978

6079
{/* Replies and actions - using flex layout with connector */}
@@ -92,8 +111,13 @@ export function CommentThread({
92111
key={reply.id}
93112
comment={reply}
94113
isReply
95-
onEdit={(id) => onEditComment(id, "")}
114+
onEdit={handleEditStart}
115+
onEditSubmit={handleEditSubmit}
116+
onEditCancel={handleEditCancel}
96117
onDelete={onDeleteComment}
118+
isEditing={editingCommentId === reply.id}
119+
members={members}
120+
membersLoading={membersLoading}
97121
/>
98122
))}
99123
</div>

0 commit comments

Comments
 (0)