@stream-io/feeds-client 0.1.8 → 0.1.9
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.
- package/@react-bindings/hooks/util/index.ts +1 -0
- package/CHANGELOG.md +15 -0
- package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
- package/dist/@react-bindings/hooks/util/useBookmarkActions.d.ts +13 -0
- package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +1 -1
- package/dist/index-react-bindings.browser.cjs +346 -140
- package/dist/index-react-bindings.browser.cjs.map +1 -1
- package/dist/index-react-bindings.browser.js +346 -141
- package/dist/index-react-bindings.browser.js.map +1 -1
- package/dist/index-react-bindings.node.cjs +346 -140
- package/dist/index-react-bindings.node.cjs.map +1 -1
- package/dist/index-react-bindings.node.js +346 -141
- package/dist/index-react-bindings.node.js.map +1 -1
- package/dist/index.browser.cjs +320 -139
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +320 -140
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +320 -139
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +320 -140
- package/dist/index.node.js.map +1 -1
- package/dist/src/Feed.d.ts +40 -9
- package/dist/src/FeedsClient.d.ts +8 -1
- package/dist/src/gen-imports.d.ts +1 -1
- package/dist/src/state-updates/follow-utils.d.ts +19 -0
- package/dist/src/state-updates/state-update-queue.d.ts +15 -0
- package/dist/src/utils.d.ts +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Feed.ts +226 -192
- package/src/FeedsClient.ts +75 -3
- package/src/gen-imports.ts +1 -1
- package/src/state-updates/activity-reaction-utils.test.ts +1 -0
- package/src/state-updates/activity-utils.test.ts +1 -0
- package/src/state-updates/follow-utils.test.ts +552 -0
- package/src/state-updates/follow-utils.ts +126 -0
- package/src/state-updates/state-update-queue.test.ts +53 -0
- package/src/state-updates/state-update-queue.ts +35 -0
- package/src/utils.test.ts +175 -0
- package/src/utils.ts +20 -0
|
@@ -4050,6 +4050,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
|
|
|
4050
4050
|
return updateActivityInActivities(updatedActivity, activities);
|
|
4051
4051
|
};
|
|
4052
4052
|
|
|
4053
|
+
const isFeedResponse = (follow) => {
|
|
4054
|
+
return 'created_by' in follow;
|
|
4055
|
+
};
|
|
4056
|
+
const handleFollowCreated = (follow, currentState, currentFeedId, connectedUserId) => {
|
|
4057
|
+
// filter non-accepted follows (the way getOrCreate does by default)
|
|
4058
|
+
if (follow.status !== 'accepted') {
|
|
4059
|
+
return { changed: false, data: currentState };
|
|
4060
|
+
}
|
|
4061
|
+
let newState = { ...currentState };
|
|
4062
|
+
// this feed followed someone
|
|
4063
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
4064
|
+
newState = {
|
|
4065
|
+
...newState,
|
|
4066
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4067
|
+
...follow.source_feed,
|
|
4068
|
+
};
|
|
4069
|
+
// Only update if following array already exists
|
|
4070
|
+
if (currentState.following !== undefined) {
|
|
4071
|
+
newState.following = [follow, ...currentState.following];
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
else if (
|
|
4075
|
+
// someone followed this feed
|
|
4076
|
+
follow.target_feed.fid === currentFeedId) {
|
|
4077
|
+
const source = follow.source_feed;
|
|
4078
|
+
newState = {
|
|
4079
|
+
...newState,
|
|
4080
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4081
|
+
...follow.target_feed,
|
|
4082
|
+
};
|
|
4083
|
+
if (source.created_by.id === connectedUserId) {
|
|
4084
|
+
newState.own_follows = currentState.own_follows
|
|
4085
|
+
? currentState.own_follows.concat(follow)
|
|
4086
|
+
: [follow];
|
|
4087
|
+
}
|
|
4088
|
+
// Only update if followers array already exists
|
|
4089
|
+
if (currentState.followers !== undefined) {
|
|
4090
|
+
newState.followers = [follow, ...currentState.followers];
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
return { changed: true, data: newState };
|
|
4094
|
+
};
|
|
4095
|
+
const handleFollowDeleted = (follow, currentState, currentFeedId, connectedUserId) => {
|
|
4096
|
+
let newState = { ...currentState };
|
|
4097
|
+
// this feed unfollowed someone
|
|
4098
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
4099
|
+
newState = {
|
|
4100
|
+
...newState,
|
|
4101
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4102
|
+
...follow.source_feed,
|
|
4103
|
+
};
|
|
4104
|
+
// Only update if following array already exists
|
|
4105
|
+
if (currentState.following !== undefined) {
|
|
4106
|
+
newState.following = currentState.following.filter((followItem) => followItem.target_feed.fid !== follow.target_feed.fid);
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
else if (
|
|
4110
|
+
// someone unfollowed this feed
|
|
4111
|
+
follow.target_feed.fid === currentFeedId) {
|
|
4112
|
+
const source = follow.source_feed;
|
|
4113
|
+
newState = {
|
|
4114
|
+
...newState,
|
|
4115
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4116
|
+
...follow.target_feed,
|
|
4117
|
+
};
|
|
4118
|
+
if (isFeedResponse(source) &&
|
|
4119
|
+
source.created_by.id === connectedUserId &&
|
|
4120
|
+
currentState.own_follows !== undefined) {
|
|
4121
|
+
newState.own_follows = currentState.own_follows.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
|
|
4122
|
+
}
|
|
4123
|
+
// Only update if followers array already exists
|
|
4124
|
+
if (currentState.followers !== undefined) {
|
|
4125
|
+
newState.followers = currentState.followers.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
return { changed: true, data: newState };
|
|
4129
|
+
};
|
|
4130
|
+
const handleFollowUpdated = (currentState) => {
|
|
4131
|
+
// For now, we'll treat follow updates as no-ops since the current implementation does
|
|
4132
|
+
// This can be enhanced later if needed
|
|
4133
|
+
return { changed: false, data: currentState };
|
|
4134
|
+
};
|
|
4135
|
+
|
|
4053
4136
|
const checkHasAnotherPage = (v, cursor) => (typeof v === 'undefined' && typeof cursor === 'undefined') ||
|
|
4054
4137
|
typeof cursor === 'string';
|
|
4055
4138
|
const isCommentResponse = (entity) => {
|
|
@@ -4058,11 +4141,41 @@ const isCommentResponse = (entity) => {
|
|
|
4058
4141
|
const Constants = {
|
|
4059
4142
|
DEFAULT_COMMENT_PAGINATION: 'first',
|
|
4060
4143
|
};
|
|
4144
|
+
const uniqueArrayMerge = (existingArray, arrayToMerge, getKey) => {
|
|
4145
|
+
const existing = new Set();
|
|
4146
|
+
existingArray.forEach((value) => {
|
|
4147
|
+
const key = getKey(value);
|
|
4148
|
+
existing.add(key);
|
|
4149
|
+
});
|
|
4150
|
+
const filteredArrayToMerge = arrayToMerge.filter((value) => {
|
|
4151
|
+
const key = getKey(value);
|
|
4152
|
+
return !existing.has(key);
|
|
4153
|
+
});
|
|
4154
|
+
return existingArray.concat(filteredArrayToMerge);
|
|
4155
|
+
};
|
|
4156
|
+
|
|
4157
|
+
const shouldUpdateState = ({ stateUpdateId, stateUpdateQueue, watch, }) => {
|
|
4158
|
+
if (!watch) {
|
|
4159
|
+
return true;
|
|
4160
|
+
}
|
|
4161
|
+
if (watch && stateUpdateQueue.has(stateUpdateId)) {
|
|
4162
|
+
stateUpdateQueue.delete(stateUpdateId);
|
|
4163
|
+
return false;
|
|
4164
|
+
}
|
|
4165
|
+
stateUpdateQueue.add(stateUpdateId);
|
|
4166
|
+
return true;
|
|
4167
|
+
};
|
|
4168
|
+
const getStateUpdateQueueIdForFollow = (follow) => {
|
|
4169
|
+
return `follow${follow.source_feed.fid}-${follow.target_feed.fid}`;
|
|
4170
|
+
};
|
|
4171
|
+
const getStateUpdateQueueIdForUnfollow = (follow) => {
|
|
4172
|
+
return `unfollow${follow.source_feed.fid}-${follow.target_feed.fid}`;
|
|
4173
|
+
};
|
|
4061
4174
|
|
|
4062
4175
|
class Feed extends FeedApi {
|
|
4063
|
-
constructor(client, groupId, id, data) {
|
|
4064
|
-
// Need this ugly cast because fileUpload endpoints :(
|
|
4176
|
+
constructor(client, groupId, id, data, watch = false) {
|
|
4065
4177
|
super(client, groupId, id);
|
|
4178
|
+
this.stateUpdateQueue = new Set();
|
|
4066
4179
|
this.eventHandlers = {
|
|
4067
4180
|
'feeds.activity.added': (event) => {
|
|
4068
4181
|
const currentActivities = this.currentState.activities;
|
|
@@ -4208,74 +4321,14 @@ class Feed extends FeedApi {
|
|
|
4208
4321
|
'feeds.feed_group.changed': Feed.noop,
|
|
4209
4322
|
'feeds.feed_group.deleted': Feed.noop,
|
|
4210
4323
|
'feeds.follow.created': (event) => {
|
|
4211
|
-
|
|
4212
|
-
if (event.follow.status !== 'accepted')
|
|
4213
|
-
return;
|
|
4214
|
-
// this feed followed someone
|
|
4215
|
-
if (event.follow.source_feed.fid === this.fid) {
|
|
4216
|
-
this.state.next((currentState) => {
|
|
4217
|
-
const newState = {
|
|
4218
|
-
...currentState,
|
|
4219
|
-
...event.follow.source_feed,
|
|
4220
|
-
};
|
|
4221
|
-
if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
|
|
4222
|
-
// TODO: respect sort
|
|
4223
|
-
newState.following = currentState.following
|
|
4224
|
-
? currentState.following.concat(event.follow)
|
|
4225
|
-
: [event.follow];
|
|
4226
|
-
}
|
|
4227
|
-
return newState;
|
|
4228
|
-
});
|
|
4229
|
-
}
|
|
4230
|
-
else if (
|
|
4231
|
-
// someone followed this feed
|
|
4232
|
-
event.follow.target_feed.fid === this.fid) {
|
|
4233
|
-
const source = event.follow.source_feed;
|
|
4234
|
-
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4235
|
-
this.state.next((currentState) => {
|
|
4236
|
-
const newState = { ...currentState, ...event.follow.target_feed };
|
|
4237
|
-
if (source.created_by.id === connectedUser?.id) {
|
|
4238
|
-
newState.own_follows = currentState.own_follows
|
|
4239
|
-
? currentState.own_follows.concat(event.follow)
|
|
4240
|
-
: [event.follow];
|
|
4241
|
-
}
|
|
4242
|
-
if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
|
|
4243
|
-
// TODO: respect sort
|
|
4244
|
-
newState.followers = currentState.followers
|
|
4245
|
-
? currentState.followers.concat(event.follow)
|
|
4246
|
-
: [event.follow];
|
|
4247
|
-
}
|
|
4248
|
-
return newState;
|
|
4249
|
-
});
|
|
4250
|
-
}
|
|
4324
|
+
this.handleFollowCreated(event.follow);
|
|
4251
4325
|
},
|
|
4252
4326
|
'feeds.follow.deleted': (event) => {
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
...currentState,
|
|
4258
|
-
...event.follow.source_feed,
|
|
4259
|
-
following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
|
|
4260
|
-
};
|
|
4261
|
-
});
|
|
4262
|
-
}
|
|
4263
|
-
else if (
|
|
4264
|
-
// someone unfollowed this feed
|
|
4265
|
-
event.follow.target_feed.fid === this.fid) {
|
|
4266
|
-
const source = event.follow.source_feed;
|
|
4267
|
-
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4268
|
-
this.state.next((currentState) => {
|
|
4269
|
-
const newState = { ...currentState, ...event.follow.target_feed };
|
|
4270
|
-
if (source.created_by.id === connectedUser?.id) {
|
|
4271
|
-
newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
|
|
4272
|
-
}
|
|
4273
|
-
newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
|
|
4274
|
-
return newState;
|
|
4275
|
-
});
|
|
4276
|
-
}
|
|
4327
|
+
this.handleFollowDeleted(event.follow);
|
|
4328
|
+
},
|
|
4329
|
+
'feeds.follow.updated': (_event) => {
|
|
4330
|
+
handleFollowUpdated(this.currentState);
|
|
4277
4331
|
},
|
|
4278
|
-
'feeds.follow.updated': Feed.noop,
|
|
4279
4332
|
'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
|
|
4280
4333
|
'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
|
|
4281
4334
|
'feeds.comment.reaction.updated': Feed.noop,
|
|
@@ -4283,13 +4336,11 @@ class Feed extends FeedApi {
|
|
|
4283
4336
|
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
4284
4337
|
this.state.next((currentState) => {
|
|
4285
4338
|
let newState;
|
|
4286
|
-
if (
|
|
4339
|
+
if (typeof currentState.members !== 'undefined') {
|
|
4287
4340
|
newState ?? (newState = {
|
|
4288
4341
|
...currentState,
|
|
4289
4342
|
});
|
|
4290
|
-
newState.members =
|
|
4291
|
-
event.member,
|
|
4292
|
-
];
|
|
4343
|
+
newState.members = [event.member, ...currentState.members];
|
|
4293
4344
|
}
|
|
4294
4345
|
if (connectedUser?.id === event.member.user.id) {
|
|
4295
4346
|
newState ?? (newState = {
|
|
@@ -4372,6 +4423,7 @@ class Feed extends FeedApi {
|
|
|
4372
4423
|
is_loading: false,
|
|
4373
4424
|
is_loading_activities: false,
|
|
4374
4425
|
comments_by_entity_id: {},
|
|
4426
|
+
watch,
|
|
4375
4427
|
});
|
|
4376
4428
|
this.client = client;
|
|
4377
4429
|
}
|
|
@@ -4451,6 +4503,8 @@ class Feed extends FeedApi {
|
|
|
4451
4503
|
}
|
|
4452
4504
|
}
|
|
4453
4505
|
else {
|
|
4506
|
+
// Empty queue when reinitializing the state
|
|
4507
|
+
this.stateUpdateQueue.clear();
|
|
4454
4508
|
const responseCopy = {
|
|
4455
4509
|
...response,
|
|
4456
4510
|
...response.feed,
|
|
@@ -4469,7 +4523,11 @@ class Feed extends FeedApi {
|
|
|
4469
4523
|
if (!request?.following_pagination?.limit) {
|
|
4470
4524
|
delete nextState.following;
|
|
4471
4525
|
}
|
|
4526
|
+
if (response.members.length === 0 && response.feed.member_count > 0) {
|
|
4527
|
+
delete nextState.members;
|
|
4528
|
+
}
|
|
4472
4529
|
nextState.last_get_or_create_request_config = request;
|
|
4530
|
+
nextState.watch = request?.watch ? request.watch : currentState.watch;
|
|
4473
4531
|
return nextState;
|
|
4474
4532
|
});
|
|
4475
4533
|
}
|
|
@@ -4483,6 +4541,56 @@ class Feed extends FeedApi {
|
|
|
4483
4541
|
});
|
|
4484
4542
|
}
|
|
4485
4543
|
}
|
|
4544
|
+
/**
|
|
4545
|
+
* @internal
|
|
4546
|
+
*/
|
|
4547
|
+
handleFollowCreated(follow) {
|
|
4548
|
+
if (!shouldUpdateState({
|
|
4549
|
+
stateUpdateId: getStateUpdateQueueIdForFollow(follow),
|
|
4550
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
4551
|
+
watch: this.currentState.watch,
|
|
4552
|
+
})) {
|
|
4553
|
+
return;
|
|
4554
|
+
}
|
|
4555
|
+
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4556
|
+
const result = handleFollowCreated(follow, this.currentState, this.fid, connectedUser?.id);
|
|
4557
|
+
if (result.changed) {
|
|
4558
|
+
this.state.next(result.data);
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
/**
|
|
4562
|
+
* @internal
|
|
4563
|
+
*/
|
|
4564
|
+
handleFollowDeleted(follow) {
|
|
4565
|
+
if (!shouldUpdateState({
|
|
4566
|
+
stateUpdateId: getStateUpdateQueueIdForUnfollow(follow),
|
|
4567
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
4568
|
+
watch: this.currentState.watch,
|
|
4569
|
+
})) {
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4573
|
+
const result = handleFollowDeleted(follow, this.currentState, this.fid, connectedUser?.id);
|
|
4574
|
+
{
|
|
4575
|
+
this.state.next(result.data);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
/**
|
|
4579
|
+
* @internal
|
|
4580
|
+
*/
|
|
4581
|
+
handleWatchStopped() {
|
|
4582
|
+
this.state.partialNext({
|
|
4583
|
+
watch: false,
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
/**
|
|
4587
|
+
* @internal
|
|
4588
|
+
*/
|
|
4589
|
+
handleWatchStarted() {
|
|
4590
|
+
this.state.partialNext({
|
|
4591
|
+
watch: true,
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4486
4594
|
handleBookmarkAdded(event) {
|
|
4487
4595
|
const currentActivities = this.currentState.activities;
|
|
4488
4596
|
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
@@ -4527,70 +4635,85 @@ class Feed extends FeedApi {
|
|
|
4527
4635
|
}
|
|
4528
4636
|
return commentIndex;
|
|
4529
4637
|
}
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4638
|
+
/**
|
|
4639
|
+
* Load child comments of entity (activity or comment) into the state, if the target entity is comment,
|
|
4640
|
+
* `entityParentId` should be provided (`CommentResponse.parent_id ?? CommentResponse.object_id`).
|
|
4641
|
+
*/
|
|
4642
|
+
loadCommentsIntoState(data) {
|
|
4643
|
+
// add initial (top level) object for processing
|
|
4644
|
+
const traverseArray = [
|
|
4645
|
+
{
|
|
4646
|
+
entityId: data.entityId,
|
|
4647
|
+
entityParentId: data.entityParentId,
|
|
4648
|
+
comments: data.comments,
|
|
4649
|
+
next: data.next,
|
|
4650
|
+
},
|
|
4651
|
+
];
|
|
4543
4652
|
this.state.next((currentState) => {
|
|
4544
|
-
const
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4653
|
+
const newCommentsByEntityId = {
|
|
4654
|
+
...currentState.comments_by_entity_id,
|
|
4655
|
+
};
|
|
4656
|
+
while (traverseArray.length) {
|
|
4657
|
+
const item = traverseArray.pop();
|
|
4658
|
+
const entityId = item.entityId;
|
|
4659
|
+
// go over entity comments and generate new objects
|
|
4660
|
+
// for further processing if there are any replies
|
|
4661
|
+
item.comments.forEach((comment) => {
|
|
4662
|
+
if (!comment.replies?.length)
|
|
4663
|
+
return;
|
|
4664
|
+
traverseArray.push({
|
|
4665
|
+
entityId: comment.id,
|
|
4666
|
+
entityParentId: entityId,
|
|
4667
|
+
comments: comment.replies,
|
|
4668
|
+
next: comment.meta?.next_cursor,
|
|
4669
|
+
});
|
|
4670
|
+
});
|
|
4671
|
+
// omit replies & meta from the comments (transform ThreadedCommentResponse to CommentResponse)
|
|
4672
|
+
// this is somehow faster than copying the whole
|
|
4673
|
+
// object and deleting the desired properties
|
|
4674
|
+
const newComments = item.comments.map(({ replies: _r, meta: _m, ...restOfTheCommentResponse }) => restOfTheCommentResponse);
|
|
4675
|
+
newCommentsByEntityId[entityId] = {
|
|
4676
|
+
...newCommentsByEntityId[entityId],
|
|
4677
|
+
entity_parent_id: item.entityParentId,
|
|
4678
|
+
pagination: {
|
|
4679
|
+
...newCommentsByEntityId[entityId]?.pagination,
|
|
4680
|
+
next: item.next,
|
|
4681
|
+
sort: data.sort,
|
|
4682
|
+
},
|
|
4683
|
+
comments: newCommentsByEntityId[entityId]?.comments
|
|
4684
|
+
? newCommentsByEntityId[entityId].comments?.concat(newComments)
|
|
4685
|
+
: newComments,
|
|
4686
|
+
};
|
|
4687
|
+
}
|
|
4549
4688
|
return {
|
|
4550
4689
|
...currentState,
|
|
4551
|
-
|
|
4690
|
+
comments_by_entity_id: newCommentsByEntityId,
|
|
4552
4691
|
};
|
|
4553
4692
|
});
|
|
4554
4693
|
}
|
|
4555
|
-
async loadNextPageComments({
|
|
4694
|
+
async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
|
|
4556
4695
|
let error;
|
|
4557
4696
|
try {
|
|
4558
4697
|
this.state.next((currentState) => ({
|
|
4559
4698
|
...currentState,
|
|
4560
4699
|
comments_by_entity_id: {
|
|
4561
4700
|
...currentState.comments_by_entity_id,
|
|
4562
|
-
[
|
|
4563
|
-
...currentState.comments_by_entity_id[
|
|
4701
|
+
[entityId]: {
|
|
4702
|
+
...currentState.comments_by_entity_id[entityId],
|
|
4564
4703
|
pagination: {
|
|
4565
|
-
...currentState.comments_by_entity_id[
|
|
4704
|
+
...currentState.comments_by_entity_id[entityId]?.pagination,
|
|
4566
4705
|
loading_next_page: true,
|
|
4567
4706
|
},
|
|
4568
4707
|
},
|
|
4569
4708
|
},
|
|
4570
4709
|
}));
|
|
4571
|
-
const { next
|
|
4572
|
-
this.
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
newPagination.sort = sort;
|
|
4579
|
-
}
|
|
4580
|
-
return {
|
|
4581
|
-
...currentState,
|
|
4582
|
-
comments_by_entity_id: {
|
|
4583
|
-
...currentState.comments_by_entity_id,
|
|
4584
|
-
[forId]: {
|
|
4585
|
-
...currentState.comments_by_entity_id[forId],
|
|
4586
|
-
parent_id: parentId,
|
|
4587
|
-
pagination: newPagination,
|
|
4588
|
-
comments: currentState.comments_by_entity_id[forId]?.comments
|
|
4589
|
-
? currentState.comments_by_entity_id[forId].comments?.concat(comments)
|
|
4590
|
-
: comments,
|
|
4591
|
-
},
|
|
4592
|
-
},
|
|
4593
|
-
};
|
|
4710
|
+
const { next, comments } = await base();
|
|
4711
|
+
this.loadCommentsIntoState({
|
|
4712
|
+
entityId,
|
|
4713
|
+
comments,
|
|
4714
|
+
entityParentId,
|
|
4715
|
+
next,
|
|
4716
|
+
sort,
|
|
4594
4717
|
});
|
|
4595
4718
|
}
|
|
4596
4719
|
catch (e) {
|
|
@@ -4601,10 +4724,10 @@ class Feed extends FeedApi {
|
|
|
4601
4724
|
...currentState,
|
|
4602
4725
|
comments_by_entity_id: {
|
|
4603
4726
|
...currentState.comments_by_entity_id,
|
|
4604
|
-
[
|
|
4605
|
-
...currentState.comments_by_entity_id[
|
|
4727
|
+
[entityId]: {
|
|
4728
|
+
...currentState.comments_by_entity_id[entityId],
|
|
4606
4729
|
pagination: {
|
|
4607
|
-
...currentState.comments_by_entity_id[
|
|
4730
|
+
...currentState.comments_by_entity_id[entityId]?.pagination,
|
|
4608
4731
|
loading_next_page: false,
|
|
4609
4732
|
},
|
|
4610
4733
|
},
|
|
@@ -4627,7 +4750,7 @@ class Feed extends FeedApi {
|
|
|
4627
4750
|
return;
|
|
4628
4751
|
}
|
|
4629
4752
|
await this.loadNextPageComments({
|
|
4630
|
-
|
|
4753
|
+
entityId: activity.id,
|
|
4631
4754
|
base: () => this.client.getComments({
|
|
4632
4755
|
...request,
|
|
4633
4756
|
sort,
|
|
@@ -4650,7 +4773,7 @@ class Feed extends FeedApi {
|
|
|
4650
4773
|
return;
|
|
4651
4774
|
}
|
|
4652
4775
|
await this.loadNextPageComments({
|
|
4653
|
-
|
|
4776
|
+
entityId: comment.id,
|
|
4654
4777
|
base: () => this.client.getCommentReplies({
|
|
4655
4778
|
...request,
|
|
4656
4779
|
comment_id: comment.id,
|
|
@@ -4660,7 +4783,7 @@ class Feed extends FeedApi {
|
|
|
4660
4783
|
Constants.DEFAULT_COMMENT_PAGINATION,
|
|
4661
4784
|
next: currentNextCursor,
|
|
4662
4785
|
}),
|
|
4663
|
-
|
|
4786
|
+
entityParentId: comment.parent_id ?? comment.object_id,
|
|
4664
4787
|
sort,
|
|
4665
4788
|
});
|
|
4666
4789
|
}
|
|
@@ -4690,17 +4813,19 @@ class Feed extends FeedApi {
|
|
|
4690
4813
|
next: currentNextCursor,
|
|
4691
4814
|
sort,
|
|
4692
4815
|
});
|
|
4693
|
-
this.state.next((currentState) =>
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4816
|
+
this.state.next((currentState) => {
|
|
4817
|
+
return {
|
|
4818
|
+
...currentState,
|
|
4819
|
+
[type]: currentState[type] === undefined
|
|
4820
|
+
? follows
|
|
4821
|
+
: uniqueArrayMerge(currentState[type], follows, (follow) => `${follow.source_feed.fid}-${follow.target_feed.fid}`),
|
|
4822
|
+
[paginationKey]: {
|
|
4823
|
+
...currentState[paginationKey],
|
|
4824
|
+
next: newNextCursor,
|
|
4825
|
+
sort,
|
|
4826
|
+
},
|
|
4827
|
+
};
|
|
4828
|
+
});
|
|
4704
4829
|
}
|
|
4705
4830
|
catch (e) {
|
|
4706
4831
|
error = e;
|
|
@@ -4753,7 +4878,7 @@ class Feed extends FeedApi {
|
|
|
4753
4878
|
this.state.next((currentState) => ({
|
|
4754
4879
|
...currentState,
|
|
4755
4880
|
members: currentState.members
|
|
4756
|
-
? currentState.members
|
|
4881
|
+
? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
|
|
4757
4882
|
: members,
|
|
4758
4883
|
member_pagination: {
|
|
4759
4884
|
...currentState.member_pagination,
|
|
@@ -5340,13 +5465,17 @@ class FeedsClient extends FeedsApi {
|
|
|
5340
5465
|
};
|
|
5341
5466
|
this.eventDispatcher.dispatch(networkEvent);
|
|
5342
5467
|
};
|
|
5343
|
-
this.getOrCreateActiveFeed = (group, id, data) => {
|
|
5468
|
+
this.getOrCreateActiveFeed = (group, id, data, watch) => {
|
|
5344
5469
|
const fid = `${group}:${id}`;
|
|
5345
5470
|
if (this.activeFeeds[fid]) {
|
|
5346
|
-
|
|
5471
|
+
const feed = this.activeFeeds[fid];
|
|
5472
|
+
if (watch && !feed.currentState.watch) {
|
|
5473
|
+
feed.handleWatchStarted();
|
|
5474
|
+
}
|
|
5475
|
+
return feed;
|
|
5347
5476
|
}
|
|
5348
5477
|
else {
|
|
5349
|
-
const feed = new Feed(this, group, id, data);
|
|
5478
|
+
const feed = new Feed(this, group, id, data, watch);
|
|
5350
5479
|
this.activeFeeds[fid] = feed;
|
|
5351
5480
|
return feed;
|
|
5352
5481
|
}
|
|
@@ -5375,6 +5504,11 @@ class FeedsClient extends FeedsApi {
|
|
|
5375
5504
|
}
|
|
5376
5505
|
}
|
|
5377
5506
|
}
|
|
5507
|
+
else {
|
|
5508
|
+
for (const activeFeed of Object.values(this.activeFeeds)) {
|
|
5509
|
+
activeFeed.handleWatchStopped();
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5378
5512
|
break;
|
|
5379
5513
|
}
|
|
5380
5514
|
case 'feeds.feed.created': {
|
|
@@ -5478,7 +5612,7 @@ class FeedsClient extends FeedsApi {
|
|
|
5478
5612
|
}
|
|
5479
5613
|
async queryFeeds(request) {
|
|
5480
5614
|
const response = await this.feedsQueryFeeds(request);
|
|
5481
|
-
const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f));
|
|
5615
|
+
const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f, request?.watch));
|
|
5482
5616
|
return {
|
|
5483
5617
|
feeds,
|
|
5484
5618
|
next: response.next,
|
|
@@ -5487,6 +5621,52 @@ class FeedsClient extends FeedsApi {
|
|
|
5487
5621
|
duration: response.duration,
|
|
5488
5622
|
};
|
|
5489
5623
|
}
|
|
5624
|
+
// For follow API endpoints we update the state after HTTP response to allow queryFeeds with watch: false
|
|
5625
|
+
async follow(request) {
|
|
5626
|
+
const response = await super.follow(request);
|
|
5627
|
+
[response.follow.source_feed.fid, response.follow.target_feed.fid].forEach((fid) => {
|
|
5628
|
+
const feed = this.activeFeeds[fid];
|
|
5629
|
+
if (feed) {
|
|
5630
|
+
feed.handleFollowCreated(response.follow);
|
|
5631
|
+
}
|
|
5632
|
+
});
|
|
5633
|
+
return response;
|
|
5634
|
+
}
|
|
5635
|
+
async followBatch(request) {
|
|
5636
|
+
const response = await super.followBatch(request);
|
|
5637
|
+
response.follows.forEach((follow) => {
|
|
5638
|
+
const feed = this.activeFeeds[follow.source_feed.fid];
|
|
5639
|
+
if (feed) {
|
|
5640
|
+
feed.handleFollowCreated(follow);
|
|
5641
|
+
}
|
|
5642
|
+
});
|
|
5643
|
+
return response;
|
|
5644
|
+
}
|
|
5645
|
+
async unfollow(request) {
|
|
5646
|
+
const response = await super.unfollow(request);
|
|
5647
|
+
[request.source, request.target].forEach((fid) => {
|
|
5648
|
+
const feed = this.activeFeeds[fid];
|
|
5649
|
+
if (feed) {
|
|
5650
|
+
feed.handleFollowDeleted({
|
|
5651
|
+
source_feed: { fid: request.source },
|
|
5652
|
+
target_feed: { fid: request.target },
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
});
|
|
5656
|
+
return response;
|
|
5657
|
+
}
|
|
5658
|
+
async stopWatchingFeed(request) {
|
|
5659
|
+
const connectionId = await this.connectionIdManager.getConnectionId();
|
|
5660
|
+
const response = await super.stopWatchingFeed({
|
|
5661
|
+
...request,
|
|
5662
|
+
connection_id: connectionId,
|
|
5663
|
+
});
|
|
5664
|
+
const feed = this.activeFeeds[`${request.feed_group_id}:${request.feed_id}`];
|
|
5665
|
+
if (feed) {
|
|
5666
|
+
feed.handleWatchStopped();
|
|
5667
|
+
}
|
|
5668
|
+
return response;
|
|
5669
|
+
}
|
|
5490
5670
|
findActiveFeedByActivityId(activityId) {
|
|
5491
5671
|
return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
|
|
5492
5672
|
}
|
|
@@ -5818,7 +5998,7 @@ const selector = ({ own_follows }) => ({
|
|
|
5818
5998
|
* that can then be used on the UI. The entity can be either an ActivityResponse or a CommentResponse
|
|
5819
5999
|
* as the hook determines internally which APIs it is supposed to use, while taking the
|
|
5820
6000
|
* correct ownCapabilities into account.
|
|
5821
|
-
* @param entity - The entity to which we want to
|
|
6001
|
+
* @param entity - The entity to which we want to apply reaction actions, can be either ActivityResponse or CommentResponse.
|
|
5822
6002
|
* @param type - The type of reaction we want to add or remove.
|
|
5823
6003
|
*/
|
|
5824
6004
|
const useReactionActions = ({ entity, type, }) => {
|
|
@@ -5849,6 +6029,31 @@ const useReactionActions = ({ entity, type, }) => {
|
|
|
5849
6029
|
return react.useMemo(() => ({ addReaction, removeReaction, toggleReaction }), [addReaction, removeReaction, toggleReaction]);
|
|
5850
6030
|
};
|
|
5851
6031
|
|
|
6032
|
+
/**
|
|
6033
|
+
* A utility hook that takes in an entity and creates bookmark actions
|
|
6034
|
+
* that can then be used on the UI. The entity is expected to be an ActivityResponse.
|
|
6035
|
+
* @param entity - The entity to which we want to apply reaction actions, expects an ActivityResponse.
|
|
6036
|
+
*/
|
|
6037
|
+
const useBookmarkActions = ({ entity, }) => {
|
|
6038
|
+
const client = useFeedsClient();
|
|
6039
|
+
const hasOwnBookmark = entity.own_bookmarks?.length > 0;
|
|
6040
|
+
const addBookmark = useStableCallback(async () => {
|
|
6041
|
+
await client?.addBookmark({ activity_id: entity.id });
|
|
6042
|
+
});
|
|
6043
|
+
const removeBookmark = useStableCallback(async () => {
|
|
6044
|
+
await client?.deleteBookmark({ activity_id: entity.id });
|
|
6045
|
+
});
|
|
6046
|
+
const toggleBookmark = useStableCallback(async () => {
|
|
6047
|
+
if (hasOwnBookmark) {
|
|
6048
|
+
await removeBookmark();
|
|
6049
|
+
}
|
|
6050
|
+
else {
|
|
6051
|
+
await addBookmark();
|
|
6052
|
+
}
|
|
6053
|
+
});
|
|
6054
|
+
return react.useMemo(() => ({ addBookmark, removeBookmark, toggleBookmark }), [addBookmark, removeBookmark, toggleBookmark]);
|
|
6055
|
+
};
|
|
6056
|
+
|
|
5852
6057
|
const StreamFeeds = ({ client, children }) => {
|
|
5853
6058
|
return (jsxRuntime.jsx(StreamFeedsContext.Provider, { value: client, children: children }));
|
|
5854
6059
|
};
|
|
@@ -5863,6 +6068,7 @@ exports.StreamFeed = StreamFeed;
|
|
|
5863
6068
|
exports.StreamFeedContext = StreamFeedContext;
|
|
5864
6069
|
exports.StreamFeeds = StreamFeeds;
|
|
5865
6070
|
exports.StreamFeedsContext = StreamFeedsContext;
|
|
6071
|
+
exports.useBookmarkActions = useBookmarkActions;
|
|
5866
6072
|
exports.useClientConnectedUser = useClientConnectedUser;
|
|
5867
6073
|
exports.useComments = useComments;
|
|
5868
6074
|
exports.useCreateFeedsClient = useCreateFeedsClient;
|