From f6ce2ba018f842c231067902cf7a0a086b7d40dd Mon Sep 17 00:00:00 2001 From: Tommaso Casaburi Date: Thu, 16 Apr 2026 15:20:49 +0700 Subject: [PATCH] fix(reply-quote-preview): keep floating OP previews at desktop width --- .../external-number-quote-link.test.tsx | 38 +++++++++++++++++++ .../markdown/external-number-quote-link.tsx | 6 ++- .../__tests__/reply-quote-preview.test.tsx | 30 +++++++++++++++ .../reply-quote-preview.tsx | 11 ++++-- src/views/post/post.module.css | 6 +++ 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/components/markdown/__tests__/external-number-quote-link.test.tsx b/src/components/markdown/__tests__/external-number-quote-link.test.tsx index c1dcdd40..8b7133b8 100644 --- a/src/components/markdown/__tests__/external-number-quote-link.test.tsx +++ b/src/components/markdown/__tests__/external-number-quote-link.test.tsx @@ -4,6 +4,7 @@ import { createRoot, type Root } from 'react-dom/client'; import { MemoryRouter } from 'react-router-dom'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import ExternalNumberQuoteLink from '../external-number-quote-link'; +import postStyles from '../../../views/post/post.module.css'; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const act = (React as { act?: (cb: () => void | Promise) => void | Promise }).act as (cb: () => void | Promise) => void | Promise; @@ -146,4 +147,41 @@ describe('ExternalNumberQuoteLink', () => { expect(document.body.querySelector('[data-testid="post-preview"]')?.textContent).toBe('cid-77'); }); + + it('uses the OP preview width class for resolved external OP previews', async () => { + testState.resolveExternalQuoteTargetMock.mockResolvedValue({ + boardPath: 'fit', + cid: 'cid-77', + comment: { cid: 'cid-77' }, + isUnavailable: false, + route: '/fit/thread/cid-77', + communityAddress: 'fit', + }); + + await act(async () => { + root.render( + createElement( + MemoryRouter, + {}, + createElement(ExternalNumberQuoteLink, { + reference: { + boardIdentifier: 'fit', + kind: 'cross-board', + number: 77, + raw: '>>>/fit/77', + }, + }), + ), + ); + }); + + await act(async () => { + container.querySelector('a')?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); + await Promise.resolve(); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + + const preview = document.body.querySelector(`.${postStyles.replyQuotePreview}`); + expect(preview?.classList.contains(postStyles.replyQuotePreviewOp)).toBe(true); + }); }); diff --git a/src/components/markdown/external-number-quote-link.tsx b/src/components/markdown/external-number-quote-link.tsx index fa95c2fc..f0716274 100644 --- a/src/components/markdown/external-number-quote-link.tsx +++ b/src/components/markdown/external-number-quote-link.tsx @@ -260,6 +260,10 @@ const ExternalNumberQuoteLink = ({ reference }: ExternalNumberQuoteLinkProps) => ) : (
{previewState.message}
); + const previewClassName = + previewState.kind === 'resolved' && previewState.target.comment && !previewState.target.comment.parentCid + ? `${postStyles.replyQuotePreview} ${postStyles.replyQuotePreviewOp}` + : postStyles.replyQuotePreview; return ( <> @@ -280,7 +284,7 @@ const ExternalNumberQuoteLink = ({ reference }: ExternalNumberQuoteLinkProps) => previewContent && createPortal(
{ outOfView.remove(); }); + it('uses the OP preview width class for floating desktop OP quotelinks', async () => { + const outOfView = appendReplyElement({ cid: 'thread-cid', inViewport: false, isThreadCard: true }); + + await renderPreview({ + isOP: true, + isQuotelinkReply: true, + quotelinkReply: { + cid: 'thread-cid', + number: 1, + communityAddress: 'music-posting.eth', + }, + }); + + const link = queryAnchorByText('>>1 (OP)'); + expect(link).toBeTruthy(); + + await act(async () => { + link?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); + }); + + const preview = document.querySelector(`.${styles.replyQuotePreview}`); + expect(preview?.classList.contains(styles.replyQuotePreviewOp)).toBe(true); + + await act(async () => { + link?.dispatchEvent(new MouseEvent('mouseout', { bubbles: true })); + }); + + outOfView.remove(); + }); + it('marks quotelinks as your own via the direct account comment lookup before author fallback', async () => { testState.account = { id: 'account-1', diff --git a/src/components/reply-quote-preview/reply-quote-preview.tsx b/src/components/reply-quote-preview/reply-quote-preview.tsx index 70ac7883..416e0a63 100644 --- a/src/components/reply-quote-preview/reply-quote-preview.tsx +++ b/src/components/reply-quote-preview/reply-quote-preview.tsx @@ -24,6 +24,9 @@ interface ReplyQuotePreviewProps { showTrailingBreak?: boolean; } +const getQuotePreviewClassName = (previewPost?: Comment) => + !previewPost?.parentCid ? `${styles.replyQuotePreview} ${styles.replyQuotePreviewOp}` : styles.replyQuotePreview; + const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => { const targetElements = document.querySelectorAll(`[data-cid="${cid}"]`); const isOpElement = (element: HTMLElement) => element.getAttribute('data-post-cid') === cid; @@ -209,7 +212,7 @@ const DesktopQuotePreview = ({ {hoveredCid === normalizedBacklinkReply?.cid && outOfViewCid === normalizedBacklinkReply?.cid && createPortal( -
+
, document.body, @@ -265,7 +268,7 @@ const DesktopQuotePreview = ({ {showTrailingBreak &&
} {shouldShowQuotelinkPreview && createPortal( -
+
, document.body, @@ -373,7 +376,7 @@ const MobileQuotePreview = ({ {hoveredCid === normalizedBacklinkReply?.cid && outOfViewCid === normalizedBacklinkReply?.cid && createPortal( -
+
, document.body, @@ -427,7 +430,7 @@ const MobileQuotePreview = ({ {showTrailingBreak &&
} {shouldShowQuotelinkPreview && createPortal( -
+
, document.body, diff --git a/src/views/post/post.module.css b/src/views/post/post.module.css index d015c69e..5d5e2879 100644 --- a/src/views/post/post.module.css +++ b/src/views/post/post.module.css @@ -672,6 +672,8 @@ border: var(--quote-preview-border); border-right: var(--quote-preview-border-right); border-bottom: var(--quote-preview-border-bottom); + box-sizing: border-box; + max-width: calc(100vw - 20px); } .postDesktop .replyQuotePreviewSpacer { @@ -724,6 +726,10 @@ padding-left: 2px; } + .replyQuotePreviewOp { + width: min(520px, calc(100vw - 20px)); + } + .postMobile { display: none; }