diff --git a/prisma/migrations/20260126103335_comment_id_in_scrap_medias/migration.sql b/prisma/migrations/20260126103335_comment_id_in_scrap_medias/migration.sql
new file mode 100644
index 000000000..4e5f2a216
--- /dev/null
+++ b/prisma/migrations/20260126103335_comment_id_in_scrap_medias/migration.sql
@@ -0,0 +1,5 @@
+-- AlterTable
+ALTER TABLE "ScrapMedias" ADD COLUMN "commentId" UUID;
+
+-- AddForeignKey
+ALTER TABLE "ScrapMedias" ADD CONSTRAINT "ScrapMedias_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/schema/comment.prisma b/prisma/schema/comment.prisma
index 430c3ac68..f365abf48 100644
--- a/prisma/schema/comment.prisma
+++ b/prisma/schema/comment.prisma
@@ -21,6 +21,10 @@ model Comment {
updatedAt DateTime @updatedAt @db.Timestamptz()
deletedAt DateTime? @db.Timestamptz()
+
+ scrapMedias ScrapMedia[]
+
+
@@map("Comments")
@@index([taskId, workspaceId, createdAt(sort: Desc)], name: "IX_Comments_taskId_workspaceId_createdAt")
}
diff --git a/prisma/schema/scrapMedia.prisma b/prisma/schema/scrapMedia.prisma
index 738f5c499..2f49c8eea 100644
--- a/prisma/schema/scrapMedia.prisma
+++ b/prisma/schema/scrapMedia.prisma
@@ -8,6 +8,10 @@ model ScrapMedia {
deletedAt DateTime? @db.Timestamptz()
templateId String? @db.Uuid
template TaskTemplate? @relation(fields: [templateId], references: [id], onDelete: Cascade)
+ commentId String? @db.Uuid
+ comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
+
+
@@index([createdAt])
@@index([filePath])
diff --git a/src/app/api/comments/comment.service.ts b/src/app/api/comments/comment.service.ts
index e9a8d61c8..fe8286143 100755
--- a/src/app/api/comments/comment.service.ts
+++ b/src/app/api/comments/comment.service.ts
@@ -388,17 +388,17 @@ export class CommentService extends BaseService {
for (const { originalSrc, newUrl } of replacements) {
htmlString = htmlString.replace(originalSrc, newUrl)
}
- // const filePaths = newFilePaths.map(({ newFilePath }) => newFilePath)
- // await this.db.scrapMedia.updateMany({
- // where: {
- // filePath: {
- // in: filePaths,
- // },
- // },
- // data: {
- // taskId: task_id,
- // },
- // }) //todo: add support for commentId in scrapMedias.
+ const filePaths = newFilePaths.map(({ newFilePath }) => newFilePath)
+ await this.db.scrapMedia.updateMany({
+ where: {
+ filePath: {
+ in: filePaths,
+ },
+ },
+ data: {
+ commentId: commentId,
+ },
+ })
return htmlString
} //todo: make this resuable since this is highly similar to what we are doing on tasks.
diff --git a/src/app/api/workers/scrap-medias/scrap-medias.service.ts b/src/app/api/workers/scrap-medias/scrap-medias.service.ts
index ca637f7e8..aa0752f81 100644
--- a/src/app/api/workers/scrap-medias/scrap-medias.service.ts
+++ b/src/app/api/workers/scrap-medias/scrap-medias.service.ts
@@ -29,6 +29,10 @@ export class ScrapMediaService {
.map((image) => image.templateId)
.filter((templateId): templateId is string => templateId !== null)
+ const commentIds = scrapMedias
+ .map((medias) => medias.commentId)
+ .filter((commentId): commentId is string => commentId !== null)
+
const tasks = taskIds.length
? await db.task.findMany({
where: {
@@ -46,41 +50,51 @@ export class ScrapMediaService {
})
: []
+ const comments =
+ commentIds.length > 0
+ ? await db.comment.findMany({
+ where: {
+ id: { in: commentIds },
+ },
+ })
+ : []
+
const scrapMediasToDelete = []
const scrapMediasToDeleteFromBucket = []
- for (const image of scrapMedias) {
+ for (const media of scrapMedias) {
try {
// For each scrap image, check if the task or taskTemplate still has the img url in its body
- const task = tasks.find((_task) => _task.id === image.taskId)
- const taskTemplate = taskTemplates.find((_template) => _template.id === image.templateId)
+ const task = tasks.find((_task) => _task.id === media.taskId)
+ const taskTemplate = taskTemplates.find((_template) => _template.id === media.templateId)
+ const comment = comments.find((_comment) => _comment.id === media.commentId)
- const isInTaskBody = task && (task.body || '').includes(image.filePath)
- const isInTemplateBody = taskTemplate && (taskTemplate.body || '').includes(image.filePath)
+ const isInTaskBody = task && (task.body || '').includes(media.filePath)
+ const isInTemplateBody = taskTemplate && (taskTemplate.body || '').includes(media.filePath)
+ const isInCommentBody = comment && (comment.content || '').includes(media.filePath)
- if (!task && !taskTemplate) {
- console.error('Could not find task for scrap image', image)
- scrapMediasToDelete.push(image.id)
- scrapMediasToDeleteFromBucket.push(image.filePath)
+ if (!task && !taskTemplate && !comment) {
+ console.error('Could not find location of scrap media', media)
+ scrapMediasToDelete.push(media.id)
+ scrapMediasToDeleteFromBucket.push(media.filePath)
continue
}
- // If image is in task body
- if (isInTaskBody || isInTemplateBody) {
- scrapMediasToDelete.push(image.id)
+ // If media is valid
+ if (isInTaskBody || isInTemplateBody || isInCommentBody) {
+ scrapMediasToDelete.push(media.id)
continue
}
- // If image is not in task body
-
- scrapMediasToDeleteFromBucket.push(image.filePath)
- scrapMediasToDelete.push(image.id)
+ // If media is not valid
+ scrapMediasToDeleteFromBucket.push(media.filePath)
+ scrapMediasToDelete.push(media.id)
} catch (e: unknown) {
- console.error('Error processing scrap image', e)
+ console.error('Error processing scrap media', e)
}
}
if (!!scrapMediasToDeleteFromBucket.length)
await db.attachment.deleteMany({ where: { filePath: { in: scrapMediasToDeleteFromBucket } } })
-
+ console.info('ScrapMediaWorker#deleteFromBucket | Deleting these medias', scrapMediasToDeleteFromBucket)
// remove attachments from bucket
await supabase.removeAttachmentsFromBucket(scrapMediasToDeleteFromBucket)
diff --git a/src/app/detail/ui/NewTaskCard.tsx b/src/app/detail/ui/NewTaskCard.tsx
index c5105b79e..a115d2f50 100644
--- a/src/app/detail/ui/NewTaskCard.tsx
+++ b/src/app/detail/ui/NewTaskCard.tsx
@@ -20,7 +20,7 @@ import { selectCreateTemplate } from '@/redux/features/templateSlice'
import { DateString } from '@/types/date'
import { CreateTaskRequest, Viewers } from '@/types/dto/tasks.dto'
import { WorkflowStateResponse } from '@/types/dto/workflowStates.dto'
-import { FilterByOptions, IAssigneeCombined, InputValue, ITemplate, UserIds } from '@/types/interfaces'
+import { AttachmentTypes, FilterByOptions, IAssigneeCombined, InputValue, ITemplate, UserIds } from '@/types/interfaces'
import { getAssigneeName, UserIdsType } from '@/utils/assignee'
import { deleteEditorAttachmentsHandler, uploadAttachmentHandler } from '@/utils/attachmentUtils'
import { createUploadFn } from '@/utils/createUploadFn'
@@ -340,7 +340,7 @@ export const NewTaskCard = ({
placeholder="Add description.."
editorClass="tapwrite-task-editor"
uploadFn={uploadFn}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', null, null)}
+ deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.TASK)}
attachmentLayout={(props) => (
)}
diff --git a/src/app/detail/ui/TaskEditor.tsx b/src/app/detail/ui/TaskEditor.tsx
index e264e5a07..1108b9504 100644
--- a/src/app/detail/ui/TaskEditor.tsx
+++ b/src/app/detail/ui/TaskEditor.tsx
@@ -12,7 +12,7 @@ import { selectTaskDetails, setOpenImage, setShowConfirmDeleteModal } from '@/re
import store from '@/redux/store'
import { CreateAttachmentRequest } from '@/types/dto/attachments.dto'
import { TaskResponse } from '@/types/dto/tasks.dto'
-import { UserType } from '@/types/interfaces'
+import { AttachmentTypes, UserType } from '@/types/interfaces'
import { getDeleteMessage } from '@/utils/dialogMessages'
import { deleteEditorAttachmentsHandler, getAttachmentPayload, uploadAttachmentHandler } from '@/utils/attachmentUtils'
import { Box } from '@mui/material'
@@ -195,7 +195,7 @@ export const TaskEditor = ({
placeholder="Add description..."
uploadFn={uploadFn}
handleImageDoubleClick={handleImagePreview}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', task_id, null)}
+ deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.TASK, task_id)}
attachmentLayout={(props) => }
addAttachmentButton
maxUploadLimit={MAX_UPLOAD_LIMIT}
diff --git a/src/app/manage-templates/ui/NewTemplateCard.tsx b/src/app/manage-templates/ui/NewTemplateCard.tsx
index 919afd292..c50aa1183 100644
--- a/src/app/manage-templates/ui/NewTemplateCard.tsx
+++ b/src/app/manage-templates/ui/NewTemplateCard.tsx
@@ -156,7 +156,7 @@ export const NewTemplateCard = ({
placeholder="Add description.."
editorClass="tapwrite-task-editor"
uploadFn={uploadFn}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', null, null)}
+ deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.TEMPLATE)}
attachmentLayout={(props) => (
)}
diff --git a/src/app/manage-templates/ui/TemplateDetails.tsx b/src/app/manage-templates/ui/TemplateDetails.tsx
index b9d6dfcba..836b8dd10 100644
--- a/src/app/manage-templates/ui/TemplateDetails.tsx
+++ b/src/app/manage-templates/ui/TemplateDetails.tsx
@@ -164,7 +164,9 @@ export default function TemplateDetails({
placeholder="Add description..."
uploadFn={uploadFn}
handleImageDoubleClick={handleImagePreview}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', template_id, null)}
+ deleteEditorAttachments={(url) =>
+ deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.TEMPLATE, template_id)
+ }
attachmentLayout={(props) => }
addAttachmentButton
maxUploadLimit={MAX_UPLOAD_LIMIT}
diff --git a/src/app/manage-templates/ui/TemplateForm.tsx b/src/app/manage-templates/ui/TemplateForm.tsx
index b5ff8d4f5..34bdfbe9d 100644
--- a/src/app/manage-templates/ui/TemplateForm.tsx
+++ b/src/app/manage-templates/ui/TemplateForm.tsx
@@ -160,14 +160,7 @@ const NewTemplateFormInputs = () => {
placeholder="Add description.."
editorClass="tapwrite-description-h-full"
uploadFn={uploadFn}
- deleteEditorAttachments={(url) =>
- deleteEditorAttachmentsHandler(
- url,
- token ?? '',
- null,
- targetMethod == TargetMethod.POST ? null : targetTemplateId,
- )
- }
+ deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.TEMPLATE)}
attachmentLayout={(props) => }
maxUploadLimit={MAX_UPLOAD_LIMIT}
parentContainerStyle={{ gap: '0px', minHeight: '60px' }}
diff --git a/src/app/ui/NewTaskForm.tsx b/src/app/ui/NewTaskForm.tsx
index 016ea5790..feb3ef7d0 100644
--- a/src/app/ui/NewTaskForm.tsx
+++ b/src/app/ui/NewTaskForm.tsx
@@ -32,6 +32,7 @@ import store from '@/redux/store'
import { HomeParamActions } from '@/types/constants'
import { WorkflowStateResponse } from '@/types/dto/workflowStates.dto'
import {
+ AttachmentTypes,
CreateTaskErrors,
FilterByOptions,
FilterOptions,
@@ -619,7 +620,7 @@ const NewTaskFormInputs = ({ isEditorReadonly }: NewTaskFormInputsProps) => {
editorClass="tapwrite-description-h-full"
uploadFn={uploadFn}
readonly={isEditorReadonly}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', null, null)}
+ deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.TASK)}
attachmentLayout={(props) => }
maxUploadLimit={MAX_UPLOAD_LIMIT}
parentContainerStyle={{ gap: '0px', minHeight: '60px' }}
diff --git a/src/cmd/backfill-attachments/index.ts b/src/cmd/backfill-attachments/index.ts
new file mode 100644
index 000000000..f18bbe630
--- /dev/null
+++ b/src/cmd/backfill-attachments/index.ts
@@ -0,0 +1,148 @@
+import DBClient from '@/lib/db'
+import { CreateAttachmentRequestSchema } from '@/types/dto/attachments.dto'
+import { getFilePathFromUrl } from '@/utils/signedUrlReplacer'
+import { SupabaseActions } from '@/utils/SupabaseActions'
+import { Task, Comment } from '@prisma/client'
+
+const ATTACHMENT_TAG_REGEX = /<\s*[a-zA-Z]+\s+[^>]*data-type="attachment"[^>]*src="([^"]+)"[^>]*>/g
+const IMG_TAG_REGEX = /
]*src="([^"]+)"[^>]*>/g
+
+interface AttachmentRequest {
+ createdById: string
+ workspaceId: string
+ attachmentRequest: ReturnType
+}
+
+interface ProcessedAttachments {
+ taskAttachmentRequests: AttachmentRequest[]
+ commentAttachmentRequests: AttachmentRequest[]
+ filesNotFoundInBucket: string[]
+}
+
+async function extractAttachmentsFromContent(
+ content: string,
+ supabaseActions: SupabaseActions,
+ filesNotFound: string[],
+): Promise> {
+ const attachments: Array<{ filePath: string; fileSize?: number; fileType?: string; fileName?: string }> = []
+ const regexes = [IMG_TAG_REGEX, ATTACHMENT_TAG_REGEX]
+
+ for (const regex of regexes) {
+ let match
+ regex.lastIndex = 0
+ while ((match = regex.exec(content)) !== null) {
+ const originalSrc = match[1]
+ const filePath = getFilePathFromUrl(originalSrc)
+ if (!filePath) continue
+ const fileMetaData = await supabaseActions.getMetaData(filePath)
+ if (!fileMetaData) {
+ filesNotFound.push(filePath)
+ continue
+ }
+ const fileName = filePath.split('/').pop()
+ attachments.push({
+ filePath,
+ fileSize: fileMetaData.size,
+ fileType: fileMetaData.contentType,
+ fileName,
+ })
+ }
+ }
+ return attachments
+}
+
+async function createAttachmentRequests(tasks: Task[], comments: Comment[]): Promise {
+ const taskAttachmentRequests: AttachmentRequest[] = []
+ const commentAttachmentRequests: AttachmentRequest[] = []
+ const filesNotFoundInBucket: string[] = []
+ const supabaseActions = new SupabaseActions()
+
+ for (const task of tasks) {
+ const bodyString = task.body ?? ''
+ const attachments = await extractAttachmentsFromContent(bodyString, supabaseActions, filesNotFoundInBucket)
+ for (const attachment of attachments) {
+ taskAttachmentRequests.push({
+ createdById: task.createdById,
+ workspaceId: task.workspaceId,
+ attachmentRequest: CreateAttachmentRequestSchema.parse({
+ taskId: task.id,
+ ...attachment,
+ }),
+ })
+ }
+ }
+
+ for (const comment of comments) {
+ const contentString = comment.content ?? ''
+ const attachments = await extractAttachmentsFromContent(contentString, supabaseActions, filesNotFoundInBucket)
+ for (const attachment of attachments) {
+ commentAttachmentRequests.push({
+ createdById: comment.initiatorId,
+ workspaceId: comment.workspaceId,
+ attachmentRequest: CreateAttachmentRequestSchema.parse({
+ commentId: comment.id,
+ ...attachment,
+ }),
+ })
+ }
+ }
+
+ if (taskAttachmentRequests.length) {
+ console.info('🔥 Task attachments to be populated:', taskAttachmentRequests.length)
+ }
+ if (commentAttachmentRequests.length) {
+ console.info('🔥 Comment attachments to be populated:', commentAttachmentRequests.length)
+ }
+ if (filesNotFoundInBucket.length) {
+ console.warn('⚠️ Files not found in bucket:', filesNotFoundInBucket)
+ }
+
+ return { taskAttachmentRequests, commentAttachmentRequests, filesNotFoundInBucket }
+}
+
+async function createAttachmentsInDatabase(
+ db: ReturnType,
+ attachmentRequests: AttachmentRequest[],
+) {
+ let created = 0
+ let skipped = 0
+
+ for (const { createdById, workspaceId, attachmentRequest } of attachmentRequests) {
+ try {
+ const existing = await db.attachment.findFirst({
+ where: { filePath: attachmentRequest.filePath },
+ })
+ if (existing) {
+ skipped++
+ continue
+ }
+ await db.attachment.create({
+ data: {
+ ...attachmentRequest,
+ createdById,
+ workspaceId,
+ },
+ })
+ created++
+ } catch (error) {
+ console.error('❌ Failed to create attachment:', attachmentRequest, error)
+ }
+ }
+
+ console.info(`📊 Created: ${created}, Skipped (already exists): ${skipped}`)
+}
+
+async function run() {
+ console.info('🧑🏻💻 Backfilling attachment entries for tasks and comments')
+
+ const db = DBClient.getInstance()
+ const [tasks, comments] = await Promise.all([db.task.findMany(), db.comment.findMany()])
+
+ const { taskAttachmentRequests, commentAttachmentRequests } = await createAttachmentRequests(tasks, comments)
+
+ await createAttachmentsInDatabase(db, [...taskAttachmentRequests, ...commentAttachmentRequests])
+
+ console.info('✅ Backfill complete')
+}
+
+run()
diff --git a/src/components/cards/CommentCard.tsx b/src/components/cards/CommentCard.tsx
index 3c3540f03..d408aed74 100644
--- a/src/components/cards/CommentCard.tsx
+++ b/src/components/cards/CommentCard.tsx
@@ -29,7 +29,7 @@ import store from '@/redux/store'
import { CommentResponse, CreateComment, UpdateComment } from '@/types/dto/comment.dto'
import { AttachmentTypes, IAssigneeCombined } from '@/types/interfaces'
import { getAssigneeName } from '@/utils/assignee'
-import { deleteEditorAttachmentsHandler, getAttachmentPayload } from '@/utils/attachmentUtils'
+import { deleteEditorAttachmentsHandler, getAttachmentPayload, getCustomFilePath } from '@/utils/attachmentUtils'
import { createUploadFn } from '@/utils/createUploadFn'
import { fetcher } from '@/utils/fetcher'
import { getTimeDifference } from '@/utils/getTimeDifference'
@@ -330,7 +330,13 @@ export const CommentCard = ({
editorClass={isReadOnly ? 'tapwrite-comment' : 'tapwrite-comment-editable'}
addAttachmentButton={!isReadOnly}
uploadFn={uploadFn}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', task_id, null)}
+ deleteEditorAttachments={(url) => {
+ const commentId = z.string().parse(commentIdRef.current)
+ const customFilePath = tokenPayload?.workspaceId
+ ? getCustomFilePath(tokenPayload?.workspaceId, task_id, commentId, url)
+ : undefined
+ return deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.COMMENT, commentId, customFilePath)
+ }}
maxUploadLimit={MAX_UPLOAD_LIMIT}
attachmentLayout={(props) => }
hardbreak
diff --git a/src/components/cards/ReplyCard.tsx b/src/components/cards/ReplyCard.tsx
index b40801ba7..23a5a8d1e 100644
--- a/src/components/cards/ReplyCard.tsx
+++ b/src/components/cards/ReplyCard.tsx
@@ -19,7 +19,7 @@ import { selectTaskBoard } from '@/redux/features/taskBoardSlice'
import { UpdateComment } from '@/types/dto/comment.dto'
import { AttachmentTypes, IAssigneeCombined } from '@/types/interfaces'
import { getAssigneeName } from '@/utils/assignee'
-import { deleteEditorAttachmentsHandler, getAttachmentPayload } from '@/utils/attachmentUtils'
+import { deleteEditorAttachmentsHandler, getAttachmentPayload, getCustomFilePath } from '@/utils/attachmentUtils'
import { createUploadFn } from '@/utils/createUploadFn'
import { getTimeDifference } from '@/utils/getTimeDifference'
import { isTapwriteContentEmpty } from '@/utils/isTapwriteContentEmpty'
@@ -247,7 +247,13 @@ export const ReplyCard = ({
editorClass={isReadOnly ? 'tapwrite-comment' : 'tapwrite-comment-editable'}
addAttachmentButton={!isReadOnly}
uploadFn={uploadFn}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', task_id, null)}
+ deleteEditorAttachments={(url) => {
+ const commentId = z.string().parse(commentIdRef.current)
+ const customFilePath = tokenPayload?.workspaceId
+ ? getCustomFilePath(tokenPayload?.workspaceId, task_id, commentId, url)
+ : undefined
+ return deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.COMMENT, commentId, customFilePath)
+ }}
maxUploadLimit={MAX_UPLOAD_LIMIT}
attachmentLayout={(props) => }
hardbreak
diff --git a/src/components/inputs/CommentInput.tsx b/src/components/inputs/CommentInput.tsx
index 3d0995d9c..9b4592f98 100644
--- a/src/components/inputs/CommentInput.tsx
+++ b/src/components/inputs/CommentInput.tsx
@@ -10,6 +10,7 @@ import { selectAuthDetails } from '@/redux/features/authDetailsSlice'
import { selectTaskBoard } from '@/redux/features/taskBoardSlice'
import { CreateAttachmentRequest } from '@/types/dto/attachments.dto'
import { CreateComment } from '@/types/dto/comment.dto'
+import { AttachmentTypes } from '@/types/interfaces'
import { deleteEditorAttachmentsHandler, uploadAttachmentHandler } from '@/utils/attachmentUtils'
import { createUploadFn } from '@/utils/createUploadFn'
import { getMentionsList } from '@/utils/getMentionList'
@@ -171,7 +172,7 @@ export const CommentInput = ({ createComment, task_id, token }: Prop) => {
whiteSpace: 'pre-wrap',
}}
uploadFn={uploadFn}
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', task_id, null)}
+ deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.COMMENT)}
attachmentLayout={(props) => (
)}
diff --git a/src/components/inputs/ReplyInput.tsx b/src/components/inputs/ReplyInput.tsx
index 3deb1bf01..fd1b125b1 100644
--- a/src/components/inputs/ReplyInput.tsx
+++ b/src/components/inputs/ReplyInput.tsx
@@ -17,6 +17,7 @@ import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } fr
import { useSelector } from 'react-redux'
import { Tapwrite } from 'tapwrite'
import { createUploadFn } from '@/utils/createUploadFn'
+import { AttachmentTypes } from '@/types/interfaces'
interface ReplyInputProps {
token: string
@@ -210,7 +211,7 @@ export const ReplyInput = ({
flexGrow: 1,
}}
addAttachmentButton
- deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', task_id, null)}
+ deleteEditorAttachments={(url) => deleteEditorAttachmentsHandler(url, token ?? '', AttachmentTypes.COMMENT)}
uploadFn={uploadFn}
maxUploadLimit={MAX_UPLOAD_LIMIT}
endButtons={}
diff --git a/src/types/common.ts b/src/types/common.ts
index eb18881fc..55146a247 100644
--- a/src/types/common.ts
+++ b/src/types/common.ts
@@ -216,8 +216,9 @@ export const NotificationRequestBodySchema = z
export const ScrapMediaRequestSchema = z.object({
filePath: z.string(),
- taskId: z.string().uuid().nullable(),
- templateId: z.string().uuid().nullable(),
+ taskId: z.string().uuid().optional(),
+ templateId: z.string().uuid().optional(),
+ commentId: z.string().uuid().optional(),
})
export type ScrapMediaRequest = z.infer
diff --git a/src/utils/attachmentUtils.ts b/src/utils/attachmentUtils.ts
index d2ff7edad..12d9af463 100644
--- a/src/utils/attachmentUtils.ts
+++ b/src/utils/attachmentUtils.ts
@@ -55,15 +55,19 @@ export const uploadAttachmentHandler = async (
export const deleteEditorAttachmentsHandler = async (
url: string,
token: string,
- task_id: string | null,
- template_id: string | null,
+ entityType: AttachmentTypes,
+ entityId?: string,
+ customFilePath?: string, //used only for comments and replies. Because newly created comments and replies have mismatched urls. And the url doesnt refresh without a page refresh.
) => {
const filePath = getFilePathFromUrl(url)
if (filePath) {
const payload: ScrapMediaRequest = {
- filePath: filePath,
- taskId: task_id,
- templateId: template_id,
+ filePath: customFilePath ?? filePath,
+ ...(entityType === AttachmentTypes.TASK
+ ? { taskId: entityId }
+ : entityType === AttachmentTypes.TEMPLATE
+ ? { templateId: entityId }
+ : { commentId: entityId }),
}
postScrapMedia(token, payload)
}
@@ -92,3 +96,12 @@ export const getFileNameFromPath = (path: string): string => {
const segments = path.split('/').filter(Boolean)
return segments[segments.length - 1] || ''
}
+
+export const getCustomFilePath = (workspaceId: string, task_id: string, commentId: string, url: string) => {
+ const filePath = getFilePathFromUrl(url)
+ if (!filePath) {
+ return undefined
+ }
+ const fileName = getFileNameFromPath(filePath)
+ return `${workspaceId}/${task_id}/comments/${commentId}/${fileName}`
+}