@stream-io/feeds-client 0.1.7 → 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 +20 -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 +363 -141
- package/dist/index-react-bindings.browser.cjs.map +1 -1
- package/dist/index-react-bindings.browser.js +363 -142
- package/dist/index-react-bindings.browser.js.map +1 -1
- package/dist/index-react-bindings.node.cjs +363 -141
- package/dist/index-react-bindings.node.cjs.map +1 -1
- package/dist/index-react-bindings.node.js +363 -142
- package/dist/index-react-bindings.node.js.map +1 -1
- package/dist/index.browser.cjs +337 -140
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +337 -141
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +337 -140
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +337 -141
- package/dist/index.node.js.map +1 -1
- package/dist/src/Feed.d.ts +42 -11
- package/dist/src/FeedsClient.d.ts +10 -3
- package/dist/src/common/real-time/StableWSConnection.d.ts +3 -3
- package/dist/src/gen/models/index.d.ts +25 -2
- 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 +230 -192
- package/src/FeedsClient.ts +75 -3
- package/src/gen/feeds/FeedsApi.ts +0 -1
- package/src/gen/model-decoders/decoders.ts +16 -0
- package/src/gen/model-decoders/event-decoder-mapping.ts +3 -0
- package/src/gen/models/index.ts +42 -4
- 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
|
@@ -1147,6 +1147,18 @@ decoders.MuteResponse = (input) => {
|
|
|
1147
1147
|
};
|
|
1148
1148
|
return decode(typeMappings, input);
|
|
1149
1149
|
};
|
|
1150
|
+
decoders.NotificationFeedUpdatedEvent = (input) => {
|
|
1151
|
+
const typeMappings = {
|
|
1152
|
+
created_at: { type: 'DatetimeType', isSingle: true },
|
|
1153
|
+
received_at: { type: 'DatetimeType', isSingle: true },
|
|
1154
|
+
aggregated_activities: {
|
|
1155
|
+
type: 'AggregatedActivityResponse',
|
|
1156
|
+
isSingle: false,
|
|
1157
|
+
},
|
|
1158
|
+
notification_status: { type: 'NotificationStatusResponse', isSingle: true },
|
|
1159
|
+
};
|
|
1160
|
+
return decode(typeMappings, input);
|
|
1161
|
+
};
|
|
1150
1162
|
decoders.NotificationStatusResponse = (input) => {
|
|
1151
1163
|
const typeMappings = {
|
|
1152
1164
|
last_seen_at: { type: 'DatetimeType', isSingle: true },
|
|
@@ -2622,7 +2634,6 @@ class FeedsApi {
|
|
|
2622
2634
|
}
|
|
2623
2635
|
async updateLiveLocation(request) {
|
|
2624
2636
|
const body = {
|
|
2625
|
-
created_by_device_id: request?.created_by_device_id,
|
|
2626
2637
|
message_id: request?.message_id,
|
|
2627
2638
|
end_at: request?.end_at,
|
|
2628
2639
|
latitude: request?.latitude,
|
|
@@ -3706,6 +3717,7 @@ const eventDecoderMapping = {
|
|
|
3706
3717
|
'feeds.follow.created': (data) => decoders.FollowCreatedEvent(data),
|
|
3707
3718
|
'feeds.follow.deleted': (data) => decoders.FollowDeletedEvent(data),
|
|
3708
3719
|
'feeds.follow.updated': (data) => decoders.FollowUpdatedEvent(data),
|
|
3720
|
+
'feeds.notification_feed.updated': (data) => decoders.NotificationFeedUpdatedEvent(data),
|
|
3709
3721
|
'feeds.poll.closed': (data) => decoders.PollClosedFeedEvent(data),
|
|
3710
3722
|
'feeds.poll.deleted': (data) => decoders.PollDeletedFeedEvent(data),
|
|
3711
3723
|
'feeds.poll.updated': (data) => decoders.PollUpdatedFeedEvent(data),
|
|
@@ -4038,6 +4050,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
|
|
|
4038
4050
|
return updateActivityInActivities(updatedActivity, activities);
|
|
4039
4051
|
};
|
|
4040
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
|
+
|
|
4041
4136
|
const checkHasAnotherPage = (v, cursor) => (typeof v === 'undefined' && typeof cursor === 'undefined') ||
|
|
4042
4137
|
typeof cursor === 'string';
|
|
4043
4138
|
const isCommentResponse = (entity) => {
|
|
@@ -4046,11 +4141,41 @@ const isCommentResponse = (entity) => {
|
|
|
4046
4141
|
const Constants = {
|
|
4047
4142
|
DEFAULT_COMMENT_PAGINATION: 'first',
|
|
4048
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
|
+
};
|
|
4049
4174
|
|
|
4050
4175
|
class Feed extends FeedApi {
|
|
4051
|
-
constructor(client, groupId, id, data) {
|
|
4052
|
-
// Need this ugly cast because fileUpload endpoints :(
|
|
4176
|
+
constructor(client, groupId, id, data, watch = false) {
|
|
4053
4177
|
super(client, groupId, id);
|
|
4178
|
+
this.stateUpdateQueue = new Set();
|
|
4054
4179
|
this.eventHandlers = {
|
|
4055
4180
|
'feeds.activity.added': (event) => {
|
|
4056
4181
|
const currentActivities = this.currentState.activities;
|
|
@@ -4196,74 +4321,14 @@ class Feed extends FeedApi {
|
|
|
4196
4321
|
'feeds.feed_group.changed': Feed.noop,
|
|
4197
4322
|
'feeds.feed_group.deleted': Feed.noop,
|
|
4198
4323
|
'feeds.follow.created': (event) => {
|
|
4199
|
-
|
|
4200
|
-
if (event.follow.status !== 'accepted')
|
|
4201
|
-
return;
|
|
4202
|
-
// this feed followed someone
|
|
4203
|
-
if (event.follow.source_feed.fid === this.fid) {
|
|
4204
|
-
this.state.next((currentState) => {
|
|
4205
|
-
const newState = {
|
|
4206
|
-
...currentState,
|
|
4207
|
-
...event.follow.source_feed,
|
|
4208
|
-
};
|
|
4209
|
-
if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
|
|
4210
|
-
// TODO: respect sort
|
|
4211
|
-
newState.following = currentState.following
|
|
4212
|
-
? currentState.following.concat(event.follow)
|
|
4213
|
-
: [event.follow];
|
|
4214
|
-
}
|
|
4215
|
-
return newState;
|
|
4216
|
-
});
|
|
4217
|
-
}
|
|
4218
|
-
else if (
|
|
4219
|
-
// someone followed this feed
|
|
4220
|
-
event.follow.target_feed.fid === this.fid) {
|
|
4221
|
-
const source = event.follow.source_feed;
|
|
4222
|
-
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4223
|
-
this.state.next((currentState) => {
|
|
4224
|
-
const newState = { ...currentState, ...event.follow.target_feed };
|
|
4225
|
-
if (source.created_by.id === connectedUser?.id) {
|
|
4226
|
-
newState.own_follows = currentState.own_follows
|
|
4227
|
-
? currentState.own_follows.concat(event.follow)
|
|
4228
|
-
: [event.follow];
|
|
4229
|
-
}
|
|
4230
|
-
if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
|
|
4231
|
-
// TODO: respect sort
|
|
4232
|
-
newState.followers = currentState.followers
|
|
4233
|
-
? currentState.followers.concat(event.follow)
|
|
4234
|
-
: [event.follow];
|
|
4235
|
-
}
|
|
4236
|
-
return newState;
|
|
4237
|
-
});
|
|
4238
|
-
}
|
|
4324
|
+
this.handleFollowCreated(event.follow);
|
|
4239
4325
|
},
|
|
4240
4326
|
'feeds.follow.deleted': (event) => {
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
...currentState,
|
|
4246
|
-
...event.follow.source_feed,
|
|
4247
|
-
following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
|
|
4248
|
-
};
|
|
4249
|
-
});
|
|
4250
|
-
}
|
|
4251
|
-
else if (
|
|
4252
|
-
// someone unfollowed this feed
|
|
4253
|
-
event.follow.target_feed.fid === this.fid) {
|
|
4254
|
-
const source = event.follow.source_feed;
|
|
4255
|
-
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4256
|
-
this.state.next((currentState) => {
|
|
4257
|
-
const newState = { ...currentState, ...event.follow.target_feed };
|
|
4258
|
-
if (source.created_by.id === connectedUser?.id) {
|
|
4259
|
-
newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
|
|
4260
|
-
}
|
|
4261
|
-
newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
|
|
4262
|
-
return newState;
|
|
4263
|
-
});
|
|
4264
|
-
}
|
|
4327
|
+
this.handleFollowDeleted(event.follow);
|
|
4328
|
+
},
|
|
4329
|
+
'feeds.follow.updated': (_event) => {
|
|
4330
|
+
handleFollowUpdated(this.currentState);
|
|
4265
4331
|
},
|
|
4266
|
-
'feeds.follow.updated': Feed.noop,
|
|
4267
4332
|
'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
|
|
4268
4333
|
'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
|
|
4269
4334
|
'feeds.comment.reaction.updated': Feed.noop,
|
|
@@ -4271,13 +4336,11 @@ class Feed extends FeedApi {
|
|
|
4271
4336
|
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
4272
4337
|
this.state.next((currentState) => {
|
|
4273
4338
|
let newState;
|
|
4274
|
-
if (
|
|
4339
|
+
if (typeof currentState.members !== 'undefined') {
|
|
4275
4340
|
newState ?? (newState = {
|
|
4276
4341
|
...currentState,
|
|
4277
4342
|
});
|
|
4278
|
-
newState.members =
|
|
4279
|
-
event.member,
|
|
4280
|
-
];
|
|
4343
|
+
newState.members = [event.member, ...currentState.members];
|
|
4281
4344
|
}
|
|
4282
4345
|
if (connectedUser?.id === event.member.user.id) {
|
|
4283
4346
|
newState ?? (newState = {
|
|
@@ -4324,6 +4387,10 @@ class Feed extends FeedApi {
|
|
|
4324
4387
|
return newState ?? currentState;
|
|
4325
4388
|
});
|
|
4326
4389
|
},
|
|
4390
|
+
'feeds.notification_feed.updated': (event) => {
|
|
4391
|
+
console.info('notification feed updated', event);
|
|
4392
|
+
// TODO: handle notification feed updates
|
|
4393
|
+
},
|
|
4327
4394
|
// the poll events should be removed from here
|
|
4328
4395
|
'feeds.poll.closed': Feed.noop,
|
|
4329
4396
|
'feeds.poll.deleted': Feed.noop,
|
|
@@ -4356,6 +4423,7 @@ class Feed extends FeedApi {
|
|
|
4356
4423
|
is_loading: false,
|
|
4357
4424
|
is_loading_activities: false,
|
|
4358
4425
|
comments_by_entity_id: {},
|
|
4426
|
+
watch,
|
|
4359
4427
|
});
|
|
4360
4428
|
this.client = client;
|
|
4361
4429
|
}
|
|
@@ -4435,6 +4503,8 @@ class Feed extends FeedApi {
|
|
|
4435
4503
|
}
|
|
4436
4504
|
}
|
|
4437
4505
|
else {
|
|
4506
|
+
// Empty queue when reinitializing the state
|
|
4507
|
+
this.stateUpdateQueue.clear();
|
|
4438
4508
|
const responseCopy = {
|
|
4439
4509
|
...response,
|
|
4440
4510
|
...response.feed,
|
|
@@ -4453,7 +4523,11 @@ class Feed extends FeedApi {
|
|
|
4453
4523
|
if (!request?.following_pagination?.limit) {
|
|
4454
4524
|
delete nextState.following;
|
|
4455
4525
|
}
|
|
4526
|
+
if (response.members.length === 0 && response.feed.member_count > 0) {
|
|
4527
|
+
delete nextState.members;
|
|
4528
|
+
}
|
|
4456
4529
|
nextState.last_get_or_create_request_config = request;
|
|
4530
|
+
nextState.watch = request?.watch ? request.watch : currentState.watch;
|
|
4457
4531
|
return nextState;
|
|
4458
4532
|
});
|
|
4459
4533
|
}
|
|
@@ -4467,6 +4541,56 @@ class Feed extends FeedApi {
|
|
|
4467
4541
|
});
|
|
4468
4542
|
}
|
|
4469
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
|
+
}
|
|
4470
4594
|
handleBookmarkAdded(event) {
|
|
4471
4595
|
const currentActivities = this.currentState.activities;
|
|
4472
4596
|
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
@@ -4511,70 +4635,85 @@ class Feed extends FeedApi {
|
|
|
4511
4635
|
}
|
|
4512
4636
|
return commentIndex;
|
|
4513
4637
|
}
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
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
|
+
];
|
|
4527
4652
|
this.state.next((currentState) => {
|
|
4528
|
-
const
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
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
|
+
}
|
|
4533
4688
|
return {
|
|
4534
4689
|
...currentState,
|
|
4535
|
-
|
|
4690
|
+
comments_by_entity_id: newCommentsByEntityId,
|
|
4536
4691
|
};
|
|
4537
4692
|
});
|
|
4538
4693
|
}
|
|
4539
|
-
async loadNextPageComments({
|
|
4694
|
+
async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
|
|
4540
4695
|
let error;
|
|
4541
4696
|
try {
|
|
4542
4697
|
this.state.next((currentState) => ({
|
|
4543
4698
|
...currentState,
|
|
4544
4699
|
comments_by_entity_id: {
|
|
4545
4700
|
...currentState.comments_by_entity_id,
|
|
4546
|
-
[
|
|
4547
|
-
...currentState.comments_by_entity_id[
|
|
4701
|
+
[entityId]: {
|
|
4702
|
+
...currentState.comments_by_entity_id[entityId],
|
|
4548
4703
|
pagination: {
|
|
4549
|
-
...currentState.comments_by_entity_id[
|
|
4704
|
+
...currentState.comments_by_entity_id[entityId]?.pagination,
|
|
4550
4705
|
loading_next_page: true,
|
|
4551
4706
|
},
|
|
4552
4707
|
},
|
|
4553
4708
|
},
|
|
4554
4709
|
}));
|
|
4555
|
-
const { next
|
|
4556
|
-
this.
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
newPagination.sort = sort;
|
|
4563
|
-
}
|
|
4564
|
-
return {
|
|
4565
|
-
...currentState,
|
|
4566
|
-
comments_by_entity_id: {
|
|
4567
|
-
...currentState.comments_by_entity_id,
|
|
4568
|
-
[forId]: {
|
|
4569
|
-
...currentState.comments_by_entity_id[forId],
|
|
4570
|
-
parent_id: parentId,
|
|
4571
|
-
pagination: newPagination,
|
|
4572
|
-
comments: currentState.comments_by_entity_id[forId]?.comments
|
|
4573
|
-
? currentState.comments_by_entity_id[forId].comments?.concat(comments)
|
|
4574
|
-
: comments,
|
|
4575
|
-
},
|
|
4576
|
-
},
|
|
4577
|
-
};
|
|
4710
|
+
const { next, comments } = await base();
|
|
4711
|
+
this.loadCommentsIntoState({
|
|
4712
|
+
entityId,
|
|
4713
|
+
comments,
|
|
4714
|
+
entityParentId,
|
|
4715
|
+
next,
|
|
4716
|
+
sort,
|
|
4578
4717
|
});
|
|
4579
4718
|
}
|
|
4580
4719
|
catch (e) {
|
|
@@ -4585,10 +4724,10 @@ class Feed extends FeedApi {
|
|
|
4585
4724
|
...currentState,
|
|
4586
4725
|
comments_by_entity_id: {
|
|
4587
4726
|
...currentState.comments_by_entity_id,
|
|
4588
|
-
[
|
|
4589
|
-
...currentState.comments_by_entity_id[
|
|
4727
|
+
[entityId]: {
|
|
4728
|
+
...currentState.comments_by_entity_id[entityId],
|
|
4590
4729
|
pagination: {
|
|
4591
|
-
...currentState.comments_by_entity_id[
|
|
4730
|
+
...currentState.comments_by_entity_id[entityId]?.pagination,
|
|
4592
4731
|
loading_next_page: false,
|
|
4593
4732
|
},
|
|
4594
4733
|
},
|
|
@@ -4611,7 +4750,7 @@ class Feed extends FeedApi {
|
|
|
4611
4750
|
return;
|
|
4612
4751
|
}
|
|
4613
4752
|
await this.loadNextPageComments({
|
|
4614
|
-
|
|
4753
|
+
entityId: activity.id,
|
|
4615
4754
|
base: () => this.client.getComments({
|
|
4616
4755
|
...request,
|
|
4617
4756
|
sort,
|
|
@@ -4634,7 +4773,7 @@ class Feed extends FeedApi {
|
|
|
4634
4773
|
return;
|
|
4635
4774
|
}
|
|
4636
4775
|
await this.loadNextPageComments({
|
|
4637
|
-
|
|
4776
|
+
entityId: comment.id,
|
|
4638
4777
|
base: () => this.client.getCommentReplies({
|
|
4639
4778
|
...request,
|
|
4640
4779
|
comment_id: comment.id,
|
|
@@ -4644,7 +4783,7 @@ class Feed extends FeedApi {
|
|
|
4644
4783
|
Constants.DEFAULT_COMMENT_PAGINATION,
|
|
4645
4784
|
next: currentNextCursor,
|
|
4646
4785
|
}),
|
|
4647
|
-
|
|
4786
|
+
entityParentId: comment.parent_id ?? comment.object_id,
|
|
4648
4787
|
sort,
|
|
4649
4788
|
});
|
|
4650
4789
|
}
|
|
@@ -4674,17 +4813,19 @@ class Feed extends FeedApi {
|
|
|
4674
4813
|
next: currentNextCursor,
|
|
4675
4814
|
sort,
|
|
4676
4815
|
});
|
|
4677
|
-
this.state.next((currentState) =>
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
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
|
+
});
|
|
4688
4829
|
}
|
|
4689
4830
|
catch (e) {
|
|
4690
4831
|
error = e;
|
|
@@ -4737,7 +4878,7 @@ class Feed extends FeedApi {
|
|
|
4737
4878
|
this.state.next((currentState) => ({
|
|
4738
4879
|
...currentState,
|
|
4739
4880
|
members: currentState.members
|
|
4740
|
-
? currentState.members
|
|
4881
|
+
? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
|
|
4741
4882
|
: members,
|
|
4742
4883
|
member_pagination: {
|
|
4743
4884
|
...currentState.member_pagination,
|
|
@@ -5324,13 +5465,17 @@ class FeedsClient extends FeedsApi {
|
|
|
5324
5465
|
};
|
|
5325
5466
|
this.eventDispatcher.dispatch(networkEvent);
|
|
5326
5467
|
};
|
|
5327
|
-
this.getOrCreateActiveFeed = (group, id, data) => {
|
|
5468
|
+
this.getOrCreateActiveFeed = (group, id, data, watch) => {
|
|
5328
5469
|
const fid = `${group}:${id}`;
|
|
5329
5470
|
if (this.activeFeeds[fid]) {
|
|
5330
|
-
|
|
5471
|
+
const feed = this.activeFeeds[fid];
|
|
5472
|
+
if (watch && !feed.currentState.watch) {
|
|
5473
|
+
feed.handleWatchStarted();
|
|
5474
|
+
}
|
|
5475
|
+
return feed;
|
|
5331
5476
|
}
|
|
5332
5477
|
else {
|
|
5333
|
-
const feed = new Feed(this, group, id, data);
|
|
5478
|
+
const feed = new Feed(this, group, id, data, watch);
|
|
5334
5479
|
this.activeFeeds[fid] = feed;
|
|
5335
5480
|
return feed;
|
|
5336
5481
|
}
|
|
@@ -5359,6 +5504,11 @@ class FeedsClient extends FeedsApi {
|
|
|
5359
5504
|
}
|
|
5360
5505
|
}
|
|
5361
5506
|
}
|
|
5507
|
+
else {
|
|
5508
|
+
for (const activeFeed of Object.values(this.activeFeeds)) {
|
|
5509
|
+
activeFeed.handleWatchStopped();
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5362
5512
|
break;
|
|
5363
5513
|
}
|
|
5364
5514
|
case 'feeds.feed.created': {
|
|
@@ -5462,7 +5612,7 @@ class FeedsClient extends FeedsApi {
|
|
|
5462
5612
|
}
|
|
5463
5613
|
async queryFeeds(request) {
|
|
5464
5614
|
const response = await this.feedsQueryFeeds(request);
|
|
5465
|
-
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));
|
|
5466
5616
|
return {
|
|
5467
5617
|
feeds,
|
|
5468
5618
|
next: response.next,
|
|
@@ -5471,6 +5621,52 @@ class FeedsClient extends FeedsApi {
|
|
|
5471
5621
|
duration: response.duration,
|
|
5472
5622
|
};
|
|
5473
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
|
+
}
|
|
5474
5670
|
findActiveFeedByActivityId(activityId) {
|
|
5475
5671
|
return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
|
|
5476
5672
|
}
|
|
@@ -5802,7 +5998,7 @@ const selector = ({ own_follows }) => ({
|
|
|
5802
5998
|
* that can then be used on the UI. The entity can be either an ActivityResponse or a CommentResponse
|
|
5803
5999
|
* as the hook determines internally which APIs it is supposed to use, while taking the
|
|
5804
6000
|
* correct ownCapabilities into account.
|
|
5805
|
-
* @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.
|
|
5806
6002
|
* @param type - The type of reaction we want to add or remove.
|
|
5807
6003
|
*/
|
|
5808
6004
|
const useReactionActions = ({ entity, type, }) => {
|
|
@@ -5833,6 +6029,31 @@ const useReactionActions = ({ entity, type, }) => {
|
|
|
5833
6029
|
return react.useMemo(() => ({ addReaction, removeReaction, toggleReaction }), [addReaction, removeReaction, toggleReaction]);
|
|
5834
6030
|
};
|
|
5835
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
|
+
|
|
5836
6057
|
const StreamFeeds = ({ client, children }) => {
|
|
5837
6058
|
return (jsxRuntime.jsx(StreamFeedsContext.Provider, { value: client, children: children }));
|
|
5838
6059
|
};
|
|
@@ -5847,6 +6068,7 @@ exports.StreamFeed = StreamFeed;
|
|
|
5847
6068
|
exports.StreamFeedContext = StreamFeedContext;
|
|
5848
6069
|
exports.StreamFeeds = StreamFeeds;
|
|
5849
6070
|
exports.StreamFeedsContext = StreamFeedsContext;
|
|
6071
|
+
exports.useBookmarkActions = useBookmarkActions;
|
|
5850
6072
|
exports.useClientConnectedUser = useClientConnectedUser;
|
|
5851
6073
|
exports.useComments = useComments;
|
|
5852
6074
|
exports.useCreateFeedsClient = useCreateFeedsClient;
|