fix(reply-quote-preview): keep floating OP previews at desktop width

This commit is contained in:
Tommaso Casaburi
2026-04-16 15:20:49 +07:00
parent b288c62c06
commit f6ce2ba018
5 changed files with 86 additions and 5 deletions
@@ -4,6 +4,7 @@ import { createRoot, type Root } from 'react-dom/client';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import ExternalNumberQuoteLink from '../external-number-quote-link'; 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; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>; const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
@@ -146,4 +147,41 @@ describe('ExternalNumberQuoteLink', () => {
expect(document.body.querySelector('[data-testid="post-preview"]')?.textContent).toBe('cid-77'); 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);
});
}); });
@@ -260,6 +260,10 @@ const ExternalNumberQuoteLink = ({ reference }: ExternalNumberQuoteLinkProps) =>
) : ( ) : (
<div className={`${styles.externalQuotePreviewState} ${styles.externalQuotePreviewError}`}>{previewState.message}</div> <div className={`${styles.externalQuotePreviewState} ${styles.externalQuotePreviewError}`}>{previewState.message}</div>
); );
const previewClassName =
previewState.kind === 'resolved' && previewState.target.comment && !previewState.target.comment.parentCid
? `${postStyles.replyQuotePreview} ${postStyles.replyQuotePreviewOp}`
: postStyles.replyQuotePreview;
return ( return (
<> <>
@@ -280,7 +284,7 @@ const ExternalNumberQuoteLink = ({ reference }: ExternalNumberQuoteLinkProps) =>
previewContent && previewContent &&
createPortal( createPortal(
<div <div
className={postStyles.replyQuotePreview} className={previewClassName}
data-thread-scroll-preview='true' data-thread-scroll-preview='true'
ref={previewRef} ref={previewRef}
style={{ left: previewPosition.left, position: 'fixed', top: previewPosition.top, zIndex: 1000 }} style={{ left: previewPosition.left, position: 'fixed', top: previewPosition.top, zIndex: 1000 }}
@@ -401,6 +401,36 @@ describe('ReplyQuotePreview', () => {
outOfView.remove(); 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 () => { it('marks quotelinks as your own via the direct account comment lookup before author fallback', async () => {
testState.account = { testState.account = {
id: 'account-1', id: 'account-1',
@@ -24,6 +24,9 @@ interface ReplyQuotePreviewProps {
showTrailingBreak?: boolean; showTrailingBreak?: boolean;
} }
const getQuotePreviewClassName = (previewPost?: Comment) =>
!previewPost?.parentCid ? `${styles.replyQuotePreview} ${styles.replyQuotePreviewOp}` : styles.replyQuotePreview;
const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => { const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
const targetElements = document.querySelectorAll(`[data-cid="${cid}"]`); const targetElements = document.querySelectorAll(`[data-cid="${cid}"]`);
const isOpElement = (element: HTMLElement) => element.getAttribute('data-post-cid') === cid; const isOpElement = (element: HTMLElement) => element.getAttribute('data-post-cid') === cid;
@@ -209,7 +212,7 @@ const DesktopQuotePreview = ({
{hoveredCid === normalizedBacklinkReply?.cid && {hoveredCid === normalizedBacklinkReply?.cid &&
outOfViewCid === normalizedBacklinkReply?.cid && outOfViewCid === normalizedBacklinkReply?.cid &&
createPortal( createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}> <div className={getQuotePreviewClassName(normalizedBacklinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedBacklinkReply} showReplies={false} /> <Post post={normalizedBacklinkReply} showReplies={false} />
</div>, </div>,
document.body, document.body,
@@ -265,7 +268,7 @@ const DesktopQuotePreview = ({
{showTrailingBreak && <br />} {showTrailingBreak && <br />}
{shouldShowQuotelinkPreview && {shouldShowQuotelinkPreview &&
createPortal( createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}> <div className={getQuotePreviewClassName(normalizedQuotelinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedQuotelinkReply} showReplies={false} /> <Post post={normalizedQuotelinkReply} showReplies={false} />
</div>, </div>,
document.body, document.body,
@@ -373,7 +376,7 @@ const MobileQuotePreview = ({
{hoveredCid === normalizedBacklinkReply?.cid && {hoveredCid === normalizedBacklinkReply?.cid &&
outOfViewCid === normalizedBacklinkReply?.cid && outOfViewCid === normalizedBacklinkReply?.cid &&
createPortal( createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}> <div className={getQuotePreviewClassName(normalizedBacklinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedBacklinkReply} showReplies={false} /> <Post post={normalizedBacklinkReply} showReplies={false} />
</div>, </div>,
document.body, document.body,
@@ -427,7 +430,7 @@ const MobileQuotePreview = ({
{showTrailingBreak && <br />} {showTrailingBreak && <br />}
{shouldShowQuotelinkPreview && {shouldShowQuotelinkPreview &&
createPortal( createPortal(
<div className={styles.replyQuotePreview} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}> <div className={getQuotePreviewClassName(normalizedQuotelinkReply)} data-thread-scroll-preview='true' ref={refs.setFloating} style={floatingStyles}>
<Post post={normalizedQuotelinkReply} showReplies={false} /> <Post post={normalizedQuotelinkReply} showReplies={false} />
</div>, </div>,
document.body, document.body,
+6
View File
@@ -672,6 +672,8 @@
border: var(--quote-preview-border); border: var(--quote-preview-border);
border-right: var(--quote-preview-border-right); border-right: var(--quote-preview-border-right);
border-bottom: var(--quote-preview-border-bottom); border-bottom: var(--quote-preview-border-bottom);
box-sizing: border-box;
max-width: calc(100vw - 20px);
} }
.postDesktop .replyQuotePreviewSpacer { .postDesktop .replyQuotePreviewSpacer {
@@ -724,6 +726,10 @@
padding-left: 2px; padding-left: 2px;
} }
.replyQuotePreviewOp {
width: min(520px, calc(100vw - 20px));
}
.postMobile { .postMobile {
display: none; display: none;
} }