diff --git a/packages/frontend/core/src/components/notification/list.style.css.ts b/packages/frontend/core/src/components/notification/list.style.css.ts index ee442003b..01b6f600b 100644 --- a/packages/frontend/core/src/components/notification/list.style.css.ts +++ b/packages/frontend/core/src/components/notification/list.style.css.ts @@ -138,3 +138,11 @@ export const itemNameLabelIcon = style({ marginRight: '4px', color: cssVarV2('icon/primary'), }); + +export const loadMoreIndicator = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '24px', + color: cssVarV2('text/secondary'), +}); diff --git a/packages/frontend/core/src/components/notification/list.tsx b/packages/frontend/core/src/components/notification/list.tsx index 074703567..aadaf017e 100644 --- a/packages/frontend/core/src/components/notification/list.tsx +++ b/packages/frontend/core/src/components/notification/list.tsx @@ -3,6 +3,7 @@ import { Button, IconButton, notify, + observeIntersection, Scrollable, Skeleton, } from '@affine/component'; @@ -35,47 +36,53 @@ import { } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useNavigateHelper } from '../hooks/use-navigate-helper'; import * as styles from './list.style.css'; export const NotificationList = () => { + const t = useI18n(); const notificationListService = useService(NotificationListService); const notifications = useLiveData(notificationListService.notifications$); const isLoading = useLiveData(notificationListService.isLoading$); const error = useLiveData(notificationListService.error$); + const hasMore = useLiveData(notificationListService.hasMore$); + const loadMoreIndicatorRef = useRef(null); const userFriendlyError = useMemo(() => { return error && UserFriendlyError.fromAny(error); }, [error]); - useEffect(() => { + useLayoutEffect(() => { // reset the notification list when the component is mounted notificationListService.reset(); notificationListService.loadMore(); }, [notificationListService]); - const handleScrollEnd = useCallback(() => { - notificationListService.loadMore(); - }, [notificationListService]); - - const handleScroll = useCallback( - (e: React.UIEvent) => { - const target = e.currentTarget; - if (target.scrollHeight - target.scrollTop <= target.clientHeight + 1) { - handleScrollEnd(); - } - }, - [handleScrollEnd] - ); + useEffect(() => { + if (loadMoreIndicatorRef.current) { + let previousIsIntersecting = false; + return observeIntersection(loadMoreIndicatorRef.current, entity => { + if (entity.isIntersecting && !previousIsIntersecting && hasMore) { + notificationListService.loadMore(); + } + previousIsIntersecting = entity.isIntersecting; + }); + } + return; + }, [hasMore, notificationListService]); return ( - + {notifications.length > 0 ? (
    {notifications.map(notification => ( @@ -94,6 +101,13 @@ export const NotificationList = () => { ) : ( )} + +
    + {hasMore ? t['com.affine.notification.loading-more']() : null} +
    diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index 0d757dff3..c11d1bd15 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -7229,6 +7229,10 @@ export function useAFFiNEI18N(): { * `No new notifications` */ ["com.affine.notification.empty"](): string; + /** + * `Loading more...` + */ + ["com.affine.notification.loading-more"](): string; /** * `You'll be notified here for @mentions and workspace invites.` */ diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 0c51356f7..47e5d303b 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1797,6 +1797,7 @@ "com.affine.notification.unsupported": "Unsupported message", "com.affine.notification.mention": "<1>{{username}} mentioned you in <2>{{docTitle}}", "com.affine.notification.empty": "No new notifications", + "com.affine.notification.loading-more": "Loading more...", "com.affine.notification.empty.description": "You'll be notified here for @mentions and workspace invites.", "com.affine.notification.invitation-accepted": "<1>{{username}} has accept your invitation", "com.affine.notification.invitation-review-request": "<1>{{username}} has requested to join <2>{{workspaceName}}",