@stream-io/feeds-client 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/@react-bindings/hooks/feed-state-hooks/index.ts +4 -0
  2. package/CHANGELOG.md +16 -0
  3. package/dist/@react-bindings/contexts/StreamSearchContext.d.ts +1 -1
  4. package/dist/@react-bindings/contexts/StreamSearchResultsContext.d.ts +1 -1
  5. package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +4 -0
  6. package/dist/@react-bindings/hooks/feed-state-hooks/useAggregatedActivities.d.ts +11 -0
  7. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivityRead.d.ts +6 -0
  8. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivitySeen.d.ts +6 -0
  9. package/dist/@react-bindings/hooks/feed-state-hooks/useNotificationStatus.d.ts +13 -0
  10. package/dist/@react-bindings/hooks/search-state-hooks/useSearchQuery.d.ts +1 -1
  11. package/dist/@react-bindings/hooks/search-state-hooks/useSearchResult.d.ts +1 -1
  12. package/dist/@react-bindings/hooks/search-state-hooks/useSearchSources.d.ts +2 -2
  13. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
  14. package/dist/@react-bindings/wrappers/StreamSearch.d.ts +1 -1
  15. package/dist/@react-bindings/wrappers/StreamSearchResults.d.ts +1 -1
  16. package/dist/index-react-bindings.browser.cjs +178 -35
  17. package/dist/index-react-bindings.browser.cjs.map +1 -1
  18. package/dist/index-react-bindings.browser.js +175 -36
  19. package/dist/index-react-bindings.browser.js.map +1 -1
  20. package/dist/index-react-bindings.node.cjs +178 -35
  21. package/dist/index-react-bindings.node.cjs.map +1 -1
  22. package/dist/index-react-bindings.node.js +175 -36
  23. package/dist/index-react-bindings.node.js.map +1 -1
  24. package/dist/index.browser.cjs +328 -180
  25. package/dist/index.browser.cjs.map +1 -1
  26. package/dist/index.browser.js +328 -181
  27. package/dist/index.browser.js.map +1 -1
  28. package/dist/index.d.ts +1 -5
  29. package/dist/index.node.cjs +328 -180
  30. package/dist/index.node.cjs.map +1 -1
  31. package/dist/index.node.js +328 -181
  32. package/dist/index.node.js.map +1 -1
  33. package/dist/src/common/{ActivitySearchSource.d.ts → search/ActivitySearchSource.d.ts} +3 -3
  34. package/dist/src/common/{BaseSearchSource.d.ts → search/BaseSearchSource.d.ts} +41 -35
  35. package/dist/src/common/{FeedSearchSource.d.ts → search/FeedSearchSource.d.ts} +3 -3
  36. package/dist/src/common/{SearchController.d.ts → search/SearchController.d.ts} +1 -3
  37. package/dist/src/common/{UserSearchSource.d.ts → search/UserSearchSource.d.ts} +4 -4
  38. package/dist/src/common/search/index.d.ts +6 -0
  39. package/dist/src/common/search/types.d.ts +22 -0
  40. package/dist/src/common/types.d.ts +1 -0
  41. package/dist/src/feed/event-handlers/activity/handle-activity-deleted.d.ts +5 -12
  42. package/dist/src/feed/event-handlers/activity/handle-activity-marked.d.ts +11 -0
  43. package/dist/src/feed/event-handlers/activity/index.d.ts +1 -0
  44. package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +8 -1
  45. package/dist/src/feed/feed.d.ts +2 -2
  46. package/dist/src/gen/models/index.d.ts +58 -26
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/index.ts +1 -5
  49. package/package.json +1 -1
  50. package/src/common/{ActivitySearchSource.ts → search/ActivitySearchSource.ts} +3 -3
  51. package/src/common/{BaseSearchSource.ts → search/BaseSearchSource.ts} +137 -69
  52. package/src/common/{FeedSearchSource.ts → search/FeedSearchSource.ts} +3 -3
  53. package/src/common/{SearchController.ts → search/SearchController.ts} +2 -7
  54. package/src/common/{UserSearchSource.ts → search/UserSearchSource.ts} +3 -3
  55. package/src/common/search/index.ts +6 -0
  56. package/src/common/search/types.ts +21 -0
  57. package/src/common/types.ts +2 -0
  58. package/src/feed/event-handlers/activity/activity-marked-utils.test.ts +208 -0
  59. package/src/feed/event-handlers/activity/activity-utils.test.ts +2 -2
  60. package/src/feed/event-handlers/activity/handle-activity-added.test.ts +86 -0
  61. package/src/feed/event-handlers/activity/handle-activity-deleted.test.ts +117 -0
  62. package/src/feed/event-handlers/activity/handle-activity-deleted.ts +8 -4
  63. package/src/feed/event-handlers/activity/handle-activity-marked.ts +68 -0
  64. package/src/feed/event-handlers/activity/handle-activity-reaction-added.test.ts +15 -15
  65. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.test.ts +14 -14
  66. package/src/feed/event-handlers/activity/handle-activity-unpinned.test.ts +4 -3
  67. package/src/feed/event-handlers/activity/handle-activity-updated.test.ts +4 -4
  68. package/src/feed/event-handlers/activity/index.ts +2 -1
  69. package/src/feed/event-handlers/bookmark/handle-bookmark-added.test.ts +14 -14
  70. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.test.ts +14 -14
  71. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.test.ts +16 -16
  72. package/src/feed/event-handlers/comment/handle-comment-added.test.ts +147 -0
  73. package/src/feed/event-handlers/comment/handle-comment-deleted.test.ts +133 -0
  74. package/src/feed/event-handlers/comment/handle-comment-deleted.ts +24 -10
  75. package/src/feed/event-handlers/comment/handle-comment-reaction.test.ts +315 -0
  76. package/src/feed/event-handlers/comment/handle-comment-updated.test.ts +131 -0
  77. package/src/feed/event-handlers/feed-member/handle-feed-member-added.test.ts +75 -0
  78. package/src/feed/event-handlers/feed-member/handle-feed-member-removed.test.ts +82 -0
  79. package/src/feed/event-handlers/feed-member/handle-feed-member-removed.ts +19 -9
  80. package/src/feed/event-handlers/feed-member/handle-feed-member-updated.test.ts +84 -0
  81. package/src/feed/event-handlers/follow/handle-follow-created.test.ts +7 -7
  82. package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +2 -2
  83. package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +1 -1
  84. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.test.ts +120 -0
  85. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +47 -3
  86. package/src/feed/feed.ts +4 -2
  87. package/src/gen/feeds/FeedsApi.ts +6 -0
  88. package/src/gen/model-decoders/decoders.ts +12 -0
  89. package/src/gen/models/index.ts +90 -34
  90. package/src/test-utils/response-generators.ts +230 -0
  91. package/dist/src/test-utils/index.d.ts +0 -1
  92. package/dist/src/test-utils/response-generators.d.ts +0 -54
