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