@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
@@ -415,6 +415,7 @@ decoders.Command = (input) => {
415
415
  decoders.CommentAddedEvent = (input) => {
416
416
  const typeMappings = {
417
417
  created_at: { type: 'DatetimeType', isSingle: true },
418
+ activity: { type: 'ActivityResponse', isSingle: true },
418
419
  comment: { type: 'CommentResponse', isSingle: true },
419
420
  received_at: { type: 'DatetimeType', isSingle: true },
420
421
  };
@@ -431,6 +432,7 @@ decoders.CommentDeletedEvent = (input) => {
431
432
  decoders.CommentReactionAddedEvent = (input) => {
432
433
  const typeMappings = {
433
434
  created_at: { type: 'DatetimeType', isSingle: true },
435
+ activity: { type: 'ActivityResponse', isSingle: true },
434
436
  comment: { type: 'CommentResponse', isSingle: true },
435
437
  reaction: { type: 'FeedsReactionResponse', isSingle: true },
436
438
  received_at: { type: 'DatetimeType', isSingle: true },
@@ -449,6 +451,7 @@ decoders.CommentReactionDeletedEvent = (input) => {
449
451
  decoders.CommentReactionUpdatedEvent = (input) => {
450
452
  const typeMappings = {
451
453
  created_at: { type: 'DatetimeType', isSingle: true },
454
+ activity: { type: 'ActivityResponse', isSingle: true },
452
455
  comment: { type: 'CommentResponse', isSingle: true },
453
456
  reaction: { type: 'FeedsReactionResponse', isSingle: true },
454
457
  received_at: { type: 'DatetimeType', isSingle: true },
@@ -811,6 +814,7 @@ decoders.Message = (input) => {
811
814
  pin_expires: { type: 'DatetimeType', isSingle: true },
812
815
  pinned_at: { type: 'DatetimeType', isSingle: true },
813
816
  thread_participants: { type: 'User', isSingle: false },
817
+ member: { type: 'ChannelMember', isSingle: true },
814
818
  pinned_by: { type: 'User', isSingle: true },
815
819
  poll: { type: 'Poll', isSingle: true },
816
820
  quoted_message: { type: 'Message', isSingle: true },
@@ -864,6 +868,8 @@ decoders.ModerationCustomActionEvent = (input) => {
864
868
  };
865
869
  decoders.ModerationFlagResponse = (input) => {
866
870
  const typeMappings = {
871
+ created_at: { type: 'DatetimeType', isSingle: true },
872
+ updated_at: { type: 'DatetimeType', isSingle: true },
867
873
  review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
868
874
  user: { type: 'UserResponse', isSingle: true },
869
875
  };
@@ -1630,6 +1636,7 @@ class FeedsApi {
1630
1636
  const body = {
1631
1637
  type: request?.type,
1632
1638
  create_notification_activity: request?.create_notification_activity,
1639
+ skip_push: request?.skip_push,
1633
1640
  custom: request?.custom,
1634
1641
  };
1635
1642
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/activities/{activity_id}/reactions', pathParams, undefined, body, 'application/json');
@@ -1776,6 +1783,7 @@ class FeedsApi {
1776
1783
  object_type: request?.object_type,
1777
1784
  create_notification_activity: request?.create_notification_activity,
1778
1785
  parent_id: request?.parent_id,
1786
+ skip_push: request?.skip_push,
1779
1787
  attachments: request?.attachments,
1780
1788
  mentioned_user_ids: request?.mentioned_user_ids,
1781
1789
  custom: request?.custom,
@@ -1829,6 +1837,7 @@ class FeedsApi {
1829
1837
  };
1830
1838
  const body = {
1831
1839
  comment: request?.comment,
1840
+ skip_push: request?.skip_push,
1832
1841
  custom: request?.custom,
1833
1842
  };
1834
1843
  const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/comments/{id}', pathParams, undefined, body, 'application/json');
@@ -1842,6 +1851,7 @@ class FeedsApi {
1842
1851
  const body = {
1843
1852
  type: request?.type,
1844
1853
  create_notification_activity: request?.create_notification_activity,
1854
+ skip_push: request?.skip_push,
1845
1855
  custom: request?.custom,
1846
1856
  };
1847
1857
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{id}/reactions', pathParams, undefined, body, 'application/json');
@@ -2082,6 +2092,7 @@ class FeedsApi {
2082
2092
  create_notification_activity: request?.create_notification_activity,
2083
2093
  follower_role: request?.follower_role,
2084
2094
  push_preference: request?.push_preference,
2095
+ skip_push: request?.skip_push,
2085
2096
  custom: request?.custom,
2086
2097
  };
2087
2098
  const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/follows', undefined, undefined, body, 'application/json');
@@ -2094,6 +2105,7 @@ class FeedsApi {
2094
2105
  target: request?.target,
2095
2106
  create_notification_activity: request?.create_notification_activity,
2096
2107
  push_preference: request?.push_preference,
2108
+ skip_push: request?.skip_push,
2097
2109
  custom: request?.custom,
2098
2110
  };
2099
2111
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/follows', undefined, undefined, body, 'application/json');
@@ -4520,20 +4532,29 @@ function handleCommentAdded(event) {
4520
4532
  function handleCommentDeleted({ comment }) {
4521
4533
  const entityId = comment.parent_id ?? comment.object_id;
4522
4534
  this.state.next((currentState) => {
4523
- const newCommentsByEntityId = {
4524
- ...currentState.comments_by_entity_id,
4525
- [entityId]: {
4526
- ...currentState.comments_by_entity_id[entityId],
4527
- },
4528
- };
4535
+ let newCommentsByEntityId;
4529
4536
  const index = this.getCommentIndex(comment, currentState);
4530
- if (newCommentsByEntityId?.[entityId]?.comments?.length && index !== -1) {
4537
+ if (index !== -1) {
4538
+ newCommentsByEntityId ?? (newCommentsByEntityId = {
4539
+ ...currentState.comments_by_entity_id,
4540
+ [entityId]: {
4541
+ ...currentState.comments_by_entity_id[entityId],
4542
+ },
4543
+ });
4531
4544
  newCommentsByEntityId[entityId].comments = [
4532
4545
  ...newCommentsByEntityId[entityId].comments,
4533
4546
  ];
4534
4547
  newCommentsByEntityId[entityId]?.comments?.splice(index, 1);
4535
4548
  }
4536
- delete newCommentsByEntityId[comment.id];
4549
+ if (typeof currentState.comments_by_entity_id[comment.id] !== 'undefined') {
4550
+ newCommentsByEntityId ?? (newCommentsByEntityId = {
4551
+ ...currentState.comments_by_entity_id,
4552
+ });
4553
+ delete newCommentsByEntityId[comment.id];
4554
+ }
4555
+ if (!newCommentsByEntityId) {
4556
+ return currentState;
4557
+ }
4537
4558
  return {
4538
4559
  ...currentState,
4539
4560
  comments_by_entity_id: newCommentsByEntityId,
@@ -4654,14 +4675,20 @@ function handleFeedMemberUpdated(event) {
4654
4675
  function handleFeedMemberRemoved(event) {
4655
4676
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
4656
4677
  this.state.next((currentState) => {
4657
- const newState = {
4658
- ...currentState,
4659
- members: currentState.members?.filter((member) => member.user.id !== event.user?.id),
4660
- };
4661
- if (connectedUser?.id === event.member_id) {
4678
+ let newState;
4679
+ if (typeof currentState.members !== 'undefined') {
4680
+ const filtered = currentState.members.filter((member) => member.user.id !== event.member_id);
4681
+ if (filtered.length !== currentState.members.length) {
4682
+ newState ?? (newState = { ...currentState });
4683
+ newState.members = filtered;
4684
+ }
4685
+ }
4686
+ if (connectedUser?.id === event.member_id &&
4687
+ typeof currentState.own_membership !== 'undefined') {
4688
+ newState ?? (newState = { ...currentState });
4662
4689
  delete newState.own_membership;
4663
4690
  }
4664
- return newState;
4691
+ return newState ?? currentState;
4665
4692
  });
4666
4693
  }
4667
4694
 
@@ -4898,7 +4925,7 @@ const removePinnedActivityFromState = (activityResponse, pinnedActivities) => {
4898
4925
  if (index !== -1) {
4899
4926
  const newActivities = [...pinnedActivities];
4900
4927
  newActivities.splice(index, 1);
4901
- return { changed: true, activities: newActivities };
4928
+ return { changed: true, pinned_activities: newActivities };
4902
4929
  }
4903
4930
  else {
4904
4931
  return { changed: false, pinned_activities: pinnedActivities };
@@ -5088,13 +5115,80 @@ function handleActivityReactionDeleted(event) {
5088
5115
  }
5089
5116
  }
5090
5117
 
5118
+ const updateNotificationStatusFromActivityMarked = (event, currentNotificationStatus, aggregatedActivities = []) => {
5119
+ if (!currentNotificationStatus) {
5120
+ return {
5121
+ changed: false,
5122
+ };
5123
+ }
5124
+ const newState = {
5125
+ ...currentNotificationStatus,
5126
+ };
5127
+ if (event.mark_all_read) {
5128
+ const allGroupIds = aggregatedActivities.map((activity) => activity.group);
5129
+ newState.read_activities = [
5130
+ ...new Set([
5131
+ ...(currentNotificationStatus.read_activities ?? []),
5132
+ ...allGroupIds,
5133
+ ]),
5134
+ ];
5135
+ }
5136
+ if (event.mark_read && event.mark_read.length > 0) {
5137
+ newState.read_activities = [
5138
+ ...new Set([
5139
+ ...(currentNotificationStatus?.read_activities ?? []),
5140
+ ...event.mark_read,
5141
+ ]),
5142
+ ];
5143
+ }
5144
+ if (event.mark_all_seen) {
5145
+ newState.last_seen_at = new Date();
5146
+ }
5147
+ return {
5148
+ changed: true,
5149
+ data: { notification_status: newState },
5150
+ };
5151
+ };
5152
+ function handleActivityMarked(event) {
5153
+ const result = updateNotificationStatusFromActivityMarked(event, this.currentState.notification_status, this.currentState.aggregated_activities);
5154
+ if (result.changed) {
5155
+ this.state.partialNext({
5156
+ notification_status: result.data?.notification_status,
5157
+ });
5158
+ }
5159
+ }
5160
+
5091
5161
  function handleFeedUpdated(event) {
5092
5162
  this.state.partialNext({ ...event.feed });
5093
5163
  }
5094
5164
 
5165
+ const updateNotificationFeedFromEvent = (event) => {
5166
+ const updates = {};
5167
+ if (event.notification_status) {
5168
+ updates.notification_status = event.notification_status;
5169
+ }
5170
+ if (event.aggregated_activities) {
5171
+ updates.aggregated_activities = event.aggregated_activities;
5172
+ }
5173
+ // Only return changed if we have actual updates
5174
+ if (Object.keys(updates).length > 0) {
5175
+ return {
5176
+ changed: true,
5177
+ data: updates,
5178
+ };
5179
+ }
5180
+ return {
5181
+ changed: false,
5182
+ };
5183
+ };
5095
5184
  function handleNotificationFeedUpdated(event) {
5096
- console.info('notification feed updated', event);
5097
- // TODO: handle notification feed updates
5185
+ const result = updateNotificationFeedFromEvent(event);
5186
+ if (result.changed) {
5187
+ this.state.partialNext({
5188
+ notification_status: result.data?.notification_status,
5189
+ aggregated_activities: result.data?.aggregated_activities,
5190
+ });
5191
+ }
5098
5192
  }
5099
5193
 
5100
5194
  function handleWatchStarted() {
@@ -5149,7 +5243,7 @@ class Feed extends FeedApi {
5149
5243
  'feeds.poll.vote_removed': Feed.noop,
5150
5244
  'feeds.activity.pinned': Feed.noop,
5151
5245
  'feeds.activity.unpinned': Feed.noop,
5152
- 'feeds.activity.marked': Feed.noop,
5246
+ 'feeds.activity.marked': handleActivityMarked.bind(this),
5153
5247
  'moderation.custom_action': Feed.noop,
5154
5248
  'moderation.flagged': Feed.noop,
5155
5249
  'moderation.mark_reviewed': Feed.noop,
@@ -6035,16 +6129,120 @@ const FeedOwnCapability = {
6035
6129
  UPDATE_FEED_MEMBERS: 'update-feed-members',
6036
6130
  };
6037
6131
 
6132
+ class SearchController {
6133
+ constructor({ config, sources } = {}) {
6134
+ this.addSource = (source) => {
6135
+ this.state.partialNext({
6136
+ sources: [...this.sources, source],
6137
+ });
6138
+ };
6139
+ this.getSource = (sourceType) => this.sources.find((s) => s.type === sourceType);
6140
+ this.removeSource = (sourceType) => {
6141
+ const newSources = this.sources.filter((s) => s.type !== sourceType);
6142
+ if (newSources.length === this.sources.length)
6143
+ return;
6144
+ this.state.partialNext({ sources: newSources });
6145
+ };
6146
+ this.activateSource = (sourceType) => {
6147
+ const source = this.getSource(sourceType);
6148
+ if (!source || source.isActive)
6149
+ return;
6150
+ if (this.config.keepSingleActiveSource) {
6151
+ this.sources.forEach((s) => {
6152
+ if (s.type !== sourceType) {
6153
+ s.deactivate();
6154
+ }
6155
+ });
6156
+ }
6157
+ source.activate();
6158
+ this.state.partialNext({ sources: [...this.sources] });
6159
+ };
6160
+ this.deactivateSource = (sourceType) => {
6161
+ const source = this.getSource(sourceType);
6162
+ if (!source?.isActive)
6163
+ return;
6164
+ if (this.activeSources.length === 1)
6165
+ return;
6166
+ source.deactivate();
6167
+ this.state.partialNext({ sources: [...this.sources] });
6168
+ };
6169
+ this.activate = () => {
6170
+ if (!this.activeSources.length) {
6171
+ const sourcesToActivate = this.config.keepSingleActiveSource
6172
+ ? this.sources.slice(0, 1)
6173
+ : this.sources;
6174
+ sourcesToActivate.forEach((s) => s.activate());
6175
+ }
6176
+ if (this.isActive)
6177
+ return;
6178
+ this.state.partialNext({ isActive: true });
6179
+ };
6180
+ this.search = async (searchQuery) => {
6181
+ const searchedSources = this.activeSources;
6182
+ this.state.partialNext({
6183
+ searchQuery,
6184
+ });
6185
+ await Promise.all(searchedSources.map((source) => source.search(searchQuery)));
6186
+ };
6187
+ this.cancelSearchQueries = () => {
6188
+ this.activeSources.forEach((s) => s.cancelScheduledQuery());
6189
+ };
6190
+ this.clear = () => {
6191
+ this.cancelSearchQueries();
6192
+ this.sources.forEach((source) => source.state.next({ ...source.initialState, isActive: source.isActive }));
6193
+ this.state.next((current) => ({
6194
+ ...current,
6195
+ isActive: true,
6196
+ queriesInProgress: [],
6197
+ searchQuery: '',
6198
+ }));
6199
+ };
6200
+ this.exit = () => {
6201
+ this.cancelSearchQueries();
6202
+ this.sources.forEach((source) => source.state.next({ ...source.initialState, isActive: source.isActive }));
6203
+ this.state.next((current) => ({
6204
+ ...current,
6205
+ isActive: false,
6206
+ queriesInProgress: [],
6207
+ searchQuery: '',
6208
+ }));
6209
+ };
6210
+ this.state = new StateStore({
6211
+ isActive: false,
6212
+ searchQuery: '',
6213
+ sources: sources ?? [],
6214
+ });
6215
+ this._internalState = new StateStore({});
6216
+ this.config = { keepSingleActiveSource: true, ...config };
6217
+ }
6218
+ get hasNext() {
6219
+ return this.sources.some((source) => source.hasNext);
6220
+ }
6221
+ get sources() {
6222
+ return this.state.getLatestValue().sources;
6223
+ }
6224
+ get activeSources() {
6225
+ return this.state.getLatestValue().sources.filter((s) => s.isActive);
6226
+ }
6227
+ get isActive() {
6228
+ return this.state.getLatestValue().isActive;
6229
+ }
6230
+ get searchQuery() {
6231
+ return this.state.getLatestValue().searchQuery;
6232
+ }
6233
+ get searchSourceTypes() {
6234
+ return this.sources.map((s) => s.type);
6235
+ }
6236
+ }
6237
+
6038
6238
  const DEFAULT_SEARCH_SOURCE_OPTIONS = {
6039
6239
  debounceMs: 300,
6040
6240
  pageSize: 10,
6041
6241
  allowEmptySearchString: false,
6242
+ resetOnNewSearchQuery: true,
6042
6243
  };
6043
- class BaseSearchSource {
6244
+ class BaseSearchSourceBase {
6044
6245
  constructor(options) {
6045
- this.setDebounceOptions = ({ debounceMs }) => {
6046
- this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6047
- };
6048
6246
  this.activate = () => {
6049
6247
  if (this.isActive)
6050
6248
  return;
@@ -6063,15 +6261,11 @@ class BaseSearchSource {
6063
6261
  (this.hasNext || hasNewSearchQuery) &&
6064
6262
  (this.allowEmptySearchString || searchString));
6065
6263
  };
6066
- this.search = (searchQuery) => this.searchDebounced(searchQuery);
6067
- const { debounceMs, pageSize, allowEmptySearchString } = {
6068
- ...DEFAULT_SEARCH_SOURCE_OPTIONS,
6069
- ...options,
6070
- };
6264
+ const { pageSize, allowEmptySearchString, resetOnNewSearchQuery } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6071
6265
  this.pageSize = pageSize;
6072
6266
  this.allowEmptySearchString = allowEmptySearchString;
6267
+ this.resetOnNewSearchQuery = resetOnNewSearchQuery;
6073
6268
  this.state = new StateStore(this.initialState);
6074
- this.setDebounceOptions({ debounceMs });
6075
6269
  }
6076
6270
  get lastQueryError() {
6077
6271
  return this.state.getLatestValue().lastQueryError;
@@ -6113,10 +6307,14 @@ class BaseSearchSource {
6113
6307
  return this.state.getLatestValue().searchQuery;
6114
6308
  }
6115
6309
  getStateBeforeFirstQuery(newSearchString) {
6310
+ const initialState = this.initialState;
6311
+ const oldItems = this.items;
6312
+ const items = this.resetOnNewSearchQuery ? initialState.items : oldItems;
6116
6313
  return {
6117
6314
  ...this.initialState,
6315
+ items,
6118
6316
  isActive: this.isActive,
6119
- isLoading: true,
6317
+ isLoading: this.resetOnNewSearchQuery ? true : !oldItems,
6120
6318
  searchQuery: newSearchString,
6121
6319
  };
6122
6320
  }
@@ -6129,12 +6327,10 @@ class BaseSearchSource {
6129
6327
  isLoading: false,
6130
6328
  items: isFirstPage
6131
6329
  ? stateUpdate.items
6132
- : [...(this.items ?? []), ...(stateUpdate.items ?? [])],
6330
+ : [...(this.items ?? []), ...(stateUpdate.items || [])],
6133
6331
  };
6134
6332
  }
6135
- async executeQuery(newSearchString) {
6136
- if (!this.canExecuteQuery(newSearchString))
6137
- return;
6333
+ prepareStateForQuery(newSearchString) {
6138
6334
  const hasNewSearchQuery = typeof newSearchString !== 'undefined';
6139
6335
  const searchString = newSearchString ?? this.searchQuery;
6140
6336
  if (hasNewSearchQuery) {
@@ -6143,20 +6339,50 @@ class BaseSearchSource {
6143
6339
  else {
6144
6340
  this.state.partialNext({ isLoading: true });
6145
6341
  }
6342
+ return { searchString, hasNewSearchQuery };
6343
+ }
6344
+ updatePaginationStateFromQuery(result) {
6345
+ const { items, next } = result;
6146
6346
  const stateUpdate = {};
6347
+ if (next || next === null) {
6348
+ stateUpdate.next = next;
6349
+ stateUpdate.hasNext = !!next;
6350
+ }
6351
+ else {
6352
+ stateUpdate.offset = (this.offset ?? 0) + items.length;
6353
+ stateUpdate.hasNext = items.length === this.pageSize;
6354
+ }
6355
+ return stateUpdate;
6356
+ }
6357
+ resetState() {
6358
+ this.state.next(this.initialState);
6359
+ }
6360
+ resetStateAndActivate() {
6361
+ this.resetState();
6362
+ this.activate();
6363
+ }
6364
+ }
6365
+ class BaseSearchSource extends BaseSearchSourceBase {
6366
+ constructor(options) {
6367
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6368
+ super(options);
6369
+ this.setDebounceOptions = ({ debounceMs }) => {
6370
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6371
+ };
6372
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
6373
+ this.setDebounceOptions({ debounceMs });
6374
+ }
6375
+ async executeQuery(newSearchString) {
6376
+ if (!this.canExecuteQuery(newSearchString))
6377
+ return;
6378
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
6379
+ let stateUpdate = {};
6147
6380
  try {
6148
6381
  const results = await this.query(searchString);
6149
6382
  if (!results)
6150
6383
  return;
6151
- const { items, next } = results;
6152
- if (typeof next === 'string' || next === null) {
6153
- stateUpdate.next = next;
6154
- stateUpdate.hasNext = !!next;
6155
- }
6156
- else {
6157
- stateUpdate.offset = (this.offset ?? 0) + items.length;
6158
- stateUpdate.hasNext = items.length === this.pageSize;
6159
- }
6384
+ const { items } = results;
6385
+ stateUpdate = this.updatePaginationStateFromQuery(results);
6160
6386
  stateUpdate.items = await this.filterQueryResults(items);
6161
6387
  }
6162
6388
  catch (e) {
@@ -6169,118 +6395,39 @@ class BaseSearchSource {
6169
6395
  cancelScheduledQuery() {
6170
6396
  this.searchDebounced.cancel();
6171
6397
  }
6172
- resetState() {
6173
- this.state.next(this.initialState);
6174
- }
6175
- resetStateAndActivate() {
6176
- this.resetState();
6177
- this.activate();
6178
- }
6179
6398
  }
6180
-
6181
- class SearchController {
6182
- constructor({ config, sources } = {}) {
6183
- this.addSource = (source) => {
6184
- this.state.partialNext({
6185
- sources: [...this.sources, source],
6186
- });
6187
- };
6188
- this.getSource = (sourceType) => this.sources.find((s) => s.type === sourceType);
6189
- this.removeSource = (sourceType) => {
6190
- const newSources = this.sources.filter((s) => s.type !== sourceType);
6191
- if (newSources.length === this.sources.length)
6192
- return;
6193
- this.state.partialNext({ sources: newSources });
6194
- };
6195
- this.activateSource = (sourceType) => {
6196
- const source = this.getSource(sourceType);
6197
- if (!source || source.isActive)
6198
- return;
6199
- if (this.config.keepSingleActiveSource) {
6200
- this.sources.forEach((s) => {
6201
- if (s.type !== sourceType) {
6202
- s.deactivate();
6203
- }
6204
- });
6205
- }
6206
- source.activate();
6207
- this.state.partialNext({ sources: [...this.sources] });
6208
- };
6209
- this.deactivateSource = (sourceType) => {
6210
- const source = this.getSource(sourceType);
6211
- if (!source?.isActive)
6212
- return;
6213
- if (this.activeSources.length === 1)
6214
- return;
6215
- source.deactivate();
6216
- this.state.partialNext({ sources: [...this.sources] });
6217
- };
6218
- this.activate = () => {
6219
- if (!this.activeSources.length) {
6220
- const sourcesToActivate = this.config.keepSingleActiveSource
6221
- ? this.sources.slice(0, 1)
6222
- : this.sources;
6223
- sourcesToActivate.forEach((s) => s.activate());
6224
- }
6225
- if (this.isActive)
6226
- return;
6227
- this.state.partialNext({ isActive: true });
6228
- };
6229
- this.search = async (searchQuery) => {
6230
- const searchedSources = this.activeSources;
6231
- this.state.partialNext({
6232
- searchQuery,
6233
- });
6234
- await Promise.all(searchedSources.map((source) => source.search(searchQuery)));
6235
- };
6236
- this.cancelSearchQueries = () => {
6237
- this.activeSources.forEach((s) => s.cancelScheduledQuery());
6238
- };
6239
- this.clear = () => {
6240
- this.cancelSearchQueries();
6241
- this.sources.forEach((source) => source.state.next({ ...source.initialState, isActive: source.isActive }));
6242
- this.state.next((current) => ({
6243
- ...current,
6244
- isActive: true,
6245
- queriesInProgress: [],
6246
- searchQuery: '',
6247
- }));
6248
- };
6249
- this.exit = () => {
6250
- this.cancelSearchQueries();
6251
- this.sources.forEach((source) => source.state.next({ ...source.initialState, isActive: source.isActive }));
6252
- this.state.next((current) => ({
6253
- ...current,
6254
- isActive: false,
6255
- queriesInProgress: [],
6256
- searchQuery: '',
6257
- }));
6399
+ class BaseSearchSourceSync extends BaseSearchSourceBase {
6400
+ constructor(options) {
6401
+ const { debounceMs } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
6402
+ super(options);
6403
+ this.setDebounceOptions = ({ debounceMs }) => {
6404
+ this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
6258
6405
  };
6259
- this.state = new StateStore({
6260
- isActive: false,
6261
- searchQuery: '',
6262
- sources: sources ?? [],
6263
- });
6264
- this._internalState = new StateStore({});
6265
- this.config = { keepSingleActiveSource: true, ...config };
6266
- }
6267
- get hasNext() {
6268
- return this.sources.some((source) => source.hasNext);
6269
- }
6270
- get sources() {
6271
- return this.state.getLatestValue().sources;
6272
- }
6273
- get activeSources() {
6274
- return this.state.getLatestValue().sources.filter((s) => s.isActive);
6275
- }
6276
- get isActive() {
6277
- return this.state.getLatestValue().isActive;
6406
+ this.search = (searchQuery) => this.searchDebounced(searchQuery);
6407
+ this.setDebounceOptions({ debounceMs });
6278
6408
  }
6279
- get searchQuery() {
6280
- return this.state.getLatestValue().searchQuery;
6409
+ executeQuery(newSearchString) {
6410
+ if (!this.canExecuteQuery(newSearchString))
6411
+ return;
6412
+ const { hasNewSearchQuery, searchString } = this.prepareStateForQuery(newSearchString);
6413
+ let stateUpdate = {};
6414
+ try {
6415
+ const results = this.query(searchString);
6416
+ if (!results)
6417
+ return;
6418
+ const { items } = results;
6419
+ stateUpdate = this.updatePaginationStateFromQuery(results);
6420
+ stateUpdate.items = this.filterQueryResults(items);
6421
+ }
6422
+ catch (e) {
6423
+ stateUpdate.lastQueryError = e;
6424
+ }
6425
+ finally {
6426
+ this.state.next(this.getStateAfterQuery(stateUpdate, hasNewSearchQuery));
6427
+ }
6281
6428
  }
6282
- get searchSourceTypes() {
6283
- return this.sources.map((s) => s.type);
6429
+ cancelScheduledQuery() {
6430
+ this.searchDebounced.cancel();
6284
6431
  }
6285
6432
  }
6286
6433
 
@@ -6309,36 +6456,6 @@ class ActivitySearchSource extends BaseSearchSource {
6309
6456
  }
6310
6457
  }
6311
6458
 
6312
- class UserSearchSource extends BaseSearchSource {
6313
- constructor(client, options) {
6314
- super(options);
6315
- this.type = 'user';
6316
- this.client = client;
6317
- }
6318
- async query(searchQuery) {
6319
- const { connected_user: connectedUser } = this.client.state.getLatestValue();
6320
- if (!connectedUser)
6321
- return { items: [] };
6322
- const { users: items } = await this.client.queryUsers({
6323
- payload: {
6324
- filter_conditions: {
6325
- ...(!this.allowEmptySearchString || searchQuery.length > 0
6326
- ? {
6327
- name: {
6328
- $autocomplete: searchQuery,
6329
- },
6330
- }
6331
- : {}),
6332
- },
6333
- },
6334
- });
6335
- return { items, next: undefined };
6336
- }
6337
- filterQueryResults(items) {
6338
- return items;
6339
- }
6340
- }
6341
-
6342
6459
  class FeedSearchSource extends BaseSearchSource {
6343
6460
  constructor(client, options) {
6344
6461
  super(options);
@@ -6372,8 +6489,39 @@ class FeedSearchSource extends BaseSearchSource {
6372
6489
  }
6373
6490
  }
6374
6491
 
6492
+ class UserSearchSource extends BaseSearchSource {
6493
+ constructor(client, options) {
6494
+ super(options);
6495
+ this.type = 'user';
6496
+ this.client = client;
6497
+ }
6498
+ async query(searchQuery) {
6499
+ const { connected_user: connectedUser } = this.client.state.getLatestValue();
6500
+ if (!connectedUser)
6501
+ return { items: [] };
6502
+ const { users: items } = await this.client.queryUsers({
6503
+ payload: {
6504
+ filter_conditions: {
6505
+ ...(!this.allowEmptySearchString || searchQuery.length > 0
6506
+ ? {
6507
+ name: {
6508
+ $autocomplete: searchQuery,
6509
+ },
6510
+ }
6511
+ : {}),
6512
+ },
6513
+ },
6514
+ });
6515
+ return { items, next: undefined };
6516
+ }
6517
+ filterQueryResults(items) {
6518
+ return items;
6519
+ }
6520
+ }
6521
+
6375
6522
  exports.ActivitySearchSource = ActivitySearchSource;
6376
6523
  exports.BaseSearchSource = BaseSearchSource;
6524
+ exports.BaseSearchSourceSync = BaseSearchSourceSync;
6377
6525
  exports.ChannelOwnCapability = ChannelOwnCapability;
6378
6526
  exports.Constants = Constants;
6379
6527
  exports.Feed = Feed;