@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
|
@@ -1145,6 +1145,18 @@ decoders.MuteResponse = (input) => {
|
|
|
1145
1145
|
};
|
|
1146
1146
|
return decode(typeMappings, input);
|
|
1147
1147
|
};
|
|
1148
|
+
decoders.NotificationFeedUpdatedEvent = (input) => {
|
|
1149
|
+
const typeMappings = {
|
|
1150
|
+
created_at: { type: 'DatetimeType', isSingle: true },
|
|
1151
|
+
received_at: { type: 'DatetimeType', isSingle: true },
|
|
1152
|
+
aggregated_activities: {
|
|
1153
|
+
type: 'AggregatedActivityResponse',
|
|
1154
|
+
isSingle: false,
|
|
1155
|
+
},
|
|
1156
|
+
notification_status: { type: 'NotificationStatusResponse', isSingle: true },
|
|
1157
|
+
};
|
|
1158
|
+
return decode(typeMappings, input);
|
|
1159
|
+
};
|
|
1148
1160
|
decoders.NotificationStatusResponse = (input) => {
|
|
1149
1161
|
const typeMappings = {
|
|
1150
1162
|
last_seen_at: { type: 'DatetimeType', isSingle: true },
|
|
@@ -2620,7 +2632,6 @@ class FeedsApi {
|
|
|
2620
2632
|
}
|
|
2621
2633
|
async updateLiveLocation(request) {
|
|
2622
2634
|
const body = {
|
|
2623
|
-
created_by_device_id: request?.created_by_device_id,
|
|
2624
2635
|
message_id: request?.message_id,
|
|
2625
2636
|
end_at: request?.end_at,
|
|
2626
2637
|
latitude: request?.latitude,
|
|
@@ -3704,6 +3715,7 @@ const eventDecoderMapping = {
|
|
|
3704
3715
|
'feeds.follow.created': (data) => decoders.FollowCreatedEvent(data),
|
|
3705
3716
|
'feeds.follow.deleted': (data) => decoders.FollowDeletedEvent(data),
|
|
3706
3717
|
'feeds.follow.updated': (data) => decoders.FollowUpdatedEvent(data),
|
|
3718
|
+
'feeds.notification_feed.updated': (data) => decoders.NotificationFeedUpdatedEvent(data),
|
|
3707
3719
|
'feeds.poll.closed': (data) => decoders.PollClosedFeedEvent(data),
|
|
3708
3720
|
'feeds.poll.deleted': (data) => decoders.PollDeletedFeedEvent(data),
|
|
3709
3721
|
'feeds.poll.updated': (data) => decoders.PollUpdatedFeedEvent(data),
|
|
@@ -4036,6 +4048,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
|
|
|
4036
4048
|
return updateActivityInActivities(updatedActivity, activities);
|
|
4037
4049
|
};
|
|
4038
4050
|
|
|
4051
|
+
const isFeedResponse = (follow) => {
|
|
4052
|
+
return 'created_by' in follow;
|
|
4053
|
+
};
|
|
4054
|
+
const handleFollowCreated = (follow, currentState, currentFeedId, connectedUserId) => {
|
|
4055
|
+
// filter non-accepted follows (the way getOrCreate does by default)
|
|
4056
|
+
if (follow.status !== 'accepted') {
|
|
4057
|
+
return { changed: false, data: currentState };
|
|
4058
|
+
}
|
|
4059
|
+
let newState = { ...currentState };
|
|
4060
|
+
// this feed followed someone
|
|
4061
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
4062
|
+
newState = {
|
|
4063
|
+
...newState,
|
|
4064
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4065
|
+
...follow.source_feed,
|
|
4066
|
+
};
|
|
4067
|
+
// Only update if following array already exists
|
|
4068
|
+
if (currentState.following !== undefined) {
|
|
4069
|
+
newState.following = [follow, ...currentState.following];
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
else if (
|
|
4073
|
+
// someone followed this feed
|
|
4074
|
+
follow.target_feed.fid === currentFeedId) {
|
|
4075
|
+
const source = follow.source_feed;
|
|
4076
|
+
newState = {
|
|
4077
|
+
...newState,
|
|
4078
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4079
|
+
...follow.target_feed,
|
|
4080
|
+
};
|
|
4081
|
+
if (source.created_by.id === connectedUserId) {
|
|
4082
|
+
newState.own_follows = currentState.own_follows
|
|
4083
|
+
? currentState.own_follows.concat(follow)
|
|
4084
|
+
: [follow];
|
|
4085
|
+
}
|
|
4086
|
+
// Only update if followers array already exists
|
|
4087
|
+
if (currentState.followers !== undefined) {
|
|
4088
|
+
newState.followers = [follow, ...currentState.followers];
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
return { changed: true, data: newState };
|
|
4092
|
+
};
|
|
4093
|
+
const handleFollowDeleted = (follow, currentState, currentFeedId, connectedUserId) => {
|
|
4094
|
+
let newState = { ...currentState };
|
|
4095
|
+
// this feed unfollowed someone
|
|
4096
|
+
if (follow.source_feed.fid === currentFeedId) {
|
|
4097
|
+
newState = {
|
|
4098
|
+
...newState,
|
|
4099
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4100
|
+
...follow.source_feed,
|
|
4101
|
+
};
|
|
4102
|
+
// Only update if following array already exists
|
|
4103
|
+
if (currentState.following !== undefined) {
|
|
4104
|
+
newState.following = currentState.following.filter((followItem) => followItem.target_feed.fid !== follow.target_feed.fid);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
else if (
|
|
4108
|
+
// someone unfollowed this feed
|
|
4109
|
+
follow.target_feed.fid === currentFeedId) {
|
|
4110
|
+
const source = follow.source_feed;
|
|
4111
|
+
newState = {
|
|
4112
|
+
...newState,
|
|
4113
|
+
// Update FeedResponse fields, that has the new follower/following count
|
|
4114
|
+
...follow.target_feed,
|
|
4115
|
+
};
|
|
4116
|
+
if (isFeedResponse(source) &&
|
|
4117
|
+
source.created_by.id === connectedUserId &&
|
|
4118
|
+
currentState.own_follows !== undefined) {
|
|
4119
|
+
newState.own_follows = currentState.own_follows.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
|
|
4120
|
+
}
|
|
4121
|
+
// Only update if followers array already exists
|
|
4122
|
+
if (currentState.followers !== undefined) {
|
|
4123
|
+
newState.followers = currentState.followers.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
return { changed: true, data: newState };
|
|
4127
|
+
};
|
|
4128
|
+
const handleFollowUpdated = (currentState) => {
|
|
4129
|
+
// For now, we'll treat follow updates as no-ops since the current implementation does
|
|
4130
|
+
// This can be enhanced later if needed
|
|
4131
|
+
return { changed: false, data: currentState };
|
|
4132
|
+
};
|
|
4133
|
+
|
|
4039
4134
|
const checkHasAnotherPage = (v, cursor) => (typeof v === 'undefined' && typeof cursor === 'undefined') ||
|
|
4040
4135
|
typeof cursor === 'string';
|
|
4041
4136
|
const isCommentResponse = (entity) => {
|
|
@@ -4044,11 +4139,41 @@ const isCommentResponse = (entity) => {
|
|
|
4044
4139
|
const Constants = {
|
|
4045
4140
|
DEFAULT_COMMENT_PAGINATION: 'first',
|
|
4046
4141
|
};
|
|
4142
|
+
const uniqueArrayMerge = (existingArray, arrayToMerge, getKey) => {
|
|
4143
|
+
const existing = new Set();
|
|
4144
|
+
existingArray.forEach((value) => {
|
|
4145
|
+
const key = getKey(value);
|
|
4146
|
+
existing.add(key);
|
|
4147
|
+
});
|
|
4148
|
+
const filteredArrayToMerge = arrayToMerge.filter((value) => {
|
|
4149
|
+
const key = getKey(value);
|
|
4150
|
+
return !existing.has(key);
|
|
4151
|
+
});
|
|
4152
|
+
return existingArray.concat(filteredArrayToMerge);
|
|
4153
|
+
};
|
|
4154
|
+
|
|
4155
|
+
const shouldUpdateState = ({ stateUpdateId, stateUpdateQueue, watch, }) => {
|
|
4156
|
+
if (!watch) {
|
|
4157
|
+
return true;
|
|
4158
|
+
}
|
|
4159
|
+
if (watch && stateUpdateQueue.has(stateUpdateId)) {
|
|
4160
|
+
stateUpdateQueue.delete(stateUpdateId);
|
|
4161
|
+
return false;
|
|
4162
|
+
}
|
|
4163
|
+
stateUpdateQueue.add(stateUpdateId);
|
|
4164
|
+
return true;
|
|
4165
|
+
};
|
|
4166
|
+
const getStateUpdateQueueIdForFollow = (follow) => {
|
|
4167
|
+
return `follow${follow.source_feed.fid}-${follow.target_feed.fid}`;
|
|
4168
|
+
};
|
|
4169
|
+
const getStateUpdateQueueIdForUnfollow = (follow) => {
|
|
4170
|
+
return `unfollow${follow.source_feed.fid}-${follow.target_feed.fid}`;
|
|
4171
|
+
};
|
|
4047
4172
|
|
|
4048
4173
|
class Feed extends FeedApi {
|
|
4049
|
-
constructor(client, groupId, id, data) {
|
|
4050
|
-
// Need this ugly cast because fileUpload endpoints :(
|
|
4174
|
+
constructor(client, groupId, id, data, watch = false) {
|
|
4051
4175
|
super(client, groupId, id);
|
|
4176
|
+
this.stateUpdateQueue = new Set();
|
|
4052
4177
|
this.eventHandlers = {
|
|
4053
4178
|
'feeds.activity.added': (event) => {
|
|
4054
4179
|
const currentActivities = this.currentState.activities;
|
|
@@ -4194,74 +4319,14 @@ class Feed extends FeedApi {
|
|
|
4194
4319
|
'feeds.feed_group.changed': Feed.noop,
|
|
4195
4320
|
'feeds.feed_group.deleted': Feed.noop,
|
|
4196
4321
|
'feeds.follow.created': (event) => {
|
|
4197
|
-
|
|
4198
|
-
if (event.follow.status !== 'accepted')
|
|
4199
|
-
return;
|
|
4200
|
-
// this feed followed someone
|
|
4201
|
-
if (event.follow.source_feed.fid === this.fid) {
|
|
4202
|
-
this.state.next((currentState) => {
|
|
4203
|
-
const newState = {
|
|
4204
|
-
...currentState,
|
|
4205
|
-
...event.follow.source_feed,
|
|
4206
|
-
};
|
|
4207
|
-
if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
|
|
4208
|
-
// TODO: respect sort
|
|
4209
|
-
newState.following = currentState.following
|
|
4210
|
-
? currentState.following.concat(event.follow)
|
|
4211
|
-
: [event.follow];
|
|
4212
|
-
}
|
|
4213
|
-
return newState;
|
|
4214
|
-
});
|
|
4215
|
-
}
|
|
4216
|
-
else if (
|
|
4217
|
-
// someone followed this feed
|
|
4218
|
-
event.follow.target_feed.fid === this.fid) {
|
|
4219
|
-
const source = event.follow.source_feed;
|
|
4220
|
-
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4221
|
-
this.state.next((currentState) => {
|
|
4222
|
-
const newState = { ...currentState, ...event.follow.target_feed };
|
|
4223
|
-
if (source.created_by.id === connectedUser?.id) {
|
|
4224
|
-
newState.own_follows = currentState.own_follows
|
|
4225
|
-
? currentState.own_follows.concat(event.follow)
|
|
4226
|
-
: [event.follow];
|
|
4227
|
-
}
|
|
4228
|
-
if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
|
|
4229
|
-
// TODO: respect sort
|
|
4230
|
-
newState.followers = currentState.followers
|
|
4231
|
-
? currentState.followers.concat(event.follow)
|
|
4232
|
-
: [event.follow];
|
|
4233
|
-
}
|
|
4234
|
-
return newState;
|
|
4235
|
-
});
|
|
4236
|
-
}
|
|
4322
|
+
this.handleFollowCreated(event.follow);
|
|
4237
4323
|
},
|
|
4238
4324
|
'feeds.follow.deleted': (event) => {
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
...currentState,
|
|
4244
|
-
...event.follow.source_feed,
|
|
4245
|
-
following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
|
|
4246
|
-
};
|
|
4247
|
-
});
|
|
4248
|
-
}
|
|
4249
|
-
else if (
|
|
4250
|
-
// someone unfollowed this feed
|
|
4251
|
-
event.follow.target_feed.fid === this.fid) {
|
|
4252
|
-
const source = event.follow.source_feed;
|
|
4253
|
-
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4254
|
-
this.state.next((currentState) => {
|
|
4255
|
-
const newState = { ...currentState, ...event.follow.target_feed };
|
|
4256
|
-
if (source.created_by.id === connectedUser?.id) {
|
|
4257
|
-
newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
|
|
4258
|
-
}
|
|
4259
|
-
newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
|
|
4260
|
-
return newState;
|
|
4261
|
-
});
|
|
4262
|
-
}
|
|
4325
|
+
this.handleFollowDeleted(event.follow);
|
|
4326
|
+
},
|
|
4327
|
+
'feeds.follow.updated': (_event) => {
|
|
4328
|
+
handleFollowUpdated(this.currentState);
|
|
4263
4329
|
},
|
|
4264
|
-
'feeds.follow.updated': Feed.noop,
|
|
4265
4330
|
'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
|
|
4266
4331
|
'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
|
|
4267
4332
|
'feeds.comment.reaction.updated': Feed.noop,
|
|
@@ -4269,13 +4334,11 @@ class Feed extends FeedApi {
|
|
|
4269
4334
|
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
4270
4335
|
this.state.next((currentState) => {
|
|
4271
4336
|
let newState;
|
|
4272
|
-
if (
|
|
4337
|
+
if (typeof currentState.members !== 'undefined') {
|
|
4273
4338
|
newState ?? (newState = {
|
|
4274
4339
|
...currentState,
|
|
4275
4340
|
});
|
|
4276
|
-
newState.members =
|
|
4277
|
-
event.member,
|
|
4278
|
-
];
|
|
4341
|
+
newState.members = [event.member, ...currentState.members];
|
|
4279
4342
|
}
|
|
4280
4343
|
if (connectedUser?.id === event.member.user.id) {
|
|
4281
4344
|
newState ?? (newState = {
|
|
@@ -4322,6 +4385,10 @@ class Feed extends FeedApi {
|
|
|
4322
4385
|
return newState ?? currentState;
|
|
4323
4386
|
});
|
|
4324
4387
|
},
|
|
4388
|
+
'feeds.notification_feed.updated': (event) => {
|
|
4389
|
+
console.info('notification feed updated', event);
|
|
4390
|
+
// TODO: handle notification feed updates
|
|
4391
|
+
},
|
|
4325
4392
|
// the poll events should be removed from here
|
|
4326
4393
|
'feeds.poll.closed': Feed.noop,
|
|
4327
4394
|
'feeds.poll.deleted': Feed.noop,
|
|
@@ -4354,6 +4421,7 @@ class Feed extends FeedApi {
|
|
|
4354
4421
|
is_loading: false,
|
|
4355
4422
|
is_loading_activities: false,
|
|
4356
4423
|
comments_by_entity_id: {},
|
|
4424
|
+
watch,
|
|
4357
4425
|
});
|
|
4358
4426
|
this.client = client;
|
|
4359
4427
|
}
|
|
@@ -4433,6 +4501,8 @@ class Feed extends FeedApi {
|
|
|
4433
4501
|
}
|
|
4434
4502
|
}
|
|
4435
4503
|
else {
|
|
4504
|
+
// Empty queue when reinitializing the state
|
|
4505
|
+
this.stateUpdateQueue.clear();
|
|
4436
4506
|
const responseCopy = {
|
|
4437
4507
|
...response,
|
|
4438
4508
|
...response.feed,
|
|
@@ -4451,7 +4521,11 @@ class Feed extends FeedApi {
|
|
|
4451
4521
|
if (!request?.following_pagination?.limit) {
|
|
4452
4522
|
delete nextState.following;
|
|
4453
4523
|
}
|
|
4524
|
+
if (response.members.length === 0 && response.feed.member_count > 0) {
|
|
4525
|
+
delete nextState.members;
|
|
4526
|
+
}
|
|
4454
4527
|
nextState.last_get_or_create_request_config = request;
|
|
4528
|
+
nextState.watch = request?.watch ? request.watch : currentState.watch;
|
|
4455
4529
|
return nextState;
|
|
4456
4530
|
});
|
|
4457
4531
|
}
|
|
@@ -4465,6 +4539,56 @@ class Feed extends FeedApi {
|
|
|
4465
4539
|
});
|
|
4466
4540
|
}
|
|
4467
4541
|
}
|
|
4542
|
+
/**
|
|
4543
|
+
* @internal
|
|
4544
|
+
*/
|
|
4545
|
+
handleFollowCreated(follow) {
|
|
4546
|
+
if (!shouldUpdateState({
|
|
4547
|
+
stateUpdateId: getStateUpdateQueueIdForFollow(follow),
|
|
4548
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
4549
|
+
watch: this.currentState.watch,
|
|
4550
|
+
})) {
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4554
|
+
const result = handleFollowCreated(follow, this.currentState, this.fid, connectedUser?.id);
|
|
4555
|
+
if (result.changed) {
|
|
4556
|
+
this.state.next(result.data);
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
/**
|
|
4560
|
+
* @internal
|
|
4561
|
+
*/
|
|
4562
|
+
handleFollowDeleted(follow) {
|
|
4563
|
+
if (!shouldUpdateState({
|
|
4564
|
+
stateUpdateId: getStateUpdateQueueIdForUnfollow(follow),
|
|
4565
|
+
stateUpdateQueue: this.stateUpdateQueue,
|
|
4566
|
+
watch: this.currentState.watch,
|
|
4567
|
+
})) {
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4570
|
+
const connectedUser = this.client.state.getLatestValue().connected_user;
|
|
4571
|
+
const result = handleFollowDeleted(follow, this.currentState, this.fid, connectedUser?.id);
|
|
4572
|
+
{
|
|
4573
|
+
this.state.next(result.data);
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
/**
|
|
4577
|
+
* @internal
|
|
4578
|
+
*/
|
|
4579
|
+
handleWatchStopped() {
|
|
4580
|
+
this.state.partialNext({
|
|
4581
|
+
watch: false,
|
|
4582
|
+
});
|
|
4583
|
+
}
|
|
4584
|
+
/**
|
|
4585
|
+
* @internal
|
|
4586
|
+
*/
|
|
4587
|
+
handleWatchStarted() {
|
|
4588
|
+
this.state.partialNext({
|
|
4589
|
+
watch: true,
|
|
4590
|
+
});
|
|
4591
|
+
}
|
|
4468
4592
|
handleBookmarkAdded(event) {
|
|
4469
4593
|
const currentActivities = this.currentState.activities;
|
|
4470
4594
|
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
@@ -4509,70 +4633,85 @@ class Feed extends FeedApi {
|
|
|
4509
4633
|
}
|
|
4510
4634
|
return commentIndex;
|
|
4511
4635
|
}
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4636
|
+
/**
|
|
4637
|
+
* Load child comments of entity (activity or comment) into the state, if the target entity is comment,
|
|
4638
|
+
* `entityParentId` should be provided (`CommentResponse.parent_id ?? CommentResponse.object_id`).
|
|
4639
|
+
*/
|
|
4640
|
+
loadCommentsIntoState(data) {
|
|
4641
|
+
// add initial (top level) object for processing
|
|
4642
|
+
const traverseArray = [
|
|
4643
|
+
{
|
|
4644
|
+
entityId: data.entityId,
|
|
4645
|
+
entityParentId: data.entityParentId,
|
|
4646
|
+
comments: data.comments,
|
|
4647
|
+
next: data.next,
|
|
4648
|
+
},
|
|
4649
|
+
];
|
|
4525
4650
|
this.state.next((currentState) => {
|
|
4526
|
-
const
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4651
|
+
const newCommentsByEntityId = {
|
|
4652
|
+
...currentState.comments_by_entity_id,
|
|
4653
|
+
};
|
|
4654
|
+
while (traverseArray.length) {
|
|
4655
|
+
const item = traverseArray.pop();
|
|
4656
|
+
const entityId = item.entityId;
|
|
4657
|
+
// go over entity comments and generate new objects
|
|
4658
|
+
// for further processing if there are any replies
|
|
4659
|
+
item.comments.forEach((comment) => {
|
|
4660
|
+
if (!comment.replies?.length)
|
|
4661
|
+
return;
|
|
4662
|
+
traverseArray.push({
|
|
4663
|
+
entityId: comment.id,
|
|
4664
|
+
entityParentId: entityId,
|
|
4665
|
+
comments: comment.replies,
|
|
4666
|
+
next: comment.meta?.next_cursor,
|
|
4667
|
+
});
|
|
4668
|
+
});
|
|
4669
|
+
// omit replies & meta from the comments (transform ThreadedCommentResponse to CommentResponse)
|
|
4670
|
+
// this is somehow faster than copying the whole
|
|
4671
|
+
// object and deleting the desired properties
|
|
4672
|
+
const newComments = item.comments.map(({ replies: _r, meta: _m, ...restOfTheCommentResponse }) => restOfTheCommentResponse);
|
|
4673
|
+
newCommentsByEntityId[entityId] = {
|
|
4674
|
+
...newCommentsByEntityId[entityId],
|
|
4675
|
+
entity_parent_id: item.entityParentId,
|
|
4676
|
+
pagination: {
|
|
4677
|
+
...newCommentsByEntityId[entityId]?.pagination,
|
|
4678
|
+
next: item.next,
|
|
4679
|
+
sort: data.sort,
|
|
4680
|
+
},
|
|
4681
|
+
comments: newCommentsByEntityId[entityId]?.comments
|
|
4682
|
+
? newCommentsByEntityId[entityId].comments?.concat(newComments)
|
|
4683
|
+
: newComments,
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4531
4686
|
return {
|
|
4532
4687
|
...currentState,
|
|
4533
|
-
|
|
4688
|
+
comments_by_entity_id: newCommentsByEntityId,
|
|
4534
4689
|
};
|
|
4535
4690
|
});
|
|
4536
4691
|
}
|
|
4537
|
-
async loadNextPageComments({
|
|
4692
|
+
async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
|
|
4538
4693
|
let error;
|
|
4539
4694
|
try {
|
|
4540
4695
|
this.state.next((currentState) => ({
|
|
4541
4696
|
...currentState,
|
|
4542
4697
|
comments_by_entity_id: {
|
|
4543
4698
|
...currentState.comments_by_entity_id,
|
|
4544
|
-
[
|
|
4545
|
-
...currentState.comments_by_entity_id[
|
|
4699
|
+
[entityId]: {
|
|
4700
|
+
...currentState.comments_by_entity_id[entityId],
|
|
4546
4701
|
pagination: {
|
|
4547
|
-
...currentState.comments_by_entity_id[
|
|
4702
|
+
...currentState.comments_by_entity_id[entityId]?.pagination,
|
|
4548
4703
|
loading_next_page: true,
|
|
4549
4704
|
},
|
|
4550
4705
|
},
|
|
4551
4706
|
},
|
|
4552
4707
|
}));
|
|
4553
|
-
const { next
|
|
4554
|
-
this.
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
newPagination.sort = sort;
|
|
4561
|
-
}
|
|
4562
|
-
return {
|
|
4563
|
-
...currentState,
|
|
4564
|
-
comments_by_entity_id: {
|
|
4565
|
-
...currentState.comments_by_entity_id,
|
|
4566
|
-
[forId]: {
|
|
4567
|
-
...currentState.comments_by_entity_id[forId],
|
|
4568
|
-
parent_id: parentId,
|
|
4569
|
-
pagination: newPagination,
|
|
4570
|
-
comments: currentState.comments_by_entity_id[forId]?.comments
|
|
4571
|
-
? currentState.comments_by_entity_id[forId].comments?.concat(comments)
|
|
4572
|
-
: comments,
|
|
4573
|
-
},
|
|
4574
|
-
},
|
|
4575
|
-
};
|
|
4708
|
+
const { next, comments } = await base();
|
|
4709
|
+
this.loadCommentsIntoState({
|
|
4710
|
+
entityId,
|
|
4711
|
+
comments,
|
|
4712
|
+
entityParentId,
|
|
4713
|
+
next,
|
|
4714
|
+
sort,
|
|
4576
4715
|
});
|
|
4577
4716
|
}
|
|
4578
4717
|
catch (e) {
|
|
@@ -4583,10 +4722,10 @@ class Feed extends FeedApi {
|
|
|
4583
4722
|
...currentState,
|
|
4584
4723
|
comments_by_entity_id: {
|
|
4585
4724
|
...currentState.comments_by_entity_id,
|
|
4586
|
-
[
|
|
4587
|
-
...currentState.comments_by_entity_id[
|
|
4725
|
+
[entityId]: {
|
|
4726
|
+
...currentState.comments_by_entity_id[entityId],
|
|
4588
4727
|
pagination: {
|
|
4589
|
-
...currentState.comments_by_entity_id[
|
|
4728
|
+
...currentState.comments_by_entity_id[entityId]?.pagination,
|
|
4590
4729
|
loading_next_page: false,
|
|
4591
4730
|
},
|
|
4592
4731
|
},
|
|
@@ -4609,7 +4748,7 @@ class Feed extends FeedApi {
|
|
|
4609
4748
|
return;
|
|
4610
4749
|
}
|
|
4611
4750
|
await this.loadNextPageComments({
|
|
4612
|
-
|
|
4751
|
+
entityId: activity.id,
|
|
4613
4752
|
base: () => this.client.getComments({
|
|
4614
4753
|
...request,
|
|
4615
4754
|
sort,
|
|
@@ -4632,7 +4771,7 @@ class Feed extends FeedApi {
|
|
|
4632
4771
|
return;
|
|
4633
4772
|
}
|
|
4634
4773
|
await this.loadNextPageComments({
|
|
4635
|
-
|
|
4774
|
+
entityId: comment.id,
|
|
4636
4775
|
base: () => this.client.getCommentReplies({
|
|
4637
4776
|
...request,
|
|
4638
4777
|
comment_id: comment.id,
|
|
@@ -4642,7 +4781,7 @@ class Feed extends FeedApi {
|
|
|
4642
4781
|
Constants.DEFAULT_COMMENT_PAGINATION,
|
|
4643
4782
|
next: currentNextCursor,
|
|
4644
4783
|
}),
|
|
4645
|
-
|
|
4784
|
+
entityParentId: comment.parent_id ?? comment.object_id,
|
|
4646
4785
|
sort,
|
|
4647
4786
|
});
|
|
4648
4787
|
}
|
|
@@ -4672,17 +4811,19 @@ class Feed extends FeedApi {
|
|
|
4672
4811
|
next: currentNextCursor,
|
|
4673
4812
|
sort,
|
|
4674
4813
|
});
|
|
4675
|
-
this.state.next((currentState) =>
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4814
|
+
this.state.next((currentState) => {
|
|
4815
|
+
return {
|
|
4816
|
+
...currentState,
|
|
4817
|
+
[type]: currentState[type] === undefined
|
|
4818
|
+
? follows
|
|
4819
|
+
: uniqueArrayMerge(currentState[type], follows, (follow) => `${follow.source_feed.fid}-${follow.target_feed.fid}`),
|
|
4820
|
+
[paginationKey]: {
|
|
4821
|
+
...currentState[paginationKey],
|
|
4822
|
+
next: newNextCursor,
|
|
4823
|
+
sort,
|
|
4824
|
+
},
|
|
4825
|
+
};
|
|
4826
|
+
});
|
|
4686
4827
|
}
|
|
4687
4828
|
catch (e) {
|
|
4688
4829
|
error = e;
|
|
@@ -4735,7 +4876,7 @@ class Feed extends FeedApi {
|
|
|
4735
4876
|
this.state.next((currentState) => ({
|
|
4736
4877
|
...currentState,
|
|
4737
4878
|
members: currentState.members
|
|
4738
|
-
? currentState.members
|
|
4879
|
+
? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
|
|
4739
4880
|
: members,
|
|
4740
4881
|
member_pagination: {
|
|
4741
4882
|
...currentState.member_pagination,
|
|
@@ -5322,13 +5463,17 @@ class FeedsClient extends FeedsApi {
|
|
|
5322
5463
|
};
|
|
5323
5464
|
this.eventDispatcher.dispatch(networkEvent);
|
|
5324
5465
|
};
|
|
5325
|
-
this.getOrCreateActiveFeed = (group, id, data) => {
|
|
5466
|
+
this.getOrCreateActiveFeed = (group, id, data, watch) => {
|
|
5326
5467
|
const fid = `${group}:${id}`;
|
|
5327
5468
|
if (this.activeFeeds[fid]) {
|
|
5328
|
-
|
|
5469
|
+
const feed = this.activeFeeds[fid];
|
|
5470
|
+
if (watch && !feed.currentState.watch) {
|
|
5471
|
+
feed.handleWatchStarted();
|
|
5472
|
+
}
|
|
5473
|
+
return feed;
|
|
5329
5474
|
}
|
|
5330
5475
|
else {
|
|
5331
|
-
const feed = new Feed(this, group, id, data);
|
|
5476
|
+
const feed = new Feed(this, group, id, data, watch);
|
|
5332
5477
|
this.activeFeeds[fid] = feed;
|
|
5333
5478
|
return feed;
|
|
5334
5479
|
}
|
|
@@ -5357,6 +5502,11 @@ class FeedsClient extends FeedsApi {
|
|
|
5357
5502
|
}
|
|
5358
5503
|
}
|
|
5359
5504
|
}
|
|
5505
|
+
else {
|
|
5506
|
+
for (const activeFeed of Object.values(this.activeFeeds)) {
|
|
5507
|
+
activeFeed.handleWatchStopped();
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5360
5510
|
break;
|
|
5361
5511
|
}
|
|
5362
5512
|
case 'feeds.feed.created': {
|
|
@@ -5460,7 +5610,7 @@ class FeedsClient extends FeedsApi {
|
|
|
5460
5610
|
}
|
|
5461
5611
|
async queryFeeds(request) {
|
|
5462
5612
|
const response = await this.feedsQueryFeeds(request);
|
|
5463
|
-
const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f));
|
|
5613
|
+
const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f, request?.watch));
|
|
5464
5614
|
return {
|
|
5465
5615
|
feeds,
|
|
5466
5616
|
next: response.next,
|
|
@@ -5469,6 +5619,52 @@ class FeedsClient extends FeedsApi {
|
|
|
5469
5619
|
duration: response.duration,
|
|
5470
5620
|
};
|
|
5471
5621
|
}
|
|
5622
|
+
// For follow API endpoints we update the state after HTTP response to allow queryFeeds with watch: false
|
|
5623
|
+
async follow(request) {
|
|
5624
|
+
const response = await super.follow(request);
|
|
5625
|
+
[response.follow.source_feed.fid, response.follow.target_feed.fid].forEach((fid) => {
|
|
5626
|
+
const feed = this.activeFeeds[fid];
|
|
5627
|
+
if (feed) {
|
|
5628
|
+
feed.handleFollowCreated(response.follow);
|
|
5629
|
+
}
|
|
5630
|
+
});
|
|
5631
|
+
return response;
|
|
5632
|
+
}
|
|
5633
|
+
async followBatch(request) {
|
|
5634
|
+
const response = await super.followBatch(request);
|
|
5635
|
+
response.follows.forEach((follow) => {
|
|
5636
|
+
const feed = this.activeFeeds[follow.source_feed.fid];
|
|
5637
|
+
if (feed) {
|
|
5638
|
+
feed.handleFollowCreated(follow);
|
|
5639
|
+
}
|
|
5640
|
+
});
|
|
5641
|
+
return response;
|
|
5642
|
+
}
|
|
5643
|
+
async unfollow(request) {
|
|
5644
|
+
const response = await super.unfollow(request);
|
|
5645
|
+
[request.source, request.target].forEach((fid) => {
|
|
5646
|
+
const feed = this.activeFeeds[fid];
|
|
5647
|
+
if (feed) {
|
|
5648
|
+
feed.handleFollowDeleted({
|
|
5649
|
+
source_feed: { fid: request.source },
|
|
5650
|
+
target_feed: { fid: request.target },
|
|
5651
|
+
});
|
|
5652
|
+
}
|
|
5653
|
+
});
|
|
5654
|
+
return response;
|
|
5655
|
+
}
|
|
5656
|
+
async stopWatchingFeed(request) {
|
|
5657
|
+
const connectionId = await this.connectionIdManager.getConnectionId();
|
|
5658
|
+
const response = await super.stopWatchingFeed({
|
|
5659
|
+
...request,
|
|
5660
|
+
connection_id: connectionId,
|
|
5661
|
+
});
|
|
5662
|
+
const feed = this.activeFeeds[`${request.feed_group_id}:${request.feed_id}`];
|
|
5663
|
+
if (feed) {
|
|
5664
|
+
feed.handleWatchStopped();
|
|
5665
|
+
}
|
|
5666
|
+
return response;
|
|
5667
|
+
}
|
|
5472
5668
|
findActiveFeedByActivityId(activityId) {
|
|
5473
5669
|
return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
|
|
5474
5670
|
}
|
|
@@ -5800,7 +5996,7 @@ const selector = ({ own_follows }) => ({
|
|
|
5800
5996
|
* that can then be used on the UI. The entity can be either an ActivityResponse or a CommentResponse
|
|
5801
5997
|
* as the hook determines internally which APIs it is supposed to use, while taking the
|
|
5802
5998
|
* correct ownCapabilities into account.
|
|
5803
|
-
* @param entity - The entity to which we want to
|
|
5999
|
+
* @param entity - The entity to which we want to apply reaction actions, can be either ActivityResponse or CommentResponse.
|
|
5804
6000
|
* @param type - The type of reaction we want to add or remove.
|
|
5805
6001
|
*/
|
|
5806
6002
|
const useReactionActions = ({ entity, type, }) => {
|
|
@@ -5831,6 +6027,31 @@ const useReactionActions = ({ entity, type, }) => {
|
|
|
5831
6027
|
return useMemo(() => ({ addReaction, removeReaction, toggleReaction }), [addReaction, removeReaction, toggleReaction]);
|
|
5832
6028
|
};
|
|
5833
6029
|
|
|
6030
|
+
/**
|
|
6031
|
+
* A utility hook that takes in an entity and creates bookmark actions
|
|
6032
|
+
* that can then be used on the UI. The entity is expected to be an ActivityResponse.
|
|
6033
|
+
* @param entity - The entity to which we want to apply reaction actions, expects an ActivityResponse.
|
|
6034
|
+
*/
|
|
6035
|
+
const useBookmarkActions = ({ entity, }) => {
|
|
6036
|
+
const client = useFeedsClient();
|
|
6037
|
+
const hasOwnBookmark = entity.own_bookmarks?.length > 0;
|
|
6038
|
+
const addBookmark = useStableCallback(async () => {
|
|
6039
|
+
await client?.addBookmark({ activity_id: entity.id });
|
|
6040
|
+
});
|
|
6041
|
+
const removeBookmark = useStableCallback(async () => {
|
|
6042
|
+
await client?.deleteBookmark({ activity_id: entity.id });
|
|
6043
|
+
});
|
|
6044
|
+
const toggleBookmark = useStableCallback(async () => {
|
|
6045
|
+
if (hasOwnBookmark) {
|
|
6046
|
+
await removeBookmark();
|
|
6047
|
+
}
|
|
6048
|
+
else {
|
|
6049
|
+
await addBookmark();
|
|
6050
|
+
}
|
|
6051
|
+
});
|
|
6052
|
+
return useMemo(() => ({ addBookmark, removeBookmark, toggleBookmark }), [addBookmark, removeBookmark, toggleBookmark]);
|
|
6053
|
+
};
|
|
6054
|
+
|
|
5834
6055
|
const StreamFeeds = ({ client, children }) => {
|
|
5835
6056
|
return (jsx(StreamFeedsContext.Provider, { value: client, children: children }));
|
|
5836
6057
|
};
|
|
@@ -5841,5 +6062,5 @@ const StreamFeed = ({ feed, children }) => {
|
|
|
5841
6062
|
};
|
|
5842
6063
|
StreamFeed.displayName = 'StreamFeed';
|
|
5843
6064
|
|
|
5844
|
-
export { StreamFeed, StreamFeedContext, StreamFeeds, StreamFeedsContext, useClientConnectedUser, useComments, useCreateFeedsClient, useFeedActivities, useFeedContext, useFeedMetadata, useFeedsClient, useFollowers, useFollowing, useOwnCapabilities, useOwnFollows, useReactionActions, useStateStore, useWsConnectionState };
|
|
6065
|
+
export { StreamFeed, StreamFeedContext, StreamFeeds, StreamFeedsContext, useBookmarkActions, useClientConnectedUser, useComments, useCreateFeedsClient, useFeedActivities, useFeedContext, useFeedMetadata, useFeedsClient, useFollowers, useFollowing, useOwnCapabilities, useOwnFollows, useReactionActions, useStateStore, useWsConnectionState };
|
|
5845
6066
|
//# sourceMappingURL=index-react-bindings.browser.js.map
|