feat(core): clear all notifications (#13144)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a "Delete All" option in the notifications list, allowing users
to mark all notifications as read at once.
* Introduced a header with a menu button in the notifications list for
easier access to actions.

* **Style**
* Updated notification list layout with improved structure, including a
header and a scrollable content area.

* **Localization**
* Added a new English localization string for the "Delete all
notifications" action.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
EYHN
2025-07-11 10:50:48 +08:00
committed by GitHub
parent fc1104cd68
commit d98b45ca3d
6 changed files with 110 additions and 31 deletions
@@ -2,9 +2,32 @@ import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { keyframes, style } from '@vanilla-extract/css';
export const containerScrollViewport = style({
export const container = style({
maxHeight: '448px',
width: '360px',
display: 'flex',
flexDirection: 'column',
});
export const header = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
fontSize: cssVar('fontSm'),
lineHeight: '22px',
padding: '4px 8px 8px',
borderBottom: `1px solid ${cssVarV2('layer/insideBorder/border')}`,
});
export const scrollRoot = style({
flex: 1,
display: 'flex',
flexDirection: 'column',
});
export const scrollViewport = style({
flex: 1,
padding: '8px 0px ',
});
export const itemList = style({
@@ -2,6 +2,8 @@ import {
Avatar,
Button,
IconButton,
Menu,
MenuItem,
notify,
observeIntersection,
Scrollable,
@@ -31,6 +33,7 @@ import {
CollaborationIcon,
DeleteIcon,
EdgelessIcon,
MoreHorizontalIcon,
NotificationIcon,
PageIcon,
} from '@blocksuite/icons/rc';
@@ -80,37 +83,59 @@ export const NotificationList = () => {
return;
}, [hasMore, notificationListService]);
return (
<Scrollable.Root>
<Scrollable.Viewport className={styles.containerScrollViewport}>
{notifications.length > 0 ? (
<ul className={styles.itemList}>
{notifications.map(notification => (
<li key={notification.id}>
<NotificationItem notification={notification} />
</li>
))}
{userFriendlyError && (
<div className={styles.error}>{userFriendlyError.message}</div>
)}
</ul>
) : isLoading ? (
<NotificationItemSkeleton />
) : userFriendlyError ? (
<div className={styles.error}>{userFriendlyError.message}</div>
) : (
<NotificationListEmpty />
)}
const handleDeleteAll = useCallback(() => {
notificationListService.readAllNotifications().catch(err => {
notify.error(UserFriendlyError.fromAny(err));
});
}, [notificationListService]);
<div
ref={loadMoreIndicatorRef}
className={hasMore ? styles.loadMoreIndicator : ''}
>
{hasMore ? t['com.affine.notification.loading-more']() : null}
</div>
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
return (
<div className={styles.container}>
<div className={styles.header}>
<span>{t['com.affine.rootAppSidebar.notifications']()}</span>
{notifications.length > 0 && (
<Menu
items={
<MenuItem prefixIcon={<DeleteIcon />} onClick={handleDeleteAll}>
<span>{t['com.affine.notification.delete-all']()}</span>
</MenuItem>
}
>
<IconButton icon={<MoreHorizontalIcon />} />
</Menu>
)}
</div>
<Scrollable.Root className={styles.scrollRoot}>
<Scrollable.Viewport className={styles.scrollViewport}>
{notifications.length > 0 ? (
<ul className={styles.itemList}>
{notifications.map(notification => (
<li key={notification.id}>
<NotificationItem notification={notification} />
</li>
))}
{userFriendlyError && (
<div className={styles.error}>{userFriendlyError.message}</div>
)}
</ul>
) : isLoading ? (
<NotificationItemSkeleton />
) : userFriendlyError ? (
<div className={styles.error}>{userFriendlyError.message}</div>
) : (
<NotificationListEmpty />
)}
<div
ref={loadMoreIndicatorRef}
className={hasMore ? styles.loadMoreIndicator : ''}
>
{hasMore ? t['com.affine.notification.loading-more']() : null}
</div>
</Scrollable.Viewport>
<Scrollable.Scrollbar />
</Scrollable.Root>
</div>
);
};