Repository: toeverything/AFFiNE
Pull request #11423: fix(core): fix notification list
Author: login: EYHN
Labels: name: mod:i18n
description: Related to i18n
name: app:server
name: app:core
Created: 2025-04-02T14:08:47Z | Merged: 2025-04-03T10:58:32Z



Diff:
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 ee442003b4402..01b6f600b747b 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 074703567283b..aadaf017e3bd8 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<HTMLDivElement>(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<HTMLDivElement>) => {
-      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 (
     <Scrollable.Root>
-      <Scrollable.Viewport
-        className={styles.containerScrollViewport}
-        onScroll={handleScroll}
-      >
+      <Scrollable.Viewport className={styles.containerScrollViewport}>
         {notifications.length > 0 ? (
           <ul className={styles.itemList}>
             {notifications.map(notification => (
@@ -94,6 +101,13 @@ export const NotificationList = () => {
         ) : (
           <NotificationListEmpty />
         )}
+
+        <div
+          ref={loadMoreIndicatorRef}
+          className={hasMore ? styles.loadMoreIndicator : ''}
+        >
+          {hasMore ? t['com.affine.notification.loading-more']() : null}
+        </div>
       </Scrollable.Viewport>
       <Scrollable.Scrollbar />
     </Scrollable.Root>
diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts
index 0d757dff30aeb..c11d1bd15e4bb 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 0c51356f754db..47e5d303bb436 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}}</1> mentioned you in <2>{{docTitle}}</2>",
   "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}}</1> has accept your invitation",
   "com.affine.notification.invitation-review-request": "<1>{{username}}</1> has requested to join <2>{{workspaceName}}</2>",


Reviews:
user: login: pengx17
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T14:13:51Z
user: login: pengx17
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T14:17:20Z
user: login: pengx17
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-02T14:17:35Z
user: login: fengmk2
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T01:28:15Z
user: login: fengmk2
body: fix ci and merge it.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-03T06:04:19Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T06:26:29Z

Review comments:
diff_hunk: @@ -209,7 +209,7 @@ export class NotificationModel extends BaseModel {
       where: {
         userId,
         ...(options?.includeRead ? {} : { read: false }),
-        ...(options?.after ? { createdAt: { gt: options.after } } : {}),
+        ...(options?.after ? { createdAt: { lt: options.after } } : {}),
path: packages/backend/server/src/models/notification.ts
user: login: pengx17
body: This is strange. Are you sure?
created_at: 2025-04-02T14:13:50Z
author_association: COLLABORATOR
diff_hunk: @@ -94,6 +100,13 @@ export const NotificationList = () => {
         ) : (
           <NotificationListEmpty />
         )}
+
+        <div
+          ref={loadMoreIndicatorRef}
+          className={hasMore ? styles.loadMoreIndicator : ''}
+        >
+          {hasMore ? 'Loading more...' : null}
path: packages/frontend/core/src/components/notification/list.tsx
user: login: pengx17
body: need i18n
created_at: 2025-04-02T14:17:19Z
author_association: COLLABORATOR
diff_hunk: @@ -209,7 +209,7 @@ export class NotificationModel extends BaseModel {
       where: {
         userId,
         ...(options?.includeRead ? {} : { read: false }),
-        ...(options?.after ? { createdAt: { gt: options.after } } : {}),
+        ...(options?.after ? { createdAt: { lt: options.after } } : {}),
path: packages/backend/server/src/models/notification.ts
user: login: fengmk2
body: fixed in https://app.graphite.dev/github/pr/toeverything/AFFiNE/11430/fix(server)-correct-notification-pagination-by-using-'lt'-instead-of-'gt'-for-createdAt
created_at: 2025-04-03T01:28:15Z
author_association: COLLABORATOR
diff_hunk: @@ -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();
path: packages/frontend/core/src/components/notification/list.tsx
user: login: graphite-app[bot]
body: It appears that `useI18n` is being used on line 52, but there's no import statement for this function. Please add the appropriate import for `useI18n` at the top of the file with the other imports. This will ensure the code follows the project's import conventions and prevent potential runtime errors.
```suggestion
  const t = useI18n();
```
  

*Spotted by [Diamond](https://app.graphite.dev/diamond/?org=toeverything&ref=ai-review-comment) (based on [custom rules](https://app.graphite.dev/diamond/?org=toeverything&ref=ai-review-comment&view=settings))*<i class='graphite__hidden'><br /><br />Is this helpful? React 👍 or 👎 to let us know.</i>
created_at: 2025-04-03T06:26:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: EYHN
created_at: 2025-04-02T14:09:04Z
body: * **#11423** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/11423?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/11423?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* `canary`
<hr />

## How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:
- <kbd>merge</kbd> - adds this PR to the back of the merge queue
- <kbd>hotfix</kbd> - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using [this link](https://app.graphite.dev/invite/github/toeverything?ref=merge-queue-instructions-in-stack-comment&prId=11423).

<h2></h2>
<sub>

An organization admin has enabled the [Graphite Merge Queue](https://graphite.dev/docs/graphite-merge-queue) in this repository.

</sub>

<sub>

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

</sub>

<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-04-02T14:14:43Z
body: ## [Codecov](https://app.codecov.io/gh/toeverything/AFFiNE/pull/11423?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 53.53%. Comparing base [(`01429f8`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/01429f8f665029ccb572f8fc18f72607b89e989e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) to head [(`093bffd`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/093bffdf5c311df9480c95a506151e8a29bf7362?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).
> Report is 1 commits behind head on canary.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           canary   #11423      +/-   ##
==========================================
- Coverage   53.53%   53.53%   -0.01%     
==========================================
  Files        2572     2572              
  Lines      116466   116466              
  Branches    18996    18996              
==========================================
- Hits        62354    62353       -1     
- Misses      52804    52806       +2     
+ Partials     1308     1307       -1     
```

| [Flag](https://app.codecov.io/gh/toeverything/AFFiNE/pull/11423/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | Coverage Δ | |
|---|---|---|
| [server-test](https://app.codecov.io/gh/toeverything/AFFiNE/pull/11423/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `78.37% <ø> (ø)` | |
| [unittest](https://app.codecov.io/gh/toeverything/AFFiNE/pull/11423/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `29.41% <ø> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/toeverything/AFFiNE/pull/11423?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: graphite-app[bot]
created_at: 2025-04-03T10:15:33Z
body: ### Merge activity

* **Apr 3, 6:15 AM EDT**: `EYHN` added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).
* **Apr 3, 6:58 AM EDT**: Merged by the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).

author_association: CONTRIBUTOR