kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'fix-tl-jump' into 'main'
Fix home timeline jump when navigating back See merge request soapbox-pub/soapbox!2772environments/review-main-yi2y9f/deployments/4080
commit
702124fb79
|
@ -1,10 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useIntl, MessageDescriptor } from 'react-intl';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { Text } from 'soapbox/components/ui';
|
||||
import { Icon, Text } from 'soapbox/components/ui';
|
||||
import { useSettings } from 'soapbox/hooks';
|
||||
|
||||
interface IScrollTopButton {
|
||||
|
@ -31,67 +29,77 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
|
|||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
|
||||
// Whether we are scrolled past the `threshold`.
|
||||
const [scrolled, setScrolled] = useState<boolean>(false);
|
||||
const autoload = settings.get('autoloadTimelines') === true;
|
||||
// Whether we are scrolled above the `autoloadThreshold`.
|
||||
const [scrolledTop, setScrolledTop] = useState<boolean>(false);
|
||||
|
||||
const autoload = settings.get('autoloadTimelines') === true;
|
||||
const visible = count > 0 && scrolled;
|
||||
|
||||
const classes = clsx('fixed left-1/2 top-20 z-50 -translate-x-1/2', {
|
||||
'hidden': !visible,
|
||||
});
|
||||
|
||||
/** Number of pixels scrolled down from the top of the page. */
|
||||
const getScrollTop = (): number => {
|
||||
return (document.scrollingElement || document.documentElement).scrollTop;
|
||||
};
|
||||
|
||||
const maybeUnload = () => {
|
||||
if (autoload && getScrollTop() <= autoloadThreshold) {
|
||||
/** Unload feed items if scrolled to the top. */
|
||||
const maybeUnload = useCallback(() => {
|
||||
if (autoload && scrolledTop) {
|
||||
onClick();
|
||||
}
|
||||
};
|
||||
}, [autoload, scrolledTop, onClick]);
|
||||
|
||||
/** Set state while scrolling. */
|
||||
const handleScroll = useCallback(throttle(() => {
|
||||
maybeUnload();
|
||||
const scrollTop = getScrollTop();
|
||||
|
||||
if (getScrollTop() > threshold) {
|
||||
setScrolled(true);
|
||||
} else {
|
||||
setScrolled(false);
|
||||
}
|
||||
}, 150, { trailing: true }), [autoload, threshold, autoloadThreshold, onClick]);
|
||||
setScrolled(scrollTop > threshold);
|
||||
setScrolledTop(scrollTop <= autoloadThreshold);
|
||||
|
||||
const scrollUp = () => {
|
||||
}, 150, { trailing: true }), [threshold, autoloadThreshold]);
|
||||
|
||||
/** Scroll to top and trigger `onClick`. */
|
||||
const handleClick: React.MouseEventHandler = useCallback(() => {
|
||||
window.scrollTo({ top: 0 });
|
||||
};
|
||||
|
||||
const handleClick: React.MouseEventHandler = () => {
|
||||
setTimeout(scrollUp, 10);
|
||||
onClick();
|
||||
};
|
||||
}, [onClick]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
// Delay adding the scroll listener so navigating back doesn't
|
||||
// unload feed items before the feed is rendered.
|
||||
setTimeout(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
handleScroll();
|
||||
}, 250);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [onClick]);
|
||||
}, [handleScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
maybeUnload();
|
||||
}, [count]);
|
||||
}, [maybeUnload]);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<a className='flex cursor-pointer items-center space-x-1.5 whitespace-nowrap rounded-full bg-primary-600 px-4 py-2 text-white transition-transform hover:scale-105 hover:bg-primary-700 active:scale-100' onClick={handleClick}>
|
||||
<Icon src={require('@tabler/icons/arrow-bar-to-up.svg')} />
|
||||
<div className='fixed left-1/2 top-20 z-50 -translate-x-1/2'>
|
||||
<button
|
||||
className='flex cursor-pointer items-center space-x-1.5 whitespace-nowrap rounded-full bg-primary-600 px-4 py-2 text-white transition-transform hover:scale-105 hover:bg-primary-700 active:scale-100'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Icon
|
||||
className='h-4 w-4'
|
||||
src={require('@tabler/icons/arrow-bar-to-up.svg')}
|
||||
/>
|
||||
|
||||
{(count > 0) && (
|
||||
<Text theme='inherit' size='sm'>
|
||||
{intl.formatMessage(message, { count })}
|
||||
</Text>
|
||||
)}
|
||||
</a>
|
||||
<Text theme='inherit' size='sm'>
|
||||
{intl.formatMessage(message, { count })}
|
||||
</Text>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ import { getSettings } from 'soapbox/actions/settings';
|
|||
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
||||
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Column } from 'soapbox/components/ui';
|
||||
import { Column, Portal } from 'soapbox/components/ui';
|
||||
import PlaceholderNotification from 'soapbox/features/placeholder/components/placeholder-notification';
|
||||
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||
|
||||
|
@ -104,13 +104,13 @@ const Notifications = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const handleDequeueNotifications = () => {
|
||||
const handleDequeueNotifications = useCallback(() => {
|
||||
dispatch(dequeueNotifications());
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleRefresh = () => {
|
||||
const handleRefresh = useCallback(() => {
|
||||
return dispatch(expandNotifications());
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
handleDequeueNotifications();
|
||||
|
@ -176,11 +176,15 @@ const Notifications = () => {
|
|||
return (
|
||||
<Column ref={column} label={intl.formatMessage(messages.title)} withHeader={false}>
|
||||
{filterBarContainer}
|
||||
<ScrollTopButton
|
||||
onClick={handleDequeueNotifications}
|
||||
count={totalQueuedNotificationsCount}
|
||||
message={messages.queue}
|
||||
/>
|
||||
|
||||
<Portal>
|
||||
<ScrollTopButton
|
||||
onClick={handleDequeueNotifications}
|
||||
count={totalQueuedNotificationsCount}
|
||||
message={messages.queue}
|
||||
/>
|
||||
</Portal>
|
||||
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
{scrollContainer}
|
||||
</PullToRefresh>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { defineMessages } from 'react-intl';
|
|||
import { dequeueTimeline, scrollTopTimeline } from 'soapbox/actions/timelines';
|
||||
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
||||
import StatusList, { IStatusList } from 'soapbox/components/status-list';
|
||||
import { Portal } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||
import { makeGetStatusIds } from 'soapbox/selectors';
|
||||
|
||||
|
@ -37,9 +38,9 @@ const Timeline: React.FC<ITimeline> = ({
|
|||
const hasMore = useAppSelector(state => state.timelines.get(timelineId)?.hasMore === true);
|
||||
const totalQueuedItemsCount = useAppSelector(state => state.timelines.get(timelineId)?.totalQueuedItemsCount || 0);
|
||||
|
||||
const handleDequeueTimeline = () => {
|
||||
const handleDequeueTimeline = useCallback(() => {
|
||||
dispatch(dequeueTimeline(timelineId, onLoadMore));
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleScrollToTop = useCallback(debounce(() => {
|
||||
dispatch(scrollTopTimeline(timelineId, true));
|
||||
|
@ -51,12 +52,14 @@ const Timeline: React.FC<ITimeline> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<ScrollTopButton
|
||||
key='timeline-queue-button-header'
|
||||
onClick={handleDequeueTimeline}
|
||||
count={totalQueuedItemsCount}
|
||||
message={messages.queue}
|
||||
/>
|
||||
<Portal>
|
||||
<ScrollTopButton
|
||||
key='timeline-queue-button-header'
|
||||
onClick={handleDequeueTimeline}
|
||||
count={totalQueuedItemsCount}
|
||||
message={messages.queue}
|
||||
/>
|
||||
</Portal>
|
||||
|
||||
<StatusList
|
||||
timelineId={timelineId}
|
||||
|
|
Ładowanie…
Reference in New Issue