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 b934cddf19..e46b70dee3 100644
--- a/packages/frontend/core/src/components/notification/list.style.css.ts
+++ b/packages/frontend/core/src/components/notification/list.style.css.ts
@@ -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({
diff --git a/packages/frontend/core/src/components/notification/list.tsx b/packages/frontend/core/src/components/notification/list.tsx
index 33adee58a7..566b51dfd5 100644
--- a/packages/frontend/core/src/components/notification/list.tsx
+++ b/packages/frontend/core/src/components/notification/list.tsx
@@ -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 (
-
-
- {notifications.length > 0 ? (
-
- ) : isLoading ? (
-
- ) : userFriendlyError ? (
- {userFriendlyError.message}
- ) : (
-
- )}
+ const handleDeleteAll = useCallback(() => {
+ notificationListService.readAllNotifications().catch(err => {
+ notify.error(UserFriendlyError.fromAny(err));
+ });
+ }, [notificationListService]);
-
- {hasMore ? t['com.affine.notification.loading-more']() : null}
-
-
-
-
+ return (
+
+
+ {t['com.affine.rootAppSidebar.notifications']()}
+ {notifications.length > 0 && (
+
} onClick={handleDeleteAll}>
+ {t['com.affine.notification.delete-all']()}
+
+ }
+ >
+ } />
+
+ )}
+
+
+
+ {notifications.length > 0 ? (
+
+ ) : isLoading ? (
+
+ ) : userFriendlyError ? (
+ {userFriendlyError.message}
+ ) : (
+
+ )}
+
+
+ {hasMore ? t['com.affine.notification.loading-more']() : null}
+
+
+
+
+
);
};
diff --git a/packages/frontend/core/src/modules/notification/services/list.ts b/packages/frontend/core/src/modules/notification/services/list.ts
index 9e860b885a..020c96a3c5 100644
--- a/packages/frontend/core/src/modules/notification/services/list.ts
+++ b/packages/frontend/core/src/modules/notification/services/list.ts
@@ -88,4 +88,23 @@ export class NotificationListService extends Service {
Math.max(this.notificationCount.count$.value - 1, 0)
);
}
+
+ async readAllNotifications() {
+ // optimistic clear all notifications
+ this.reset();
+ this.notificationCount.setCount(0);
+ // avoid loading more notifications after clear all notifications
+ this.hasMore$.setValue(false);
+
+ try {
+ await this.store.readAllNotifications();
+ } catch (err) {
+ // rollback the optimistic clear all notifications
+ this.reset();
+ this.loadMore();
+
+ // rethrow the error to the caller, to notify the user
+ throw err;
+ }
+ }
}
diff --git a/packages/frontend/core/src/modules/notification/stores/notification.ts b/packages/frontend/core/src/modules/notification/stores/notification.ts
index f7f663149f..945e74bfcd 100644
--- a/packages/frontend/core/src/modules/notification/stores/notification.ts
+++ b/packages/frontend/core/src/modules/notification/stores/notification.ts
@@ -5,6 +5,7 @@ import {
mentionUserMutation,
notificationCountQuery,
type PaginationInput,
+ readAllNotificationsMutation,
readNotificationMutation,
type UnionNotificationBodyType,
} from '@affine/graphql';
@@ -85,6 +86,12 @@ export class NotificationStore extends Store {
});
}
+ readAllNotifications() {
+ return this.gqlService.gql({
+ query: readAllNotificationsMutation,
+ });
+ }
+
async mentionUser(
userId: string,
workspaceId: string,
diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts
index e97a94decf..91f8040ee3 100644
--- a/packages/frontend/i18n/src/i18n.gen.ts
+++ b/packages/frontend/i18n/src/i18n.gen.ts
@@ -7778,6 +7778,10 @@ export function useAFFiNEI18N(): {
* `Accept & Join`
*/
["com.affine.notification.invitation.accept"](): string;
+ /**
+ * `Delete all notifications`
+ */
+ ["com.affine.notification.delete-all"](): string;
/**
* `Tips`
*/
diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json
index 060ce21459..cd9e8f5523 100644
--- a/packages/frontend/i18n/src/resources/en.json
+++ b/packages/frontend/i18n/src/resources/en.json
@@ -1948,6 +1948,7 @@
"com.affine.notification.invitation-blocked": "There is an issue regarding your invitation to <1>{{workspaceName}}1> ",
"com.affine.notification.invitation": "<1>{{username}}1> invited you to join <2>{{workspaceName}}2>",
"com.affine.notification.invitation.accept": "Accept & Join",
+ "com.affine.notification.delete-all": "Delete all notifications",
"tips": "Tips",
"Template": "Template",
"com.affine.template-list.delete": "Delete Template",