mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(post): avoid duplicate history entries on OP permalinks
This commit is contained in:
@@ -48,7 +48,7 @@ import useQuotedByMap from '../../hooks/use-quoted-by-map';
|
|||||||
import useProgressiveRender from '../../hooks/use-progressive-render';
|
import useProgressiveRender from '../../hooks/use-progressive-render';
|
||||||
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
||||||
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount } from '../../lib/utils/replies-preview-utils';
|
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount } from '../../lib/utils/replies-preview-utils';
|
||||||
import { scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
import { openThreadAtTop } from '../../lib/utils/thread-scroll-utils';
|
||||||
|
|
||||||
const { addChallenge } = useChallengesStore.getState();
|
const { addChallenge } = useChallengesStore.getState();
|
||||||
|
|
||||||
@@ -277,10 +277,11 @@ const PostInfo = ({
|
|||||||
|
|
||||||
if (isInPostPageView && !isReply) {
|
if (isInPostPageView && !isReply) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(threadRoute);
|
openThreadAtTop({
|
||||||
scrollThreadContainerToTop(cid);
|
cid,
|
||||||
window.requestAnimationFrame(() => {
|
currentPathname: location.pathname,
|
||||||
scrollThreadContainerToTop(cid);
|
navigate,
|
||||||
|
threadRoute,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import useQuotedByMap from '../../hooks/use-quoted-by-map';
|
|||||||
import useProgressiveRender from '../../hooks/use-progressive-render';
|
import useProgressiveRender from '../../hooks/use-progressive-render';
|
||||||
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
import { BOARD_REPLIES_PREVIEW_FETCH_SIZE, BOARD_REPLIES_PREVIEW_VISIBLE_COUNT, REPLIES_PER_PAGE } from '../../lib/constants';
|
||||||
import { filterRepliesForDisplay, getPreviewDisplayReplies } from '../../lib/utils/replies-preview-utils';
|
import { filterRepliesForDisplay, getPreviewDisplayReplies } from '../../lib/utils/replies-preview-utils';
|
||||||
import { scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
|
import { openThreadAtTop } from '../../lib/utils/thread-scroll-utils';
|
||||||
|
|
||||||
const { addChallenge } = useChallengesStore.getState();
|
const { addChallenge } = useChallengesStore.getState();
|
||||||
|
|
||||||
@@ -256,10 +256,11 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
|
|||||||
|
|
||||||
if (isInPostPageView && !isReply) {
|
if (isInPostPageView && !isReply) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(threadRoute);
|
openThreadAtTop({
|
||||||
scrollThreadContainerToTop(cid);
|
cid,
|
||||||
window.requestAnimationFrame(() => {
|
currentPathname: location.pathname,
|
||||||
scrollThreadContainerToTop(cid);
|
navigate,
|
||||||
|
threadRoute,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { clearThreadScrollSpacer, openThreadAtTop } from '../thread-scroll-utils';
|
||||||
|
|
||||||
|
const appendThreadContainer = (cid: string, top = 240) => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
element.dataset.threadContainerCid = cid;
|
||||||
|
element.getBoundingClientRect = () =>
|
||||||
|
({
|
||||||
|
bottom: top + 100,
|
||||||
|
height: 100,
|
||||||
|
left: 0,
|
||||||
|
right: 100,
|
||||||
|
top,
|
||||||
|
width: 100,
|
||||||
|
}) as DOMRect;
|
||||||
|
document.body.appendChild(element);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('thread-scroll-utils', () => {
|
||||||
|
let scrollToMock: ReturnType<typeof vi.fn>;
|
||||||
|
let requestAnimationFrameMock: ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
scrollToMock = vi.fn();
|
||||||
|
requestAnimationFrameMock = vi.fn((callback: FrameRequestCallback) => {
|
||||||
|
callback(0);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'scrollTo', {
|
||||||
|
configurable: true,
|
||||||
|
value: scrollToMock,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, 'requestAnimationFrame', {
|
||||||
|
configurable: true,
|
||||||
|
value: requestAnimationFrameMock,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, 'scrollY', {
|
||||||
|
configurable: true,
|
||||||
|
value: 0,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, 'innerHeight', {
|
||||||
|
configurable: true,
|
||||||
|
value: 100,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(document.documentElement, 'scrollHeight', {
|
||||||
|
configurable: true,
|
||||||
|
value: 200,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clearThreadScrollSpacer();
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scrolls the current thread without pushing duplicate history entries', () => {
|
||||||
|
appendThreadContainer('thread-cid');
|
||||||
|
const navigateMock = vi.fn();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
openThreadAtTop({
|
||||||
|
cid: 'thread-cid',
|
||||||
|
currentPathname: '/mu/thread/thread-cid',
|
||||||
|
navigate: navigateMock,
|
||||||
|
threadRoute: '/mu/thread/thread-cid',
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(navigateMock).not.toHaveBeenCalled();
|
||||||
|
expect(requestAnimationFrameMock).toHaveBeenCalledOnce();
|
||||||
|
expect(scrollToMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(scrollToMock).toHaveBeenNthCalledWith(1, {
|
||||||
|
behavior: 'auto',
|
||||||
|
left: 0,
|
||||||
|
top: 240,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('navigates to the OP thread before scrolling when the route differs', () => {
|
||||||
|
appendThreadContainer('thread-cid');
|
||||||
|
const navigateMock = vi.fn();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
openThreadAtTop({
|
||||||
|
cid: 'thread-cid',
|
||||||
|
currentPathname: '/mu/thread/reply-cid',
|
||||||
|
navigate: navigateMock,
|
||||||
|
threadRoute: '/mu/thread/thread-cid',
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(navigateMock).toHaveBeenCalledWith('/mu/thread/thread-cid');
|
||||||
|
expect(requestAnimationFrameMock).toHaveBeenCalledOnce();
|
||||||
|
expect(scrollToMock).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when the permalink cannot resolve a thread target', () => {
|
||||||
|
const navigateMock = vi.fn();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
openThreadAtTop({
|
||||||
|
cid: undefined,
|
||||||
|
currentPathname: '/mu/thread/thread-cid',
|
||||||
|
navigate: navigateMock,
|
||||||
|
threadRoute: '/mu/thread/thread-cid',
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
expect(navigateMock).not.toHaveBeenCalled();
|
||||||
|
expect(requestAnimationFrameMock).not.toHaveBeenCalled();
|
||||||
|
expect(scrollToMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -46,3 +46,28 @@ export const scrollThreadContainerToTop = (cid?: string) => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const openThreadAtTop = ({
|
||||||
|
cid,
|
||||||
|
currentPathname,
|
||||||
|
navigate,
|
||||||
|
threadRoute,
|
||||||
|
}: {
|
||||||
|
cid?: string;
|
||||||
|
currentPathname?: string;
|
||||||
|
navigate: (route: string) => void;
|
||||||
|
threadRoute?: string;
|
||||||
|
}) => {
|
||||||
|
if (!cid || !threadRoute) return false;
|
||||||
|
|
||||||
|
if (currentPathname !== threadRoute) {
|
||||||
|
navigate(threadRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollThreadContainerToTop(cid);
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
scrollThreadContainerToTop(cid);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user