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 throttle from 'lodash/throttle';
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useIntl, MessageDescriptor } from 'react-intl';
|
import { useIntl, MessageDescriptor } from 'react-intl';
|
||||||
|
|
||||||
import Icon from 'soapbox/components/icon';
|
import { Icon, Text } from 'soapbox/components/ui';
|
||||||
import { Text } from 'soapbox/components/ui';
|
|
||||||
import { useSettings } from 'soapbox/hooks';
|
import { useSettings } from 'soapbox/hooks';
|
||||||
|
|
||||||
interface IScrollTopButton {
|
interface IScrollTopButton {
|
||||||
|
@ -31,67 +29,77 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
|
// Whether we are scrolled past the `threshold`.
|
||||||
const [scrolled, setScrolled] = useState<boolean>(false);
|
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 visible = count > 0 && scrolled;
|
||||||
|
|
||||||
const classes = clsx('fixed left-1/2 top-20 z-50 -translate-x-1/2', {
|
/** Number of pixels scrolled down from the top of the page. */
|
||||||
'hidden': !visible,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getScrollTop = (): number => {
|
const getScrollTop = (): number => {
|
||||||
return (document.scrollingElement || document.documentElement).scrollTop;
|
return (document.scrollingElement || document.documentElement).scrollTop;
|
||||||
};
|
};
|
||||||
|
|
||||||
const maybeUnload = () => {
|
/** Unload feed items if scrolled to the top. */
|
||||||
if (autoload && getScrollTop() <= autoloadThreshold) {
|
const maybeUnload = useCallback(() => {
|
||||||
|
if (autoload && scrolledTop) {
|
||||||
onClick();
|
onClick();
|
||||||
}
|
}
|
||||||
};
|
}, [autoload, scrolledTop, onClick]);
|
||||||
|
|
||||||
|
/** Set state while scrolling. */
|
||||||
const handleScroll = useCallback(throttle(() => {
|
const handleScroll = useCallback(throttle(() => {
|
||||||
maybeUnload();
|
const scrollTop = getScrollTop();
|
||||||
|
|
||||||
if (getScrollTop() > threshold) {
|
setScrolled(scrollTop > threshold);
|
||||||
setScrolled(true);
|
setScrolledTop(scrollTop <= autoloadThreshold);
|
||||||
} else {
|
|
||||||
setScrolled(false);
|
|
||||||
}
|
|
||||||
}, 150, { trailing: true }), [autoload, threshold, autoloadThreshold, onClick]);
|
|
||||||
|
|
||||||
const scrollUp = () => {
|
}, 150, { trailing: true }), [threshold, autoloadThreshold]);
|
||||||
|
|
||||||
|
/** Scroll to top and trigger `onClick`. */
|
||||||
|
const handleClick: React.MouseEventHandler = useCallback(() => {
|
||||||
window.scrollTo({ top: 0 });
|
window.scrollTo({ top: 0 });
|
||||||
};
|
|
||||||
|
|
||||||
const handleClick: React.MouseEventHandler = () => {
|
|
||||||
setTimeout(scrollUp, 10);
|
|
||||||
onClick();
|
onClick();
|
||||||
};
|
}, [onClick]);
|
||||||
|
|
||||||
useEffect(() => {
|
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 () => {
|
return () => {
|
||||||
window.removeEventListener('scroll', handleScroll);
|
window.removeEventListener('scroll', handleScroll);
|
||||||
};
|
};
|
||||||
}, [onClick]);
|
}, [handleScroll]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
maybeUnload();
|
maybeUnload();
|
||||||
}, [count]);
|
}, [maybeUnload]);
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className='fixed left-1/2 top-20 z-50 -translate-x-1/2'>
|
||||||
<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}>
|
<button
|
||||||
<Icon src={require('@tabler/icons/arrow-bar-to-up.svg')} />
|
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'>
|
||||||
<Text theme='inherit' size='sm'>
|
{intl.formatMessage(message, { count })}
|
||||||
{intl.formatMessage(message, { count })}
|
</Text>
|
||||||
</Text>
|
</button>
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { getSettings } from 'soapbox/actions/settings';
|
||||||
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
||||||
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
||||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
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 PlaceholderNotification from 'soapbox/features/placeholder/components/placeholder-notification';
|
||||||
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -104,13 +104,13 @@ const Notifications = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDequeueNotifications = () => {
|
const handleDequeueNotifications = useCallback(() => {
|
||||||
dispatch(dequeueNotifications());
|
dispatch(dequeueNotifications());
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = useCallback(() => {
|
||||||
return dispatch(expandNotifications());
|
return dispatch(expandNotifications());
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleDequeueNotifications();
|
handleDequeueNotifications();
|
||||||
|
@ -176,11 +176,15 @@ const Notifications = () => {
|
||||||
return (
|
return (
|
||||||
<Column ref={column} label={intl.formatMessage(messages.title)} withHeader={false}>
|
<Column ref={column} label={intl.formatMessage(messages.title)} withHeader={false}>
|
||||||
{filterBarContainer}
|
{filterBarContainer}
|
||||||
<ScrollTopButton
|
|
||||||
onClick={handleDequeueNotifications}
|
<Portal>
|
||||||
count={totalQueuedNotificationsCount}
|
<ScrollTopButton
|
||||||
message={messages.queue}
|
onClick={handleDequeueNotifications}
|
||||||
/>
|
count={totalQueuedNotificationsCount}
|
||||||
|
message={messages.queue}
|
||||||
|
/>
|
||||||
|
</Portal>
|
||||||
|
|
||||||
<PullToRefresh onRefresh={handleRefresh}>
|
<PullToRefresh onRefresh={handleRefresh}>
|
||||||
{scrollContainer}
|
{scrollContainer}
|
||||||
</PullToRefresh>
|
</PullToRefresh>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { defineMessages } from 'react-intl';
|
||||||
import { dequeueTimeline, scrollTopTimeline } from 'soapbox/actions/timelines';
|
import { dequeueTimeline, scrollTopTimeline } from 'soapbox/actions/timelines';
|
||||||
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
||||||
import StatusList, { IStatusList } from 'soapbox/components/status-list';
|
import StatusList, { IStatusList } from 'soapbox/components/status-list';
|
||||||
|
import { Portal } from 'soapbox/components/ui';
|
||||||
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||||
import { makeGetStatusIds } from 'soapbox/selectors';
|
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 hasMore = useAppSelector(state => state.timelines.get(timelineId)?.hasMore === true);
|
||||||
const totalQueuedItemsCount = useAppSelector(state => state.timelines.get(timelineId)?.totalQueuedItemsCount || 0);
|
const totalQueuedItemsCount = useAppSelector(state => state.timelines.get(timelineId)?.totalQueuedItemsCount || 0);
|
||||||
|
|
||||||
const handleDequeueTimeline = () => {
|
const handleDequeueTimeline = useCallback(() => {
|
||||||
dispatch(dequeueTimeline(timelineId, onLoadMore));
|
dispatch(dequeueTimeline(timelineId, onLoadMore));
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleScrollToTop = useCallback(debounce(() => {
|
const handleScrollToTop = useCallback(debounce(() => {
|
||||||
dispatch(scrollTopTimeline(timelineId, true));
|
dispatch(scrollTopTimeline(timelineId, true));
|
||||||
|
@ -51,12 +52,14 @@ const Timeline: React.FC<ITimeline> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrollTopButton
|
<Portal>
|
||||||
key='timeline-queue-button-header'
|
<ScrollTopButton
|
||||||
onClick={handleDequeueTimeline}
|
key='timeline-queue-button-header'
|
||||||
count={totalQueuedItemsCount}
|
onClick={handleDequeueTimeline}
|
||||||
message={messages.queue}
|
count={totalQueuedItemsCount}
|
||||||
/>
|
message={messages.queue}
|
||||||
|
/>
|
||||||
|
</Portal>
|
||||||
|
|
||||||
<StatusList
|
<StatusList
|
||||||
timelineId={timelineId}
|
timelineId={timelineId}
|
||||||
|
|
Ładowanie…
Reference in New Issue