mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix: clear selected preview comments (#3144)
* fix: The "clear" button for comments is not functioning; the comments no longer have serial numbers.
* fix: The active pin always renders {visibleComments.length + 1}, but showActivePin (= commentCreateMode) is also true while editing an existing comment: onOpenComment at line 6821 calls setCommentCreateMode(true) and setActiveCommentTarget(snapshot) against the saved comment the user just clicked. In that path the overlay now stamps a stale number on top of an existing saved marker (e.g. clicking the pin showing 2 paints an additional 3 at the same position), which contradicts the invariant this PR is restoring — that preview-area numbers match the side-panel numbers.
---------
Co-authored-by: 郑惠 <14549727+felicia-study@user.noreply.gitee.com>
This commit is contained in:
parent
ed16de6f92
commit
b746efefe2
2 changed files with 103 additions and 9 deletions
|
|
@ -1996,7 +1996,7 @@ export function CommentSidePanel({
|
|||
<div className="comment-side-empty">
|
||||
{t('chat.comments.emptySaved')}
|
||||
</div>
|
||||
) : sorted.map((comment) => {
|
||||
) : sorted.map((comment, index) => {
|
||||
const selected = visibleSelectedIds.has(comment.id);
|
||||
const active = comment.id === activeCommentId;
|
||||
return (
|
||||
|
|
@ -2009,7 +2009,7 @@ export function CommentSidePanel({
|
|||
>
|
||||
<div className="comment-side-item-head">
|
||||
<span className="comment-side-author">
|
||||
<strong>{commentDisplayLabel(comment, t)}</strong>
|
||||
<strong>{`${index + 1}. ${commentDisplayLabel(comment, t)}`}</strong>
|
||||
</span>
|
||||
<span className="comment-side-time">{formatCommentTime(comment.createdAt, t)}</span>
|
||||
<button
|
||||
|
|
@ -2834,6 +2834,12 @@ function CommentPreviewOverlays({
|
|||
.filter((item): item is { comment: PreviewComment; index: number; snapshot: PreviewCommentSnapshot } =>
|
||||
Boolean(item.snapshot),
|
||||
);
|
||||
const activeSavedIndex = activeTarget
|
||||
? visibleComments.findIndex(({ snapshot }) => snapshot.elementId === activeTarget.elementId)
|
||||
: -1;
|
||||
const activePinNumber = activeSavedIndex >= 0
|
||||
? activeSavedIndex + 1
|
||||
: visibleComments.length + 1;
|
||||
const targetOverlay = activeTarget ?? hoveredTarget;
|
||||
return (
|
||||
<div className="comment-overlay-layer" aria-hidden={false}>
|
||||
|
|
@ -2861,10 +2867,10 @@ function CommentPreviewOverlays({
|
|||
event.stopPropagation();
|
||||
onOpenComment(comment, snapshot);
|
||||
}}
|
||||
title={`${label}: ${comment.note}`}
|
||||
title={`${index + 1}. ${label}: ${comment.note}`}
|
||||
aria-label={`Open comment for ${label}`}
|
||||
>
|
||||
C
|
||||
{index + 1}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -2884,7 +2890,7 @@ function CommentPreviewOverlays({
|
|||
data-testid="comment-active-pin"
|
||||
aria-hidden="true"
|
||||
>
|
||||
C
|
||||
{activePinNumber}
|
||||
</div>
|
||||
) : null}
|
||||
{boardTool === 'pod' && strokePoints.length > 1 ? (
|
||||
|
|
@ -6181,7 +6187,25 @@ function HtmlViewer({
|
|||
return next;
|
||||
});
|
||||
}}
|
||||
onClearSelection={() => setSelectedSideCommentIds(new Set())}
|
||||
onClearSelection={() => {
|
||||
if (selectedSideCommentIds.size === 0) return;
|
||||
if (!onRemovePreviewComment) {
|
||||
setSelectedSideCommentIds(new Set());
|
||||
return;
|
||||
}
|
||||
const selectedIds = new Set(selectedSideCommentIds);
|
||||
const targets = visibleSideComments
|
||||
.filter((comment) => selectedIds.has(comment.id))
|
||||
.map((comment) => comment.id);
|
||||
if (targets.length === 0) {
|
||||
setSelectedSideCommentIds(new Set());
|
||||
return;
|
||||
}
|
||||
void (async () => {
|
||||
await Promise.allSettled(targets.map((id) => onRemovePreviewComment(id)));
|
||||
setSelectedSideCommentIds(new Set());
|
||||
})();
|
||||
}}
|
||||
onReply={(comment) => {
|
||||
// Reply == edit on a flat-thread model: prefill the
|
||||
// popover with the existing note so the user sees and
|
||||
|
|
|
|||
|
|
@ -1696,8 +1696,8 @@ describe('FileViewer tweaks toolbar', () => {
|
|||
fireEvent.click(screen.getByTestId('comment-panel-toggle'));
|
||||
|
||||
expect(screen.getByTestId('comment-side-panel')).toBeTruthy();
|
||||
expect(screen.getByTestId('comment-saved-marker-pin-newer').textContent).toBe('C');
|
||||
expect(screen.getByTestId('comment-saved-marker-pin-older').textContent).toBe('C');
|
||||
expect(screen.getByTestId('comment-saved-marker-pin-newer').textContent).toBe('1');
|
||||
expect(screen.getByTestId('comment-saved-marker-pin-older').textContent).toBe('2');
|
||||
|
||||
clickAgentTool('board-mode-toggle');
|
||||
|
||||
|
|
@ -1722,7 +1722,7 @@ describe('FileViewer tweaks toolbar', () => {
|
|||
},
|
||||
}));
|
||||
|
||||
expect((await screen.findByTestId('comment-active-pin')).textContent).toBe('C');
|
||||
expect((await screen.findByTestId('comment-active-pin')).textContent).toBe('3');
|
||||
expect(screen.getByTestId('comment-saved-marker-pin-newer')).toBeTruthy();
|
||||
expect(screen.getByTestId('comment-saved-marker-pin-older')).toBeTruthy();
|
||||
|
||||
|
|
@ -1732,6 +1732,7 @@ describe('FileViewer tweaks toolbar', () => {
|
|||
expect(activeItem?.className).toContain('active');
|
||||
expect(activeItem?.getAttribute('aria-current')).toBe('true');
|
||||
});
|
||||
expect(screen.getByTestId('comment-active-pin').textContent).toBe('1');
|
||||
expect(document.querySelector('[data-comment-id="comment-older"]')?.className).not.toContain('active');
|
||||
});
|
||||
|
||||
|
|
@ -2076,6 +2077,75 @@ describe('FileViewer tweaks toolbar', () => {
|
|||
expect(screen.queryByTestId('comment-side-selectbar')).toBeNull();
|
||||
expect(screen.queryByTestId('comment-side-collapsed-rail')).toBeNull();
|
||||
});
|
||||
|
||||
it('deletes selected comments when clear is clicked', async () => {
|
||||
const removed: string[] = [];
|
||||
|
||||
function Harness() {
|
||||
const [comments, setComments] = useState<PreviewComment[]>([
|
||||
{
|
||||
id: 'comment-1',
|
||||
projectId: 'project-1',
|
||||
conversationId: 'conversation-1',
|
||||
filePath: 'preview.html',
|
||||
elementId: 'pin-1',
|
||||
selector: '[data-od-pin="pin-1"]',
|
||||
label: 'pin-1',
|
||||
text: '',
|
||||
htmlHint: '',
|
||||
position: { x: 16, y: 20, width: 18, height: 18 },
|
||||
note: 'First',
|
||||
status: 'open',
|
||||
createdAt: 10,
|
||||
updatedAt: 10,
|
||||
},
|
||||
{
|
||||
id: 'comment-2',
|
||||
projectId: 'project-1',
|
||||
conversationId: 'conversation-1',
|
||||
filePath: 'preview.html',
|
||||
elementId: 'pin-2',
|
||||
selector: '[data-od-pin="pin-2"]',
|
||||
label: 'pin-2',
|
||||
text: '',
|
||||
htmlHint: '',
|
||||
position: { x: 48, y: 20, width: 18, height: 18 },
|
||||
note: 'Second',
|
||||
status: 'open',
|
||||
createdAt: 20,
|
||||
updatedAt: 20,
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<FileViewer
|
||||
projectId="project-1"
|
||||
projectKind="prototype"
|
||||
file={htmlPreviewFile()}
|
||||
liveHtml='<html><body><main data-od-id="hero">Hero</main></body></html>'
|
||||
previewComments={comments}
|
||||
onRemovePreviewComment={async (commentId) => {
|
||||
removed.push(commentId);
|
||||
setComments((current) => current.filter((comment) => comment.id !== commentId));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render(<Harness />);
|
||||
fireEvent.click(screen.getByTestId('comment-panel-toggle'));
|
||||
const selectButtons = screen.getAllByRole('button', { name: /select/i });
|
||||
const firstSelectButton = selectButtons[0];
|
||||
expect(firstSelectButton).toBeTruthy();
|
||||
if (!firstSelectButton) return;
|
||||
fireEvent.click(firstSelectButton);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Clear' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Second')).toBeNull();
|
||||
});
|
||||
expect(removed).toEqual(['comment-2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyInspectOverridesToSource', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue