grain.social is a photo sharing platform built on atproto.

feat: delete comments if logged in user

Changed files
+80 -19
src
modules
+80 -19
src/modules/comments.tsx
··· 158 158 <div class="flex-1 flex flex-col py-4 gap-6 overflow-y-scroll grain-scroll-area"> 159 159 {topLevel.map((comment) => ( 160 160 <div key={comment.cid} class="flex flex-col gap-4"> 161 - <CommentBlock comment={comment} /> 161 + <CommentBlock userProfile={userProfile} comment={comment} /> 162 162 163 163 {repliesByParent.get(comment.uri)?.map((reply) => ( 164 164 <div key={reply.cid} class="ml-6"> 165 - <CommentBlock comment={reply} /> 165 + <CommentBlock userProfile={userProfile} comment={reply} /> 166 166 </div> 167 167 ))} 168 168 </div> ··· 183 183 ); 184 184 } 185 185 186 - function CommentBlock({ comment }: Readonly<{ comment: CommentView }>) { 186 + function CommentBlock( 187 + { userProfile, comment }: Readonly< 188 + { userProfile: ProfileView; comment: CommentView } 189 + >, 190 + ) { 187 191 const gallery = isGalleryView(comment.subject) ? comment.subject : undefined; 188 192 const rkey = gallery ? new AtUri(gallery.uri).rkey : undefined; 189 193 return ( ··· 209 213 /> 210 214 )} 211 215 212 - {!comment.replyTo 213 - ? ( 214 - <button 215 - type="button" 216 - class="w-fit p-0 mt-2 cursor-pointer text-zinc-600 dark:text-zinc-500 font-semibold text-sm" 217 - hx-get={`/ui/comments/${gallery?.creator.did}/gallery/${rkey}/reply?comment=${ 218 - encodeURIComponent(comment.uri) 219 - }`} 220 - hx-trigger="click" 221 - hx-target="#dialog-target" 222 - hx-swap="innerHTML" 223 - > 224 - Reply 225 - </button> 226 - ) 227 - : null} 216 + <div class="flex gap-2"> 217 + {!comment.replyTo 218 + ? ( 219 + <button 220 + type="button" 221 + class="w-fit p-0 mt-2 cursor-pointer text-zinc-600 dark:text-zinc-500 font-semibold text-sm" 222 + hx-get={`/ui/comments/${gallery?.creator.did}/gallery/${rkey}/reply?comment=${ 223 + encodeURIComponent(comment.uri) 224 + }`} 225 + hx-trigger="click" 226 + hx-target="#dialog-target" 227 + hx-swap="innerHTML" 228 + > 229 + Reply 230 + </button> 231 + ) 232 + : null} 233 + {userProfile.did === comment.author.did 234 + ? ( 235 + <button 236 + type="button" 237 + class="w-fit p-0 mt-2 cursor-pointer text-zinc-600 dark:text-zinc-500 font-semibold text-sm" 238 + hx-delete={`/actions/comments/${gallery?.creator.did}/gallery/${rkey}?comment=${ 239 + encodeURIComponent(comment.uri) 240 + }`} 241 + hx-confirm="Are you sure you want to delete this comment?" 242 + hx-target="#dialog-target" 243 + hx-swap="innerHTML" 244 + > 245 + Delete 246 + </button> 247 + ) 248 + : null} 249 + </div> 228 250 </div> 229 251 </div> 230 252 ); ··· 324 346 ); 325 347 } catch (error) { 326 348 console.error("Error creating comment:", error); 349 + } 350 + 351 + const comments = getGalleryComments(gallery.uri, ctx); 352 + 353 + return ctx.html( 354 + <GalleryCommentsDialog 355 + userProfile={profile} 356 + comments={comments} 357 + gallery={gallery} 358 + />, 359 + ); 360 + }, 361 + ), 362 + 363 + route( 364 + "/actions/comments/:creatorDid/gallery/:rkey", 365 + ["DELETE"], 366 + async (req, params, ctx) => { 367 + const { did } = ctx.requireAuth(); 368 + const profile = getActorProfile(did, ctx); 369 + if (!profile) return ctx.next(); 370 + 371 + const url = new URL(req.url); 372 + const commentUri = url.searchParams.get("comment"); 373 + 374 + if (!commentUri) { 375 + return new Response("Comment URI is required", { status: 400 }); 376 + } 377 + 378 + const creatorDid = params.creatorDid; 379 + const rkey = params.rkey; 380 + 381 + const gallery = getGallery(creatorDid, rkey, ctx); 382 + if (!gallery) return ctx.next(); 383 + 384 + try { 385 + await ctx.deleteRecord(commentUri); 386 + } catch (error) { 387 + console.error("Error deleting comment:", error); 327 388 } 328 389 329 390 const comments = getGalleryComments(gallery.uri, ctx);