diff --git a/src/elements/content-sidebar/activity-feed-v2/FeedItemRow.tsx b/src/elements/content-sidebar/activity-feed-v2/FeedItemRow.tsx index cd2af75786..8be0c04ade 100644 --- a/src/elements/content-sidebar/activity-feed-v2/FeedItemRow.tsx +++ b/src/elements/content-sidebar/activity-feed-v2/FeedItemRow.tsx @@ -118,7 +118,10 @@ const FeedItemRow = ({ const serialized = serializeEditorContent(content); if (!serialized || !serialized.text.trim()) return; if (id === item.id) { - onCommentUpdate?.(id, serialized.text, undefined, serialized.hasMention, permissions); + const text = item.annotationTimestampMarkup + ? `${item.annotationTimestampMarkup} ${serialized.text}` + : serialized.text; + onCommentUpdate?.(id, text, undefined, serialized.hasMention, permissions); return; } dispatchReplyEdit({ diff --git a/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx b/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx index 5df04af2ef..20a98f948d 100644 --- a/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx +++ b/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx @@ -417,6 +417,44 @@ describe('elements/content-sidebar/activity-feed-v2/FeedItemRow', () => { expect(mockedSeekVideoToMs).toHaveBeenCalledWith(8055); }); + + test('should re-prepend timestamp markup when editing a video comment so the badge survives the update', () => { + mockedSerializeEditorContent.mockReturnValue({ hasMention: false, text: 'edited-text' }); + const onCommentUpdate = jest.fn(); + const timestampedComment: TransformedCommentItem = { + ...mockComment, + annotationTarget: { timestamp: '0:08', type: AnnotationBadgeType.Frame }, + annotationTimestampMarkup: '#[timestamp:8055,versionId:2390295731268]', + annotationTimestampMs: 8055, + }; + render(); + + lastThreadedAnnotationProps.onEdit?.('comment-1', { type: 'doc', content: [] }); + + expect(onCommentUpdate).toHaveBeenCalledWith( + 'comment-1', + '#[timestamp:8055,versionId:2390295731268] edited-text', + undefined, + false, + commentPermissions, + ); + }); + + test('should not modify edit text for a regular comment without timestamp markup', () => { + mockedSerializeEditorContent.mockReturnValue({ hasMention: false, text: 'edited-text' }); + const onCommentUpdate = jest.fn(); + render(); + + lastThreadedAnnotationProps.onEdit?.('comment-1', { type: 'doc', content: [] }); + + expect(onCommentUpdate).toHaveBeenCalledWith( + 'comment-1', + 'edited-text', + undefined, + false, + commentPermissions, + ); + }); }); describe('annotation rendering', () => { diff --git a/src/elements/content-sidebar/activity-feed-v2/__tests__/transformers.test.ts b/src/elements/content-sidebar/activity-feed-v2/__tests__/transformers.test.ts index c319f685c6..593764b840 100644 --- a/src/elements/content-sidebar/activity-feed-v2/__tests__/transformers.test.ts +++ b/src/elements/content-sidebar/activity-feed-v2/__tests__/transformers.test.ts @@ -689,6 +689,7 @@ describe('elements/content-sidebar/activity-feed-v2/transformers', () => { const result = extractTimestampMarkup('#[timestamp:8055,versionId:2390295731268] wowo'); expect(result.cleanText).toBe('wowo'); expect(result.target).toEqual({ timestamp: '0:08', type: 'frame' }); + expect(result.timestampMarkup).toBe('#[timestamp:8055,versionId:2390295731268]'); expect(result.timestampMs).toBe(8055); }); @@ -696,6 +697,7 @@ describe('elements/content-sidebar/activity-feed-v2/transformers', () => { const result = extractTimestampMarkup('#[timestamp:65000] hello'); expect(result.cleanText).toBe('hello'); expect(result.target).toEqual({ timestamp: '1:05', type: 'frame' }); + expect(result.timestampMarkup).toBe('#[timestamp:65000]'); expect(result.timestampMs).toBe(65000); }); @@ -759,6 +761,7 @@ describe('elements/content-sidebar/activity-feed-v2/transformers', () => { expect(result!.type).toBe('comment'); if (result!.type === 'comment') { expect(result.annotationTarget).toEqual({ timestamp: '0:08', type: 'frame' }); + expect(result.annotationTimestampMarkup).toBe('#[timestamp:8055,versionId:2390295731268]'); expect(result.annotationTimestampMs).toBe(8055); } }); @@ -778,6 +781,7 @@ describe('elements/content-sidebar/activity-feed-v2/transformers', () => { const result = transformFeedItem(comment as unknown as FeedItem); if (result!.type === 'comment') { expect(result.annotationTarget).toBeUndefined(); + expect(result.annotationTimestampMarkup).toBeUndefined(); expect(result.annotationTimestampMs).toBeUndefined(); } }); diff --git a/src/elements/content-sidebar/activity-feed-v2/transformers.ts b/src/elements/content-sidebar/activity-feed-v2/transformers.ts index 93497bd133..7518ebbd93 100644 --- a/src/elements/content-sidebar/activity-feed-v2/transformers.ts +++ b/src/elements/content-sidebar/activity-feed-v2/transformers.ts @@ -46,7 +46,12 @@ const TIMESTAMP_MARKUP_REGEX = /^#\[timestamp:(\d+)(?:,versionId:\d+)?\]\s*/; export const extractTimestampMarkup = ( text: string, -): { cleanText: string; target?: AnnotationBadgeTargetType; timestampMs?: number } => { +): { + cleanText: string; + target?: AnnotationBadgeTargetType; + timestampMarkup?: string; + timestampMs?: number; +} => { if (!text) return { cleanText: '' }; const match = text.match(TIMESTAMP_MARKUP_REGEX); if (!match) return { cleanText: text }; @@ -60,7 +65,7 @@ export const extractTimestampMarkup = ( timestamp: convertMillisecondsToTimestamp(ms), type: AnnotationBadgeType.Frame, }; - return { cleanText, target, timestampMs: ms }; + return { cleanText, target, timestampMarkup: fullMatch.trimEnd(), timestampMs: ms }; }; const parseLine = (line: string, authorId: string): (MentionNode | TextNode)[] => { @@ -292,11 +297,17 @@ export const transformFeedItem = (item: FeedItem, currentUserId?: string): Trans const comment = item as unknown as Comment; const commentIsResolved = comment.status === 'resolved'; const rawText = comment.tagged_message || comment.message || ''; - const { cleanText, target: annotationTarget, timestampMs } = extractTimestampMarkup(rawText); + const { + cleanText, + target: annotationTarget, + timestampMarkup, + timestampMs, + } = extractTimestampMarkup(rawText); const root = commentToTextMessage(comment, cleanText); const replies = (comment.replies ?? []).map(reply => commentToTextMessage(reply)); return { annotationTarget, + annotationTimestampMarkup: timestampMarkup, annotationTimestampMs: timestampMs, id: comment.id, isResolved: commentIsResolved, diff --git a/src/elements/content-sidebar/activity-feed-v2/types.ts b/src/elements/content-sidebar/activity-feed-v2/types.ts index 9710a5ad2b..75aa4757af 100644 --- a/src/elements/content-sidebar/activity-feed-v2/types.ts +++ b/src/elements/content-sidebar/activity-feed-v2/types.ts @@ -47,6 +47,7 @@ type ResolvedInfo = { export type TransformedCommentItem = { annotationTarget?: AnnotationBadgeTargetType; + annotationTimestampMarkup?: string; annotationTimestampMs?: number; id: string; messages: TextMessageType[];