@stream-io/feeds-client 0.1.8 → 0.1.10

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 (59) hide show
  1. package/@react-bindings/hooks/search-state-hooks/index.ts +3 -0
  2. package/@react-bindings/hooks/util/index.ts +1 -0
  3. package/@react-bindings/index.ts +5 -0
  4. package/CHANGELOG.md +22 -0
  5. package/dist/@react-bindings/contexts/StreamSearchContext.d.ts +12 -0
  6. package/dist/@react-bindings/contexts/StreamSearchResultsContext.d.ts +12 -0
  7. package/dist/@react-bindings/hooks/search-state-hooks/index.d.ts +3 -0
  8. package/dist/@react-bindings/hooks/search-state-hooks/useSearchQuery.d.ts +4 -0
  9. package/dist/@react-bindings/hooks/search-state-hooks/useSearchResult.d.ts +8 -0
  10. package/dist/@react-bindings/hooks/search-state-hooks/useSearchSources.d.ts +4 -0
  11. package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
  12. package/dist/@react-bindings/hooks/util/useBookmarkActions.d.ts +13 -0
  13. package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +1 -1
  14. package/dist/@react-bindings/index.d.ts +5 -0
  15. package/dist/@react-bindings/wrappers/StreamSearch.d.ts +12 -0
  16. package/dist/@react-bindings/wrappers/StreamSearchResults.d.ts +12 -0
  17. package/dist/index-react-bindings.browser.cjs +431 -156
  18. package/dist/index-react-bindings.browser.cjs.map +1 -1
  19. package/dist/index-react-bindings.browser.js +422 -157
  20. package/dist/index-react-bindings.browser.js.map +1 -1
  21. package/dist/index-react-bindings.node.cjs +431 -156
  22. package/dist/index-react-bindings.node.cjs.map +1 -1
  23. package/dist/index-react-bindings.node.js +422 -157
  24. package/dist/index-react-bindings.node.js.map +1 -1
  25. package/dist/index.browser.cjs +346 -264
  26. package/dist/index.browser.cjs.map +1 -1
  27. package/dist/index.browser.js +346 -265
  28. package/dist/index.browser.js.map +1 -1
  29. package/dist/index.node.cjs +346 -264
  30. package/dist/index.node.cjs.map +1 -1
  31. package/dist/index.node.js +346 -265
  32. package/dist/index.node.js.map +1 -1
  33. package/dist/src/Feed.d.ts +40 -9
  34. package/dist/src/FeedsClient.d.ts +8 -1
  35. package/dist/src/common/BaseSearchSource.d.ts +3 -1
  36. package/dist/src/common/FeedSearchSource.d.ts +5 -1
  37. package/dist/src/common/SearchController.d.ts +2 -0
  38. package/dist/src/gen-imports.d.ts +1 -1
  39. package/dist/src/state-updates/follow-utils.d.ts +19 -0
  40. package/dist/src/state-updates/state-update-queue.d.ts +15 -0
  41. package/dist/src/utils.d.ts +1 -0
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/src/Feed.ts +226 -192
  45. package/src/FeedsClient.ts +75 -3
  46. package/src/common/ActivitySearchSource.ts +5 -15
  47. package/src/common/BaseSearchSource.ts +9 -9
  48. package/src/common/FeedSearchSource.ts +20 -65
  49. package/src/common/SearchController.ts +2 -0
  50. package/src/common/UserSearchSource.ts +9 -61
  51. package/src/gen-imports.ts +1 -1
  52. package/src/state-updates/activity-reaction-utils.test.ts +1 -0
  53. package/src/state-updates/activity-utils.test.ts +1 -0
  54. package/src/state-updates/follow-utils.test.ts +552 -0
  55. package/src/state-updates/follow-utils.ts +126 -0
  56. package/src/state-updates/state-update-queue.test.ts +53 -0
  57. package/src/state-updates/state-update-queue.ts +35 -0
  58. package/src/utils.test.ts +175 -0
  59. package/src/utils.ts +20 -0
@@ -4058,6 +4058,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4058
4058
  return updateActivityInActivities(updatedActivity, activities);
4059
4059
  };
4060
4060
 