@@ -5,3 +5,7 @@ export * from './useFollowers';
5
5
  export * from './useFollowing';
6
6
  export * from './useFeedMetadata';
7
7
  export * from './useOwnFollows';
8
+ export * from './useNotificationStatus';
9
+ export * from './useAggregatedActivities';
10
+ export * from './useIsAggregatedActivityRead';
11
+ export * from './useIsAggregatedActivitySeen';
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.2.3](https://github.com/GetStream/stream-feeds-js/compare/@stream-io/feeds-client-0.2.2...@stream-io/feeds-client-0.2.3) (2025-09-01)
6
+
7
+
8
+ ### Features
9
+
10
+ * add the ability to keep search items stable while searching ([#104](https://github.com/GetStream/stream-feeds-js/issues/104)) ([62a9808](https://github.com/GetStream/stream-feeds-js/commit/62a980890ce3a16e34a6d323ef8f58d58d2f8f4c))
11
+ * update to latest API spec ([#106](https://github.com/GetStream/stream-feeds-js/issues/106)) ([6555d15](https://github.com/GetStream/stream-feeds-js/commit/6555d15049fddace837cfd592bff2d18293a16be))
12
+
13
+ ## [0.2.2](https://github.com/GetStream/stream-feeds-js/compare/@stream-io/feeds-client-0.2.1...@stream-io/feeds-client-0.2.2) (2025-08-25)
14
+
15
+
16
+ ### Features
17
+
18
+ * notification feed support ([#86](https://github.com/GetStream/stream-feeds-js/issues/86)) ([9ba245e](https://github.com/GetStream/stream-feeds-js/commit/9ba245e969c77f5c241fb4f5da93491ba87c3278))
19
+ * notification feeds ([#102](https://github.com/GetStream/stream-feeds-js/issues/102)) ([6822da8](https://github.com/GetStream/stream-feeds-js/commit/6822da8516ef83e66fd9b74f5497136ca51f8bda))
20
+
5
21
  ## [0.2.1](https://github.com/GetStream/stream-feeds-js/compare/@stream-io/feeds-client-0.2.0...@stream-io/feeds-client-0.2.1) (2025-08-20)
6
22
 
7
23
 
@@ -1,4 +1,4 @@
1
- import type { SearchController } from '../../src/common/SearchController';
1
+ import type { SearchController } from '../../src/common/search';
2
2
  export declare const StreamSearchContext: import("react").Context<SearchController | undefined>;
3
3
  /**
4
4
  * The props for the StreamSearchProvider component.
@@ -1,4 +1,4 @@
1
- import type { SearchSource } from '../../src/common/BaseSearchSource';
1
+ import type { SearchSource } from '../../src/common/search';
2
2
  export declare const StreamSearchResultsContext: import("react").Context<SearchSource<any> | undefined>;
3
3
  /**
4
4
  * The props for the StreamSearchResultsProvider component.
@@ -5,3 +5,7 @@ export * from './useFollowers';
5
5
  export * from './useFollowing';
6
6
  export * from './useFeedMetadata';
7
7
  export * from './useOwnFollows';
8
+ export * from './useNotificationStatus';
9
+ export * from './useAggregatedActivities';
10
+ export * from './useIsAggregatedActivityRead';
11
+ export * from './useIsAggregatedActivitySeen';
@@ -0,0 +1,11 @@
1
+ import { Feed, FeedState } from '../../../src/feed';
2
+ declare const selector: ({ aggregated_activities }: FeedState) => {
3
+ aggregated_activities: import("../../..").AggregatedActivityResponse[] | undefined;
4
+ };
5
+ type UseAggregatedActivitiesReturnType = ReturnType<typeof selector>;
6
+ /**
7
+ * A React hook that returns a reactive object containing the current aggregated activities.
8
+ */
9
+ export declare function useAggregatedActivities(feedFromProps: Feed): UseAggregatedActivitiesReturnType;
10
+ export declare function useAggregatedActivities(feedFromProps?: Feed): UseAggregatedActivitiesReturnType | undefined;
11
+ export {};
@@ -0,0 +1,6 @@
1
+ import { AggregatedActivityResponse } from '../../../src/gen/models';
2
+ import { Feed } from '../../../src/feed';
3
+ export declare const useIsAggregatedActivityRead: ({ feed: feedFromProps, aggregatedActivity, }: {
4
+ feed?: Feed;
5
+ aggregatedActivity: AggregatedActivityResponse;
6
+ }) => boolean;
@@ -0,0 +1,6 @@
1
+ import { AggregatedActivityResponse } from '../../../src/gen/models';
2
+ import { Feed } from '../../../src/feed';
3
+ export declare const useIsAggregatedActivitySeen: ({ feed: feedFromProps, aggregatedActivity, }: {
4
+ feed?: Feed;
5
+ aggregatedActivity: AggregatedActivityResponse;
6
+ }) => boolean;
@@ -0,0 +1,13 @@
1
+ import { Feed, FeedState } from '../../../src/feed';
2
+ declare const selector: ({ notification_status }: FeedState) => {
3
+ unread: number;
4
+ unseen: number;
5
+ last_read_at: Date | undefined;
6
+ last_seen_at: Date | undefined;
7
+ read_activities: string[] | undefined;
8
+ seen_activities: string[] | undefined;
9
+ };
10
+ type UseNotificationStatusReturnType = ReturnType<typeof selector>;
11
+ export declare function useNotificationStatus(feed: Feed): UseNotificationStatusReturnType;
12
+ export declare function useNotificationStatus(feed?: Feed): UseNotificationStatusReturnType | undefined;
13
+ export {};
@@ -1,4 +1,4 @@
1
- import type { SearchController } from '../../../src/common/SearchController';
1
+ import type { SearchController } from '../../../src/common/search';
2
2
  export declare const useSearchQuery: (controllerFromProps?: SearchController) => {
3
3
  searchQuery: string;
4
4
  } | undefined;
@@ -1,4 +1,4 @@
1
- import type { SearchSource } from '../../../src/common/BaseSearchSource';
1
+ import type { SearchSource } from '../../../src/common/search';
2
2
  export declare const useSearchResult: (sourceFromProps?: SearchSource) => {
3
3
  items: any[] | undefined;
4
4
  error: Error | undefined;
@@ -1,4 +1,4 @@
1
- import type { SearchController } from '../../../src/common/SearchController';
1
+ import type { SearchController } from '../../../src/common/search';
2
2
  export declare const useSearchSources: (controllerFromProps?: SearchController) => {
3
- sources: import("../../..").SearchSource<any>[];
3
+ sources: import("../../../src/common/search").SearchSource<any>[];
4
4
  } | undefined;
@@ -7,6 +7,6 @@ export type StreamFeedProps = {
7
7
  feed: Feed;
8
8
  };
9
9
  export declare const StreamFeed: {
10
- ({ feed, children }: PropsWithChildren<StreamFeedProps>): import("react/jsx-runtime").JSX.Element;
10
+ ({ feed, children, }: PropsWithChildren<StreamFeedProps>): import("react/jsx-runtime").JSX.Element;
11
11
  displayName: string;
12
12
  };
@@ -1,5 +1,5 @@
1
1
  import { PropsWithChildren } from 'react';
2
- import type { SearchController } from '../../src/common/SearchController';
2
+ import type { SearchController } from '../../src/common/search';
3
3
  /**
4
4
  * The props for the StreamSearch component. It accepts a `SearchController` instance.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  import { PropsWithChildren } from 'react';
2
- import type { SearchSource } from '../../src/common/BaseSearchSource';
2
+ import type { SearchSource } from '../../src/common/search';
3
3
  /**
4
4
  * The props for the StreamSearchResults component. It accepts a `SearchSource` instance.
5
5
  */
@@ -688,6 +688,7 @@ decoders.Command = (input) => {
688
688
  decoders.CommentAddedEvent = (input) => {
689
689
  const typeMappings = {
690
690
  created_at: { type: 'DatetimeType', isSingle: true },
691
+ activity: { type: 'ActivityResponse', isSingle: true },
691
692
  comment: { type: 'CommentResponse', isSingle: true },
692
693
  received_at: { type: 'DatetimeType', isSingle: true },
693
694
  };
@@ -704,6 +705,7 @@ decoders.CommentDeletedEvent = (input) => {
704
705
  decoders.CommentReactionAddedEvent = (input) => {
705
706
  const typeMappings = {
706
707
  created_at: { type: 'DatetimeType', isSingle: true },
708
+ activity: { type: 'ActivityResponse', isSingle: true },
707
709
  comment: { type: 'CommentResponse', isSingle: true },
708
710
  reaction: { type: 'FeedsReactionResponse', isSingle: true },
709
711
  received_at: { type: 'DatetimeType', isSingle: true },
@@ -722,6 +724,7 @@ decoders.CommentReactionDeletedEvent = (input) => {
722
724
  decoders.CommentReactionUpdatedEvent = (input) => {
723
725
  const typeMappings = {
724
726
  created_at: { type: 'DatetimeType', isSingle: true },
727
+ activity: { type: 'ActivityResponse', isSingle: true },
725
728
  comment: { type: 'CommentResponse', isSingle: true },
726
729
  reaction: { type: 'FeedsReactionResponse', isSingle: true },
727
730
  received_at: { type: 'DatetimeType', isSingle: true },
@@ -1084,6 +1087,7 @@ decoders.Message = (input) => {
1084
1087
  pin_expires: { type: 'DatetimeType', isSingle: true },
1085
1088
  pinned_at: { type: 'DatetimeType', isSingle: true },
1086
1089
  thread_participants: { type: 'User', isSingle: false },
1090
+ member: { type: 'ChannelMember', isSingle: true },
1087
1091
  pinned_by: { type: 'User', isSingle: true },
1088
1092
  poll: { type: 'Poll', isSingle: true },
1089
1093
  quoted_message: { type: 'Message', isSingle: true },
@@ -1137,6 +1141,8 @@ decoders.ModerationCustomActionEvent = (input) => {
1137
1141
  };
1138
1142
  decoders.ModerationFlagResponse = (input) => {
1139
1143
  const typeMappings = {
1144
+ created_at: { type: 'DatetimeType', isSingle: true },
1145
+ updated_at: { type: 'DatetimeType', isSingle: true },
1140
1146
  review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
1141
1147
  user: { type: 'UserResponse', isSingle: true },
1142
1148
  };
@@ -1903,6 +1909,7 @@ class FeedsApi {
1903
1909
  const body = {
1904
1910
  type: request?.type,
1905
1911
  create_notification_activity: request?.create_notification_activity,
1912
+ skip_push: request?.skip_push,
1906
1913
  custom: request?.custom,
1907
1914
  };
1908
1915
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/activities/{activity_id}/reactions', pathParams, undefined, body, 'application/json');
@@ -2049,6 +2056,7 @@ class FeedsApi {
2049
2056
  object_type: request?.object_type,
2050
2057
  create_notification_activity: request?.create_notification_activity,
2051
2058
  parent_id: request?.parent_id,
2059
+ skip_push: request?.skip_push,
2052
2060
  attachments: request?.attachments,
2053
2061
  mentioned_user_ids: request?.mentioned_user_ids,
2054
2062
  custom: request?.custom,
@@ -2102,6 +2110,7 @@ class FeedsApi {
2102
2110
  };
2103
2111
  const body = {
2104
2112
  comment: request?.comment,
2113
+ skip_push: request?.skip_push,
2105
2114
  custom: request?.custom,
2106
2115
  };
2107
2116
  const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/comments/{id}', pathParams, undefined, body, 'application/json');
@@ -2115,6 +2124,7 @@ class FeedsApi {
2115
2124
  const body = {
2116
2125
  type: request?.type,
2117
2126
  create_notification_activity: request?.create_notification_activity,
2127
+ skip_push: request?.skip_push,
2118
2128
  custom: request?.custom,
2119
2129
  };
2120
2130
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{id}/reactions', pathParams, undefined, body, 'application/json');
@@ -2355,6 +2365,7 @@ class FeedsApi {
2355
2365
  create_notification_activity: request?.create_notification_activity,
2356
2366
  follower_role: request?.follower_role,
2357
2367
  push_preference: request?.push_preference,
2368
+ skip_push: request?.skip_push,
2358
2369
  custom: request?.custom,
2359
2370
  };
2360
2371
  const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/follows', undefined, undefined, body, 'application/json');
@@ -2367,6 +2378,7 @@ class FeedsApi {
2367
2378
  target: request?.target,
2368
2379
  create_notification_activity: request?.create_notification_activity,
2369
2380
  push_preference: request?.push_preference,
2381
+ skip_push: request?.skip_push,
2370
2382
  custom: request?.custom,
2371
2383
  };
2372
2384
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/follows', undefined, undefined, body, 'application/json');
@@ -4505,20 +4517,29 @@ function handleCommentAdded(event) {
4505
4517
  function handleCommentDeleted({ comment }) {
4506
4518
  const entityId = comment.parent_id ?? comment.object_id;
4507
4519
  this.state.next((currentState) => {
4508
- const newCommentsByEntityId = {
4509
- ...currentState.comments_by_entity_id,
4510
- [entityId]: {
4511
- ...currentState.comments_by_entity_id[entityId],
4512
- },
4513
- };
4520
+ let newCommentsByEntityId;
4514
4521
  const index = this.getCommentIndex(comment, currentState);
4515
- if (newCommentsByEntityId?.[entityId]?.comments?.length && index !== -1) {
4522
+ if (index !== -1) {
4523
+ newCommentsByEntityId ?? (newCommentsByEntityId = {
4524
+ ...currentState.comments_by_entity_id,
4525
+ [entityId]: {
4526
+ ...currentState.comments_by_entity_id[entityId],
4527
+ },
4528
+ });
4516
4529
  newCommentsByEntityId[entityId].comments = [
4517
4530
  ...newCommentsByEntityId[entityId].comments,
4518
4531
  ];
4519
4532
  newCommentsByEntityId[entityId]?.comments?.splice(index, 1);
4520
4533
  }
4521
- delete newCommentsByEntityId[comment.id];
4534
+ if (typeof currentState.comments_by_entity_id[comment.id] !== 'undefined') {
4535
+ newCommentsByEntityId ?? (newCommentsByEntityId = {
4536
+ ...currentState.comments_by_entity_id,
4537
+ });
4538
+ delete newCommentsByEntityId[comment.id];
4539
+ }
4540
+ if (!newCommentsByEntityId) {
4541
+ return currentState;
4542
+ }
4522
4543
  return {
4523
4544
  ...currentState,
4524
4545
  comments_by_entity_id: newCommentsByEntityId,
@@ -4639,14 +4660,20 @@ function handleFeedMemberUpdated(event) {
4639
4660
  function handleFeedMemberRemoved(event) {
4640
4661
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
4641
4662
  this.state.next((currentState) => {
4642
- const newState = {
4643
- ...currentState,
4644
- members: currentState.members?.filter((member) => member.user.id !== event.user?.id),
4645
- };
4646
- if (connectedUser?.id === event.member_id) {
4663
+ let newState;
4664
+ if (typeof currentState.members !== 'undefined') {
4665
+ const filtered = currentState.members.filter((member) => member.user.id !== event.member_id);
4666
+ if (filtered.length !== currentState.members.length) {
4667
+ newState ?? (newState = { ...currentState });
4668
+ newState.members = filtered;
4669
+ }
4670
+ }
4671
+ if (connectedUser?.id === event.member_id &&
4672
+ typeof currentState.own_membership !== 'undefined') {
4673
+ newState ?? (newState = { ...currentState });
4647
4674
  delete newState.own_membership;
4648
4675
  }
4649
- return newState;
4676
+ return newState ?? currentState;
4650
4677
  });
4651
4678
  }
4652
4679
 
@@ -4883,7 +4910,7 @@ const removePinnedActivityFromState = (activityResponse, pinnedActivities) => {
4883
4910
  if (index !== -1) {
4884
4911
  const newActivities = [...pinnedActivities];
4885
4912
  newActivities.splice(index, 1);
4886
- return { changed: true, activities: newActivities };
4913
+ return { changed: true, pinned_activities: newActivities };
4887
4914
  }
4888
4915
  else {
4889
4916
  return { changed: false, pinned_activities: pinnedActivities };
@@ -5073,13 +5100,80 @@ function handleActivityReactionDeleted(event) {
5073
5100
  }
5074
5101
  }
5075
5102
 
5103
+ const updateNotificationStatusFromActivityMarked = (event, currentNotificationStatus, aggregatedActivities = []) => {
5104
+ if (!currentNotificationStatus) {
5105
+ return {
5106
+ changed: false,
5107
+ };
5108
+ }
5109
+ const newState = {
5110
+ ...currentNotificationStatus,
5111
+ };
5112
+ if (event.mark_all_read) {
5113
+ const allGroupIds = aggregatedActivities.map((activity) => activity.group);
5114
+ newState.read_activities = [
5115
+ ...new Set([
5116
+ ...(currentNotificationStatus.read_activities ?? []),
5117
+ ...allGroupIds,
5118
+ ]),
5119
+ ];
5120
+ }
5121
+ if (event.mark_read && event.mark_read.length > 0) {
5122
+ newState.read_activities = [
5123
+ ...new Set([
5124
+ ...(currentNotificationStatus?.read_activities ?? []),
5125
+ ...event.mark_read,
5126
+ ]),
5127
+ ];
5128
+ }
5129
+ if (event.mark_all_seen) {
5130
+ newState.last_seen_at = new Date();
5131
+ }
5132
+ return {
5133
+ changed: true,
5134
+ data: { notification_status: newState },
5135
+ };
5136
+ };
5137
+ function handleActivityMarked(event) {
5138
+ const result = updateNotificationStatusFromActivityMarked(event, this.currentState.notification_status, this.currentState.aggregated_activities);
5139
+ if (result.changed) {
5140
+ this.state.partialNext({
5141
+ notification_status: result.data?.notification_status,
5142
+ });
5143
+ }
5144
+ }
5145
+
5076
5146
  function handleFeedUpdated(event) {
5077
5147
  this.state.partialNext({ ...event.feed });
5078
5148
  }
5079
5149
 
5150
+ const updateNotificationFeedFromEvent = (event) => {
5151
+ const updates = {};
5152
+ if (event.notification_status) {
5153
+ updates.notification_status = event.notification_status;
5154
+ }
5155
+ if (event.aggregated_activities) {
5156
+ updates.aggregated_activities = event.aggregated_activities;
5157
+ }
5158
+ // Only return changed if we have actual updates
5159
+ if (Object.keys(updates).length > 0) {
5160
+ return {
5161
+ changed: true,
5162
+ data: updates,
5163
+ };
5164
+ }
5165
+ return {
5166
+ changed: false,
5167
+ };
5168
+ };
5080
5169
  function handleNotificationFeedUpdated(event) {
5081
- console.info('notification feed updated', event);
5082
- // TODO: handle notification feed updates
5170
+ const result = updateNotificationFeedFromEvent(event);
5171
+ if (result.changed) {
5172
+ this.state.partialNext({
5173
+ notification_status: result.data?.notification_status,
5174
+ aggregated_activities: result.data?.aggregated_activities,
5175
+ });
5176
+ }
5083
5177
  }
5084
5178
 
5085
5179
  function handleWatchStarted() {
@@ -5134,7 +5228,7 @@ class Feed extends FeedApi {
5134
5228
  'feeds.poll.vote_removed': Feed.noop,
5135
5229
  'feeds.activity.pinned': Feed.noop,
5136
5230
  'feeds.activity.unpinned': Feed.noop,
5137
- 'feeds.activity.marked': Feed.noop,
5231
+ 'feeds.activity.marked': handleActivityMarked.bind(this),
5138
5232
  'moderation.custom_action': Feed.noop,
5139
5233
  'moderation.flagged': Feed.noop,
5140
5234
  'moderation.mark_reviewed': Feed.noop,
@@ -6006,10 +6100,10 @@ const useFeedsClient = () => {
6006
6100
  */
6007
6101
  const useClientConnectedUser = () => {
6008
6102
  const client = useFeedsClient();
6009
- const { user } = useStateStore(client?.state, selector$a) ?? {};
6103
+ const { user } = useStateStore(client?.state, selector$c) ?? {};
6010
6104
  return user;
6011
6105
  };
6012
- const selector$a = (nextState) => ({
6106
+ const selector$c = (nextState) => ({
6013
6107
  user: nextState.connected_user,
6014
6108
  });
6015
6109
 
@@ -6018,10 +6112,10 @@ const selector$a = (nextState) => ({
6018
6112
  */
6019
6113
  const useWsConnectionState = () => {
6020
6114
  const client = useFeedsClient();
6021
- const { is_healthy } = useStateStore(client?.state, selector$9) ?? {};
6115
+ const { is_healthy } = useStateStore(client?.state, selector$b) ?? {};
6022
6116
  return { is_healthy };
6023
6117
  };
6024
- const selector$9 = (nextState) => ({
6118
+ const selector$b = (nextState) => ({
6025
6119
  is_healthy: nextState.is_ws_connection_healthy,
6026
6120
  });
6027
6121
 
@@ -6071,7 +6165,7 @@ const useStableCallback = (callback) => {
6071
6165
  const useFeedActivities = (feedFromProps) => {
6072
6166
  const feedFromContext = useFeedContext();
6073
6167
  const feed = feedFromProps ?? feedFromContext;
6074
- const data = useStateStore(feed?.state, selector$8);
6168
+ const data = useStateStore(feed?.state, selector$a);
6075
6169
  const loadNextPage = useStableCallback(async () => {
6076
6170
  if (!feed || !data?.has_next_page || data?.is_loading) {
6077
6171
  return;
@@ -6080,7 +6174,7 @@ const useFeedActivities = (feedFromProps) => {
6080
6174
  });
6081
6175
  return react.useMemo(() => ({ ...data, loadNextPage }), [data, loadNextPage]);
6082
6176
  };
6083
- const selector$8 = ({ is_loading_activities, next, activities = [] }) => ({
6177
+ const selector$a = ({ is_loading_activities, next, activities = [] }) => ({
6084
6178
  is_loading: is_loading_activities,
6085
6179
  has_next_page: typeof next !== 'undefined',
6086
6180
  activities,
@@ -6153,13 +6247,13 @@ const FeedOwnCapability = {
6153
6247
  };
6154
6248
 
6155
6249
  const stableEmptyArray = [];
6156
- const selector$7 = (currentState) => ({
6250
+ const selector$9 = (currentState) => ({
6157
6251
  oc: currentState.own_capabilities ?? stableEmptyArray,
6158
6252
  });
6159
6253
  const useOwnCapabilities = (feedFromProps) => {
6160
6254
  const feedFromContext = useFeedContext();
6161
6255
  const feed = feedFromProps ?? feedFromContext;
6162
- const { oc = stableEmptyArray } = useStateStore(feed?.state, selector$7) ?? {};
6256
+ const { oc = stableEmptyArray } = useStateStore(feed?.state, selector$9) ?? {};
6163
6257
  return react.useMemo(() => ({
6164
6258
  can_add_activity: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY) > -1,
6165
6259
  can_add_activity_reaction: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY_REACTION) > -1,
@@ -6194,7 +6288,7 @@ const useOwnCapabilities = (feedFromProps) => {
6194
6288
  }), [oc]);
6195
6289
  };
6196
6290
 
6197
- const selector$6 = ({ follower_count, followers, followers_pagination, }) => ({
6291
+ const selector$8 = ({ follower_count, followers, followers_pagination, }) => ({
6198
6292
  follower_count,
6199
6293
  followers,
6200
6294
  followers_pagination,
@@ -6202,7 +6296,7 @@ const selector$6 = ({ follower_count, followers, followers_pagination, }) => ({
6202
6296
  function useFollowers(feedFromProps) {
6203
6297
  const feedFromContext = useFeedContext();
6204
6298
  const feed = feedFromProps ?? feedFromContext;
6205
- const data = useStateStore(feed?.state, selector$6);
6299
+ const data = useStateStore(feed?.state, selector$8);
6206
6300
  const loadNextPage = react.useCallback((...options) => feed?.loadNextPageFollowers(...options), [feed]);
6207
6301
  return react.useMemo(() => {
6208
6302
  if (!data) {
@@ -6217,7 +6311,7 @@ function useFollowers(feedFromProps) {
6217
6311
  }, [data, loadNextPage]);
6218
6312
  }
6219
6313
 
6220
- const selector$5 = ({ following_count, following, following_pagination, }) => ({
6314
+ const selector$7 = ({ following_count, following, following_pagination, }) => ({
6221
6315
  following_count,
6222
6316
  following,
6223
6317
  following_pagination,
@@ -6225,7 +6319,7 @@ const selector$5 = ({ following_count, following, following_pagination, }) => ({
6225
6319
  function useFollowing(feedFromProps) {
6226
6320
  const feedFromContext = useFeedContext();
6227
6321
  const feed = feedFromProps ?? feedFromContext;
6228
- const data = useStateStore(feed?.state, selector$5);
6322
+ const data = useStateStore(feed?.state, selector$7);
6229
6323
  const loadNextPage = react.useCallback((...options) => feed?.loadNextPageFollowing(...options), [feed]);
6230
6324
  return react.useMemo(() => {
6231
6325
  if (!data) {
@@ -6247,9 +6341,9 @@ function useFollowing(feedFromProps) {
6247
6341
  const useFeedMetadata = (feedFromProps) => {
6248
6342
  const feedFromContext = useFeedContext();
6249
6343
  const feed = feedFromProps ?? feedFromContext;
6250
- return useStateStore(feed?.state, selector$4);
6344
+ return useStateStore(feed?.state, selector$6);
6251
6345
  };
6252
- const selector$4 = ({ follower_count = 0, following_count = 0, created_by, created_at, updated_at, }) => ({
6346
+ const selector$6 = ({ follower_count = 0, following_count = 0, created_by, created_at, updated_at, }) => ({
6253
6347
  created_by,
6254
6348
  follower_count,
6255
6349
  following_count,
@@ -6264,12 +6358,57 @@ const selector$4 = ({ follower_count = 0, following_count = 0, created_by, creat
6264
6358
  const useOwnFollows = (feedFromProps) => {
6265
6359
  const feedFromContext = useFeedContext();
6266
6360
  const feed = feedFromProps ?? feedFromContext;
6267
- return useStateStore(feed?.state, selector$3);
6361
+ return useStateStore(feed?.state, selector$5);
6268
6362
  };
6269
- const selector$3 = ({ own_follows }) => ({
6363
+ const selector$5 = ({ own_follows }) => ({
6270
6364
  own_follows,
6271
6365
  });
6272
6366
 
6367
+ const selector$4 = ({ notification_status }) => ({
6368
+ unread: notification_status?.unread ?? 0,
6369
+ unseen: notification_status?.unseen ?? 0,
6370
+ last_read_at: notification_status?.last_read_at,
6371
+ last_seen_at: notification_status?.last_seen_at,
6372
+ read_activities: notification_status?.read_activities,
6373
+ seen_activities: notification_status?.seen_activities,
6374
+ });
6375
+ function useNotificationStatus(feedFromProps) {
6376
+ const feedFromContext = useFeedContext();
6377
+ const feed = feedFromProps ?? feedFromContext;
6378
+ return useStateStore(feed?.state, selector$4);
6379
+ }
6380
+
6381
+ const selector$3 = ({ aggregated_activities }) => ({
6382
+ aggregated_activities,
6383
+ });
6384
+ function useAggregatedActivities(feedFromProps) {
6385
+ const feedFromContext = useFeedContext();
6386
+ const feed = feedFromProps ?? feedFromContext;
6387
+ return useStateStore(feed?.state, selector$3);
6388
+ }
6389
+
6390
+ const useIsAggregatedActivityRead = ({ feed: feedFromProps, aggregatedActivity, }) => {
6391
+ const feedFromContext = useFeedContext();
6392
+ const feed = feedFromProps ?? feedFromContext;
6393
+ const { read_activities: readActivities, /* last_read_at: lastReadAt */ } = useNotificationStatus(feed) ?? {};
6394
+ const group = aggregatedActivity.group;
6395
+ return react.useMemo(() =>
6396
+ // FIXME: This part of the condition does not work as marking individual groups as read also updates the last_read_at. Should be uncommented once it's fixed on the backend.
6397
+ // (lastReadAt &&
6398
+ // aggregatedActivity.updated_at.getTime() <= lastReadAt.getTime()) ||
6399
+ (readActivities ?? []).includes(group), [readActivities, group]);
6400
+ };
6401
+
6402
+ const useIsAggregatedActivitySeen = ({ feed: feedFromProps, aggregatedActivity, }) => {
6403
+ const feedFromContext = useFeedContext();
6404
+ const feed = feedFromProps ?? feedFromContext;
6405
+ const { seen_activities: seenActivities, last_seen_at: lastSeenAt } = useNotificationStatus(feed) ?? {};
6406
+ const group = aggregatedActivity.group;
6407
+ return react.useMemo(() => (lastSeenAt &&
6408
+ aggregatedActivity.updated_at.getTime() < lastSeenAt.getTime()) ||
6409
+ (seenActivities ?? []).includes(group), [lastSeenAt, aggregatedActivity.updated_at, seenActivities, group]);
6410
+ };
6411
+
6273
6412
  const StreamSearchResultsContext = react.createContext(undefined);
6274
6413
  /**
6275
6414
  * Hook to access the nearest SearchSource instance.
@@ -6386,7 +6525,7 @@ const StreamFeeds = ({ client, children }) => {
6386
6525
  };
6387
6526
  StreamFeeds.displayName = 'StreamFeeds';
6388
6527
 
6389
- const StreamFeed = ({ feed, children }) => {
6528
+ const StreamFeed = ({ feed, children, }) => {
6390
6529
  return (jsxRuntime.jsx(StreamFeedContext.Provider, { value: feed, children: children }));
6391
6530
  };
6392
6531
  StreamFeed.displayName = 'StreamFeed';
@@ -6409,6 +6548,7 @@ exports.StreamSearch = StreamSearch;
6409
6548
  exports.StreamSearchContext = StreamSearchContext;
6410
6549
  exports.StreamSearchResults = StreamSearchResults;
6411
6550
  exports.StreamSearchResultsContext = StreamSearchResultsContext;
6551
+ exports.useAggregatedActivities = useAggregatedActivities;
6412
6552
  exports.useBookmarkActions = useBookmarkActions;
6413
6553
  exports.useClientConnectedUser = useClientConnectedUser;
6414
6554
  exports.useComments = useComments;
@@ -6419,6 +6559,9 @@ exports.useFeedMetadata = useFeedMetadata;
6419
6559
  exports.useFeedsClient = useFeedsClient;
6420
6560
  exports.useFollowers = useFollowers;
6421
6561
  exports.useFollowing = useFollowing;
6562
+ exports.useIsAggregatedActivityRead = useIsAggregatedActivityRead;
6563
+ exports.useIsAggregatedActivitySeen = useIsAggregatedActivitySeen;
6564
+ exports.useNotificationStatus = useNotificationStatus;
6422
6565
  exports.useOwnCapabilities = useOwnCapabilities;
6423
6566
  exports.useOwnFollows = useOwnFollows;
6424
6567
  exports.useReactionActions = useReactionActions;