4061
+ const isFeedResponse = (follow) => {
4062
+ return 'created_by' in follow;
4063
+ };
4064
+ const handleFollowCreated = (follow, currentState, currentFeedId, connectedUserId) => {
4065
+ // filter non-accepted follows (the way getOrCreate does by default)
4066
+ if (follow.status !== 'accepted') {
4067
+ return { changed: false, data: currentState };
4068
+ }
4069
+ let newState = { ...currentState };
4070
+ // this feed followed someone
4071
+ if (follow.source_feed.fid === currentFeedId) {
4072
+ newState = {
4073
+ ...newState,
4074
+ // Update FeedResponse fields, that has the new follower/following count
4075
+ ...follow.source_feed,
4076
+ };
4077
+ // Only update if following array already exists
4078
+ if (currentState.following !== undefined) {
4079
+ newState.following = [follow, ...currentState.following];
4080
+ }
4081
+ }
4082
+ else if (
4083
+ // someone followed this feed
4084
+ follow.target_feed.fid === currentFeedId) {
4085
+ const source = follow.source_feed;
4086
+ newState = {
4087
+ ...newState,
4088
+ // Update FeedResponse fields, that has the new follower/following count
4089
+ ...follow.target_feed,
4090
+ };
4091
+ if (source.created_by.id === connectedUserId) {
4092
+ newState.own_follows = currentState.own_follows
4093
+ ? currentState.own_follows.concat(follow)
4094
+ : [follow];
4095
+ }
4096
+ // Only update if followers array already exists
4097
+ if (currentState.followers !== undefined) {
4098
+ newState.followers = [follow, ...currentState.followers];
4099
+ }
4100
+ }
4101
+ return { changed: true, data: newState };
4102
+ };
4103
+ const handleFollowDeleted = (follow, currentState, currentFeedId, connectedUserId) => {
4104
+ let newState = { ...currentState };
4105
+ // this feed unfollowed someone
4106
+ if (follow.source_feed.fid === currentFeedId) {
4107
+ newState = {
4108
+ ...newState,
4109
+ // Update FeedResponse fields, that has the new follower/following count
4110
+ ...follow.source_feed,
4111
+ };
4112
+ // Only update if following array already exists
4113
+ if (currentState.following !== undefined) {
4114
+ newState.following = currentState.following.filter((followItem) => followItem.target_feed.fid !== follow.target_feed.fid);
4115
+ }
4116
+ }
4117
+ else if (
4118
+ // someone unfollowed this feed
4119
+ follow.target_feed.fid === currentFeedId) {
4120
+ const source = follow.source_feed;
4121
+ newState = {
4122
+ ...newState,
4123
+ // Update FeedResponse fields, that has the new follower/following count
4124
+ ...follow.target_feed,
4125
+ };
4126
+ if (isFeedResponse(source) &&
4127
+ source.created_by.id === connectedUserId &&
4128
+ currentState.own_follows !== undefined) {
4129
+ newState.own_follows = currentState.own_follows.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
4130
+ }
4131
+ // Only update if followers array already exists
4132
+ if (currentState.followers !== undefined) {
4133
+ newState.followers = currentState.followers.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
4134
+ }
4135
+ }
4136
+ return { changed: true, data: newState };
4137
+ };
4138
+ const handleFollowUpdated = (currentState) => {
4139
+ // For now, we'll treat follow updates as no-ops since the current implementation does
4140
+ // This can be enhanced later if needed
4141
+ return { changed: false, data: currentState };
4142
+ };
4143
+
4061
4144
  const isImageFile = (file) => {
4062
4145
  // photoshop files begin with 'image/'
4063
4146
  return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
@@ -4073,11 +4156,41 @@ const isCommentResponse = (entity) => {
4073
4156
  const Constants = {
4074
4157
  DEFAULT_COMMENT_PAGINATION: 'first',
4075
4158
  };
4159
+ const uniqueArrayMerge = (existingArray, arrayToMerge, getKey) => {
4160
+ const existing = new Set();
4161
+ existingArray.forEach((value) => {
4162
+ const key = getKey(value);
4163
+ existing.add(key);
4164
+ });
4165
+ const filteredArrayToMerge = arrayToMerge.filter((value) => {
4166
+ const key = getKey(value);
4167
+ return !existing.has(key);
4168
+ });
4169
+ return existingArray.concat(filteredArrayToMerge);
4170
+ };
4171
+
4172
+ const shouldUpdateState = ({ stateUpdateId, stateUpdateQueue, watch, }) => {
4173
+ if (!watch) {
4174
+ return true;
4175
+ }
4176
+ if (watch && stateUpdateQueue.has(stateUpdateId)) {
4177
+ stateUpdateQueue.delete(stateUpdateId);
4178
+ return false;
4179
+ }
4180
+ stateUpdateQueue.add(stateUpdateId);
4181
+ return true;
4182
+ };
4183
+ const getStateUpdateQueueIdForFollow = (follow) => {
4184
+ return `follow${follow.source_feed.fid}-${follow.target_feed.fid}`;
4185
+ };
4186
+ const getStateUpdateQueueIdForUnfollow = (follow) => {
4187
+ return `unfollow${follow.source_feed.fid}-${follow.target_feed.fid}`;
4188
+ };
4076
4189
 
4077
4190
  class Feed extends FeedApi {
4078
- constructor(client, groupId, id, data) {
4079
- // Need this ugly cast because fileUpload endpoints :(
4191
+ constructor(client, groupId, id, data, watch = false) {
4080
4192
  super(client, groupId, id);
4193
+ this.stateUpdateQueue = new Set();
4081
4194
  this.eventHandlers = {
4082
4195
  'feeds.activity.added': (event) => {
4083
4196
  const currentActivities = this.currentState.activities;
@@ -4223,74 +4336,14 @@ class Feed extends FeedApi {
4223
4336
  'feeds.feed_group.changed': Feed.noop,
4224
4337
  'feeds.feed_group.deleted': Feed.noop,
4225
4338
  'feeds.follow.created': (event) => {
4226
- // filter non-accepted follows (the way getOrCreate does by default)
4227
- if (event.follow.status !== 'accepted')
4228
- return;
4229
- // this feed followed someone
4230
- if (event.follow.source_feed.fid === this.fid) {
4231
- this.state.next((currentState) => {
4232
- const newState = {
4233
- ...currentState,
4234
- ...event.follow.source_feed,
4235
- };
4236
- if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
4237
- // TODO: respect sort
4238
- newState.following = currentState.following
4239
- ? currentState.following.concat(event.follow)
4240
- : [event.follow];
4241
- }
4242
- return newState;
4243
- });
4244
- }
4245
- else if (
4246
- // someone followed this feed
4247
- event.follow.target_feed.fid === this.fid) {
4248
- const source = event.follow.source_feed;
4249
- const connectedUser = this.client.state.getLatestValue().connected_user;
4250
- this.state.next((currentState) => {
4251
- const newState = { ...currentState, ...event.follow.target_feed };
4252
- if (source.created_by.id === connectedUser?.id) {
4253
- newState.own_follows = currentState.own_follows
4254
- ? currentState.own_follows.concat(event.follow)
4255
- : [event.follow];
4256
- }
4257
- if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
4258
- // TODO: respect sort
4259
- newState.followers = currentState.followers
4260
- ? currentState.followers.concat(event.follow)
4261
- : [event.follow];
4262
- }
4263
- return newState;
4264
- });
4265
- }
4339
+ this.handleFollowCreated(event.follow);
4266
4340
  },
4267
4341
  'feeds.follow.deleted': (event) => {
4268
- // this feed unfollowed someone
4269
- if (event.follow.source_feed.fid === this.fid) {
4270
- this.state.next((currentState) => {
4271
- return {
4272
- ...currentState,
4273
- ...event.follow.source_feed,
4274
- following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
4275
- };
4276
- });
4277
- }
4278
- else if (
4279
- // someone unfollowed this feed
4280
- event.follow.target_feed.fid === this.fid) {
4281
- const source = event.follow.source_feed;
4282
- const connectedUser = this.client.state.getLatestValue().connected_user;
4283
- this.state.next((currentState) => {
4284
- const newState = { ...currentState, ...event.follow.target_feed };
4285
- if (source.created_by.id === connectedUser?.id) {
4286
- newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4287
- }
4288
- newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4289
- return newState;
4290
- });
4291
- }
4342
+ this.handleFollowDeleted(event.follow);
4343
+ },
4344
+ 'feeds.follow.updated': (_event) => {
4345
+ handleFollowUpdated(this.currentState);
4292
4346
  },
4293
- 'feeds.follow.updated': Feed.noop,
4294
4347
  'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
4295
4348
  'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4296
4349
  'feeds.comment.reaction.updated': Feed.noop,
@@ -4298,13 +4351,11 @@ class Feed extends FeedApi {
4298
4351
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
4299
4352
  this.state.next((currentState) => {
4300
4353
  let newState;
4301
- if (!checkHasAnotherPage(currentState.members, currentState.member_pagination?.next)) {
4354
+ if (typeof currentState.members !== 'undefined') {
4302
4355
  newState ?? (newState = {
4303
4356
  ...currentState,
4304
4357
  });
4305
- newState.members = newState.members?.concat(event.member) ?? [
4306
- event.member,
4307
- ];
4358
+ newState.members = [event.member, ...currentState.members];
4308
4359
  }
4309
4360
  if (connectedUser?.id === event.member.user.id) {
4310
4361
  newState ?? (newState = {
@@ -4387,6 +4438,7 @@ class Feed extends FeedApi {
4387
4438
  is_loading: false,
4388
4439
  is_loading_activities: false,
4389
4440
  comments_by_entity_id: {},
4441
+ watch,
4390
4442
  });
4391
4443
  this.client = client;
4392
4444
  }
@@ -4466,6 +4518,8 @@ class Feed extends FeedApi {
4466
4518
  }
4467
4519
  }
4468
4520
  else {
4521
+ // Empty queue when reinitializing the state
4522
+ this.stateUpdateQueue.clear();
4469
4523
  const responseCopy = {
4470
4524
  ...response,
4471
4525
  ...response.feed,
@@ -4484,7 +4538,11 @@ class Feed extends FeedApi {
4484
4538
  if (!request?.following_pagination?.limit) {
4485
4539
  delete nextState.following;
4486
4540
  }
4541
+ if (response.members.length === 0 && response.feed.member_count > 0) {
4542
+ delete nextState.members;
4543
+ }
4487
4544
  nextState.last_get_or_create_request_config = request;
4545
+ nextState.watch = request?.watch ? request.watch : currentState.watch;
4488
4546
  return nextState;
4489
4547
  });
4490
4548
  }
@@ -4498,6 +4556,56 @@ class Feed extends FeedApi {
4498
4556
  });
4499
4557
  }
4500
4558
  }
4559
+ /**
4560
+ * @internal
4561
+ */
4562
+ handleFollowCreated(follow) {
4563
+ if (!shouldUpdateState({
4564
+ stateUpdateId: getStateUpdateQueueIdForFollow(follow),
4565
+ stateUpdateQueue: this.stateUpdateQueue,
4566
+ watch: this.currentState.watch,
4567
+ })) {
4568
+ return;
4569
+ }
4570
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4571
+ const result = handleFollowCreated(follow, this.currentState, this.fid, connectedUser?.id);
4572
+ if (result.changed) {
4573
+ this.state.next(result.data);
4574
+ }
4575
+ }
4576
+ /**
4577
+ * @internal
4578
+ */
4579
+ handleFollowDeleted(follow) {
4580
+ if (!shouldUpdateState({
4581
+ stateUpdateId: getStateUpdateQueueIdForUnfollow(follow),
4582
+ stateUpdateQueue: this.stateUpdateQueue,
4583
+ watch: this.currentState.watch,
4584
+ })) {
4585
+ return;
4586
+ }
4587
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4588
+ const result = handleFollowDeleted(follow, this.currentState, this.fid, connectedUser?.id);
4589
+ {
4590
+ this.state.next(result.data);
4591
+ }
4592
+ }
4593
+ /**
4594
+ * @internal
4595
+ */
4596
+ handleWatchStopped() {
4597
+ this.state.partialNext({
4598
+ watch: false,
4599
+ });
4600
+ }
4601
+ /**
4602
+ * @internal
4603
+ */
4604
+ handleWatchStarted() {
4605
+ this.state.partialNext({
4606
+ watch: true,
4607
+ });
4608
+ }
4501
4609
  handleBookmarkAdded(event) {
4502
4610
  const currentActivities = this.currentState.activities;
4503
4611
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
@@ -4542,70 +4650,85 @@ class Feed extends FeedApi {
4542
4650
  }
4543
4651
  return commentIndex;
4544
4652
  }
4545
- getActivityIndex(activity, state) {
4546
- const { activities } = state ?? this.currentState;
4547
- if (!activities) {
4548
- return -1;
4549
- }
4550
- let activityIndex = activities.indexOf(activity);
4551
- // fast lookup failed, try slower approach
4552
- if (activityIndex === -1) {
4553
- activityIndex = activities.findIndex((activity_) => activity_.id === activity.id);
4554
- }
4555
- return activityIndex;
4556
- }
4557
- updateActivityInState(activity, patch) {
4653
+ /**
4654
+ * Load child comments of entity (activity or comment) into the state, if the target entity is comment,
4655
+ * `entityParentId` should be provided (`CommentResponse.parent_id ?? CommentResponse.object_id`).
4656
+ */
4657
+ loadCommentsIntoState(data) {
4658
+ // add initial (top level) object for processing
4659
+ const traverseArray = [
4660
+ {
4661
+ entityId: data.entityId,
4662
+ entityParentId: data.entityParentId,
4663
+ comments: data.comments,
4664
+ next: data.next,
4665
+ },
4666
+ ];
4558
4667
  this.state.next((currentState) => {
4559
- const activityIndex = this.getActivityIndex(activity, currentState);
4560
- if (activityIndex === -1)
4561
- return currentState;
4562
- const nextActivities = [...currentState.activities];
4563
- nextActivities[activityIndex] = patch(currentState.activities[activityIndex]);
4668
+ const newCommentsByEntityId = {
4669
+ ...currentState.comments_by_entity_id,
4670
+ };
4671
+ while (traverseArray.length) {
4672
+ const item = traverseArray.pop();
4673
+ const entityId = item.entityId;
4674
+ // go over entity comments and generate new objects
4675
+ // for further processing if there are any replies
4676
+ item.comments.forEach((comment) => {
4677
+ if (!comment.replies?.length)
4678
+ return;
4679
+ traverseArray.push({
4680
+ entityId: comment.id,
4681
+ entityParentId: entityId,
4682
+ comments: comment.replies,
4683
+ next: comment.meta?.next_cursor,
4684
+ });
4685
+ });
4686
+ // omit replies & meta from the comments (transform ThreadedCommentResponse to CommentResponse)
4687
+ // this is somehow faster than copying the whole
4688
+ // object and deleting the desired properties
4689
+ const newComments = item.comments.map(({ replies: _r, meta: _m, ...restOfTheCommentResponse }) => restOfTheCommentResponse);
4690
+ newCommentsByEntityId[entityId] = {
4691
+ ...newCommentsByEntityId[entityId],
4692
+ entity_parent_id: item.entityParentId,
4693
+ pagination: {
4694
+ ...newCommentsByEntityId[entityId]?.pagination,
4695
+ next: item.next,
4696
+ sort: data.sort,
4697
+ },
4698
+ comments: newCommentsByEntityId[entityId]?.comments
4699
+ ? newCommentsByEntityId[entityId].comments?.concat(newComments)
4700
+ : newComments,
4701
+ };
4702
+ }
4564
4703
  return {
4565
4704
  ...currentState,
4566
- activities: nextActivities,
4705
+ comments_by_entity_id: newCommentsByEntityId,
4567
4706
  };
4568
4707
  });
4569
4708
  }
4570
- async loadNextPageComments({ forId, base, sort, parentId, }) {
4709
+ async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
4571
4710
  let error;
4572
4711
  try {
4573
4712
  this.state.next((currentState) => ({
4574
4713
  ...currentState,
4575
4714
  comments_by_entity_id: {
4576
4715
  ...currentState.comments_by_entity_id,
4577
- [forId]: {
4578
- ...currentState.comments_by_entity_id[forId],
4716
+ [entityId]: {
4717
+ ...currentState.comments_by_entity_id[entityId],
4579
4718
  pagination: {
4580
- ...currentState.comments_by_entity_id[forId]?.pagination,
4719
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4581
4720
  loading_next_page: true,
4582
4721
  },
4583
4722
  },
4584
4723
  },
4585
4724
  }));
4586
- const { next: newNextCursor, comments } = await base();
4587
- this.state.next((currentState) => {
4588
- const newPagination = {
4589
- ...currentState.comments_by_entity_id[forId]?.pagination,
4590
- next: newNextCursor,
4591
- };
4592
- if (typeof newPagination.sort === 'undefined') {
4593
- newPagination.sort = sort;
4594
- }
4595
- return {
4596
- ...currentState,
4597
- comments_by_entity_id: {
4598
- ...currentState.comments_by_entity_id,
4599
- [forId]: {
4600
- ...currentState.comments_by_entity_id[forId],
4601
- parent_id: parentId,
4602
- pagination: newPagination,
4603
- comments: currentState.comments_by_entity_id[forId]?.comments
4604
- ? currentState.comments_by_entity_id[forId].comments?.concat(comments)
4605
- : comments,
4606
- },
4607
- },
4608
- };
4725
+ const { next, comments } = await base();
4726
+ this.loadCommentsIntoState({
4727
+ entityId,
4728
+ comments,
4729
+ entityParentId,
4730
+ next,
4731
+ sort,
4609
4732
  });
4610
4733
  }
4611
4734
  catch (e) {
@@ -4616,10 +4739,10 @@ class Feed extends FeedApi {
4616
4739
  ...currentState,
4617
4740
  comments_by_entity_id: {
4618
4741
  ...currentState.comments_by_entity_id,
4619
- [forId]: {
4620
- ...currentState.comments_by_entity_id[forId],
4742
+ [entityId]: {
4743
+ ...currentState.comments_by_entity_id[entityId],
4621
4744
  pagination: {
4622
- ...currentState.comments_by_entity_id[forId]?.pagination,
4745
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4623
4746
  loading_next_page: false,
4624
4747
  },
4625
4748
  },
@@ -4642,7 +4765,7 @@ class Feed extends FeedApi {
4642
4765
  return;
4643
4766
  }
4644
4767
  await this.loadNextPageComments({
4645
- forId: activity.id,
4768
+ entityId: activity.id,
4646
4769
  base: () => this.client.getComments({
4647
4770
  ...request,
4648
4771
  sort,
@@ -4665,7 +4788,7 @@ class Feed extends FeedApi {
4665
4788
  return;
4666
4789
  }
4667
4790
  await this.loadNextPageComments({
4668
- forId: comment.id,
4791
+ entityId: comment.id,
4669
4792
  base: () => this.client.getCommentReplies({
4670
4793
  ...request,
4671
4794
  comment_id: comment.id,
@@ -4675,7 +4798,7 @@ class Feed extends FeedApi {
4675
4798
  Constants.DEFAULT_COMMENT_PAGINATION,
4676
4799
  next: currentNextCursor,
4677
4800
  }),
4678
- parentId: comment.parent_id ?? comment.object_id,
4801
+ entityParentId: comment.parent_id ?? comment.object_id,
4679
4802
  sort,
4680
4803
  });
4681
4804
  }
@@ -4705,17 +4828,19 @@ class Feed extends FeedApi {
4705
4828
  next: currentNextCursor,
4706
4829
  sort,
4707
4830
  });
4708
- this.state.next((currentState) => ({
4709
- ...currentState,
4710
- [type]: currentState[type]
4711
- ? currentState[type].concat(follows)
4712
- : follows,
4713
- [paginationKey]: {
4714
- ...currentState[paginationKey],
4715
- next: newNextCursor,
4716
- sort,
4717
- },
4718
- }));
4831
+ this.state.next((currentState) => {
4832
+ return {
4833
+ ...currentState,
4834
+ [type]: currentState[type] === undefined
4835
+ ? follows
4836
+ : uniqueArrayMerge(currentState[type], follows, (follow) => `${follow.source_feed.fid}-${follow.target_feed.fid}`),
4837
+ [paginationKey]: {
4838
+ ...currentState[paginationKey],
4839
+ next: newNextCursor,
4840
+ sort,
4841
+ },
4842
+ };
4843
+ });
4719
4844
  }
4720
4845
  catch (e) {
4721
4846
  error = e;
@@ -4768,7 +4893,7 @@ class Feed extends FeedApi {
4768
4893
  this.state.next((currentState) => ({
4769
4894
  ...currentState,
4770
4895
  members: currentState.members
4771
- ? currentState.members.concat(members)
4896
+ ? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
4772
4897
  : members,
4773
4898
  member_pagination: {
4774
4899
  ...currentState.member_pagination,
@@ -5355,13 +5480,17 @@ class FeedsClient extends FeedsApi {
5355
5480
  };
5356
5481
  this.eventDispatcher.dispatch(networkEvent);
5357
5482
  };
5358
- this.getOrCreateActiveFeed = (group, id, data) => {
5483
+ this.getOrCreateActiveFeed = (group, id, data, watch) => {
5359
5484
  const fid = `${group}:${id}`;
5360
5485
  if (this.activeFeeds[fid]) {
5361
- return this.activeFeeds[fid];
5486
+ const feed = this.activeFeeds[fid];
5487
+ if (watch && !feed.currentState.watch) {
5488
+ feed.handleWatchStarted();
5489
+ }
5490
+ return feed;
5362
5491
  }
5363
5492
  else {
5364
- const feed = new Feed(this, group, id, data);
5493
+ const feed = new Feed(this, group, id, data, watch);
5365
5494
  this.activeFeeds[fid] = feed;
5366
5495
  return feed;
5367
5496
  }
@@ -5390,6 +5519,11 @@ class FeedsClient extends FeedsApi {
5390
5519
  }
5391
5520
  }
5392
5521
  }
5522
+ else {
5523
+ for (const activeFeed of Object.values(this.activeFeeds)) {
5524
+ activeFeed.handleWatchStopped();
5525
+ }
5526
+ }
5393
5527
  break;
5394
5528
  }
5395
5529
  case 'feeds.feed.created': {
@@ -5493,7 +5627,7 @@ class FeedsClient extends FeedsApi {
5493
5627
  }
5494
5628
  async queryFeeds(request) {
5495
5629
  const response = await this.feedsQueryFeeds(request);
5496
- const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f));
5630
+ const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f, request?.watch));
5497
5631
  return {
5498
5632
  feeds,
5499
5633
  next: response.next,
@@ -5502,6 +5636,52 @@ class FeedsClient extends FeedsApi {
5502
5636
  duration: response.duration,
5503
5637
  };
5504
5638
  }
5639
+ // For follow API endpoints we update the state after HTTP response to allow queryFeeds with watch: false
5640
+ async follow(request) {
5641
+ const response = await super.follow(request);
5642
+ [response.follow.source_feed.fid, response.follow.target_feed.fid].forEach((fid) => {
5643
+ const feed = this.activeFeeds[fid];
5644
+ if (feed) {
5645
+ feed.handleFollowCreated(response.follow);
5646
+ }
5647
+ });
5648
+ return response;
5649
+ }
5650
+ async followBatch(request) {
5651
+ const response = await super.followBatch(request);
5652
+ response.follows.forEach((follow) => {
5653
+ const feed = this.activeFeeds[follow.source_feed.fid];
5654
+ if (feed) {
5655
+ feed.handleFollowCreated(follow);
5656
+ }
5657
+ });
5658
+ return response;
5659
+ }
5660
+ async unfollow(request) {
5661
+ const response = await super.unfollow(request);
5662
+ [request.source, request.target].forEach((fid) => {
5663
+ const feed = this.activeFeeds[fid];
5664
+ if (feed) {
5665
+ feed.handleFollowDeleted({
5666
+ source_feed: { fid: request.source },
5667
+ target_feed: { fid: request.target },
5668
+ });
5669
+ }
5670
+ });
5671
+ return response;
5672
+ }
5673
+ async stopWatchingFeed(request) {
5674
+ const connectionId = await this.connectionIdManager.getConnectionId();
5675
+ const response = await super.stopWatchingFeed({
5676
+ ...request,
5677
+ connection_id: connectionId,
5678
+ });
5679
+ const feed = this.activeFeeds[`${request.feed_group_id}:${request.feed_id}`];
5680
+ if (feed) {
5681
+ feed.handleWatchStopped();
5682
+ }
5683
+ return response;
5684
+ }
5505
5685
  findActiveFeedByActivityId(activityId) {
5506
5686
  return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
5507
5687
  }
@@ -5581,6 +5761,7 @@ const FeedOwnCapability = {
5581
5761
  const DEFAULT_SEARCH_SOURCE_OPTIONS = {
5582
5762
  debounceMs: 300,
5583
5763
  pageSize: 10,
5764
+ allowEmptySearchString: false,
5584
5765
  };
5585
5766
  class BaseSearchSource {
5586
5767
  constructor(options) {
@@ -5603,14 +5784,15 @@ class BaseSearchSource {
5603
5784
  return !!(this.isActive &&
5604
5785
  !this.isLoading &&
5605
5786
  (this.hasNext || hasNewSearchQuery) &&
5606
- searchString);
5787
+ (this.allowEmptySearchString || searchString));
5607
5788
  };
5608
5789
  this.search = (searchQuery) => this.searchDebounced(searchQuery);
5609
- const { debounceMs, pageSize } = {
5790
+ const { debounceMs, pageSize, allowEmptySearchString } = {
5610
5791
  ...DEFAULT_SEARCH_SOURCE_OPTIONS,
5611
5792
  ...options,
5612
5793
  };
5613
5794
  this.pageSize = pageSize;
5795
+ this.allowEmptySearchString = allowEmptySearchString;
5614
5796
  this.state = new StateStore(this.initialState);
5615
5797
  this.setDebounceOptions({ debounceMs });
5616
5798
  }
@@ -5826,12 +6008,6 @@ class SearchController {
5826
6008
  }
5827
6009
 
5828
6010
  class ActivitySearchSource extends BaseSearchSource {
5829
- // messageSearchChannelFilters: ChannelFilters | undefined;
5830
- // messageSearchFilters: MessageFilters | undefined;
5831
- // messageSearchSort: SearchMessageSort | undefined;
5832
- // channelQueryFilters: ChannelFilters | undefined;
5833
- // channelQuerySort: ChannelSort | undefined;
5834
- // channelQueryOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
5835
6011
  constructor(client, options) {
5836
6012
  super(options);
5837
6013
  this.type = 'activity';
@@ -5843,7 +6019,9 @@ class ActivitySearchSource extends BaseSearchSource {
5843
6019
  return { items: [] };
5844
6020
  const { activities: items, next } = await this.client.queryActivities({
5845
6021
  sort: [{ direction: -1, field: 'created_at' }],
5846
- filter: { text: { $autocomplete: searchQuery } },
6022
+ ...(!this.allowEmptySearchString || searchQuery.length > 0
6023
+ ? { filter: { text: { $autocomplete: searchQuery } } }
6024
+ : {}),
5847
6025
  limit: 10,
5848
6026
  next: this.next ?? undefined,
5849
6027
  });
@@ -5853,19 +6031,8 @@ class ActivitySearchSource extends BaseSearchSource {
5853
6031
  return items;
5854
6032
  }
5855
6033
  }
5856
- // filter: {
5857
- // 'feed.name': { $autocomplete: searchQuery }
5858
- // 'feed.description': { $autocomplete: searchQuery }
5859
- // 'created_by.name': { $autocomplete: searchQuery }
5860
- // },
5861
6034
 
5862
6035
  class UserSearchSource extends BaseSearchSource {
5863
- // messageSearchChannelFilters: ChannelFilters | undefined;
5864
- // messageSearchFilters: MessageFilters | undefined;
5865
- // messageSearchSort: SearchMessageSort | undefined;
5866
- // channelQueryFilters: ChannelFilters | undefined;
5867
- // channelQuerySort: ChannelSort | undefined;
5868
- // channelQueryOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
5869
6036
  constructor(client, options) {
5870
6037
  super(options);
5871
6038
  this.type = 'user';
@@ -5875,57 +6042,16 @@ class UserSearchSource extends BaseSearchSource {
5875
6042
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
5876
6043
  if (!connectedUser)
5877
6044
  return { items: [] };
5878
- // const channelFilters: ChannelFilters = {
5879
- // members: { $in: [this.client.userID] },
5880
- // ...this.messageSearchChannelFilters,
5881
- // } as ChannelFilters;
5882
- // const messageFilters: MessageFilters = {
5883
- // text: searchQuery,
5884
- // type: 'regular', // FIXME: type: 'reply' resp. do not filter by type and allow to jump to a message in a thread - missing support
5885
- // ...this.messageSearchFilters,
5886
- // } as MessageFilters;
5887
- // const sort: SearchMessageSort = {
5888
- // created_at: -1,
5889
- // ...this.messageSearchSort,
5890
- // };
5891
- // const options = {
5892
- // limit: this.pageSize,
5893
- // next: this.next,
5894
- // sort,
5895
- // } as SearchOptions;
5896
- // const { next, results } = await this.client.search(
5897
- // channelFilters,
5898
- // messageFilters,
5899
- // options,
5900
- // );
5901
- // const items = results.map(({ message }) => message);
5902
- // const cids = Array.from(
5903
- // items.reduce((acc, message) => {
5904
- // if (message.cid && !this.client.activeChannels[message.cid])
5905
- // acc.add(message.cid);
5906
- // return acc;
5907
- // }, new Set<string>()), // keep the cids unique
5908
- // );
5909
- // const allChannelsLoadedLocally = cids.length === 0;
5910
- // if (!allChannelsLoadedLocally) {
5911
- // await this.client.queryChannels(
5912
- // {
5913
- // cid: { $in: cids },
5914
- // ...this.channelQueryFilters,
5915
- // } as ChannelFilters,
5916
- // {
5917
- // last_message_at: -1,
5918
- // ...this.channelQuerySort,
5919
- // },
5920
- // this.channelQueryOptions,
5921
- // );
5922
- // }
5923
6045
  const { users: items } = await this.client.queryUsers({
5924
6046
  payload: {
5925
6047
  filter_conditions: {
5926
- name: {
5927
- $autocomplete: searchQuery,
5928
- },
6048
+ ...(!this.allowEmptySearchString || searchQuery.length > 0
6049
+ ? {
6050
+ name: {
6051
+ $autocomplete: searchQuery,
6052
+ },
6053
+ }
6054
+ : {}),
5929
6055
  },
5930
6056
  },
5931
6057
  });
@@ -5937,75 +6063,30 @@ class UserSearchSource extends BaseSearchSource {
5937
6063
  }
5938
6064
 
5939
6065
  class FeedSearchSource extends BaseSearchSource {
5940
- // messageSearchChannelFilters: ChannelFilters | undefined;
5941
- // messageSearchFilters: MessageFilters | undefined;
5942
- // messageSearchSort: SearchMessageSort | undefined;
5943
- // channelQueryFilters: ChannelFilters | undefined;
5944
- // channelQuerySort: ChannelSort | undefined;
5945
- // channelQueryOptions: Omit<ChannelOptions, 'limit' | 'offset'> | undefined;
5946
6066
  constructor(client, options) {
5947
6067
  super(options);
5948
6068
  this.type = 'feed';
5949
6069
  this.client = client;
6070
+ this.feedGroupId = options?.groupId;
5950
6071
  }
5951
6072
  async query(searchQuery) {
5952
6073
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
5953
6074
  if (!connectedUser)
5954
6075
  return { items: [] };
5955
- // const channelFilters: ChannelFilters = {
5956
- // members: { $in: [this.client.userID] },
5957
- // ...this.messageSearchChannelFilters,
5958
- // } as ChannelFilters;
5959
- // const messageFilters: MessageFilters = {
5960
- // text: searchQuery,
5961
- // type: 'regular', // FIXME: type: 'reply' resp. do not filter by type and allow to jump to a message in a thread - missing support
5962
- // ...this.messageSearchFilters,
5963
- // } as MessageFilters;
5964
- // const sort: SearchMessageSort = {
5965
- // created_at: -1,
5966
- // ...this.messageSearchSort,
5967
- // };
5968
- // const options = {
5969
- // limit: this.pageSize,
5970
- // next: this.next,
5971
- // sort,
5972
- // } as SearchOptions;
5973
- // const { next, results } = await this.client.search(
5974
- // channelFilters,
5975
- // messageFilters,
5976
- // options,
5977
- // );
5978
- // const items = results.map(({ message }) => message);
5979
- // const cids = Array.from(
5980
- // items.reduce((acc, message) => {
5981
- // if (message.cid && !this.client.activeChannels[message.cid])
5982
- // acc.add(message.cid);
5983
- // return acc;
5984
- // }, new Set<string>()), // keep the cids unique
5985
- // );
5986
- // const allChannelsLoadedLocally = cids.length === 0;
5987
- // if (!allChannelsLoadedLocally) {
5988
- // await this.client.queryChannels(
5989
- // {
5990
- // cid: { $in: cids },
5991
- // ...this.channelQueryFilters,
5992
- // } as ChannelFilters,
5993
- // {
5994
- // last_message_at: -1,
5995
- // ...this.channelQuerySort,
5996
- // },
5997
- // this.channelQueryOptions,
5998
- // );
5999
- // }
6000
6076
  const { feeds: items, next } = await this.client.queryFeeds({
6001
6077
  filter: {
6002
- group_id: 'user',
6003
- $or: [
6004
- { name: { $autocomplete: searchQuery } },
6005
- { description: { $autocomplete: searchQuery } },
6006
- { 'created_by.name': { $autocomplete: searchQuery } },
6007
- ],
6078
+ ...(this.feedGroupId ? { group_id: this.feedGroupId } : {}),
6079
+ ...(!this.allowEmptySearchString || searchQuery.length > 0
6080
+ ? {
6081
+ $or: [
6082
+ { name: { $autocomplete: searchQuery } },
6083
+ { description: { $autocomplete: searchQuery } },
6084
+ { 'created_by.name': { $autocomplete: searchQuery } },
6085
+ ],
6086
+ }
6087
+ : {}),
6008
6088
  },
6089
+ next: this.next ?? undefined,
6009
6090
  });
6010
6091
  return { items, next };
6011
6092
  }
@@ -6034,4 +6115,5 @@ exports.isImageFile = isImageFile;
6034
6115
  exports.isPatch = isPatch;
6035
6116
  exports.isVideoFile = isVideoFile;
6036
6117
  exports.isVoteAnswer = isVoteAnswer;
6118
+ exports.uniqueArrayMerge = uniqueArrayMerge;
6037
6119
  //# sourceMappingURL=index.browser.cjs.map