@stream-io/feeds-client 0.1.4 → 0.1.6
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/client-state-hooks/index.ts +2 -0
- package/@react-bindings/hooks/feed-state-hooks/index.ts +7 -0
- package/@react-bindings/hooks/internal/index.ts +1 -0
- package/@react-bindings/hooks/util/index.ts +1 -0
- package/@react-bindings/index.ts +6 -3
- package/CHANGELOG.md +31 -0
- package/dist/@react-bindings/contexts/StreamFeedContext.d.ts +12 -0
- package/dist/@react-bindings/hooks/client-state-hooks/index.d.ts +2 -0
- package/dist/@react-bindings/hooks/client-state-hooks/useClientConnectedUser.d.ts +4 -0
- package/dist/@react-bindings/hooks/client-state-hooks/useWsConnectionState.d.ts +6 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +7 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useComments.d.ts +19 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useFeedActivities.d.ts +11 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useFeedMetadata.d.ts +12 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useFollowers.d.ts +16 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useFollowing.d.ts +16 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useOwnCapabilities.d.ts +33 -0
- package/dist/@react-bindings/hooks/feed-state-hooks/useOwnFollows.d.ts +8 -0
- package/dist/@react-bindings/hooks/internal/index.d.ts +1 -0
- package/dist/@react-bindings/hooks/internal/useStableCallback.d.ts +25 -0
- package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
- package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +17 -0
- package/dist/@react-bindings/index.d.ts +5 -3
- package/dist/@react-bindings/wrappers/StreamFeed.d.ts +12 -0
- package/dist/index-react-bindings.browser.cjs +521 -210
- package/dist/index-react-bindings.browser.cjs.map +1 -1
- package/dist/index-react-bindings.browser.js +514 -212
- package/dist/index-react-bindings.browser.js.map +1 -1
- package/dist/index-react-bindings.node.cjs +521 -210
- package/dist/index-react-bindings.node.cjs.map +1 -1
- package/dist/index-react-bindings.node.js +514 -212
- package/dist/index-react-bindings.node.js.map +1 -1
- package/dist/index.browser.cjs +212 -106
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +209 -107
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +212 -106
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +209 -107
- package/dist/index.node.js.map +1 -1
- package/dist/src/Feed.d.ts +7 -3
- package/dist/src/FeedsClient.d.ts +6 -5
- package/dist/src/types.d.ts +7 -0
- package/dist/src/utils.d.ts +9 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -2
- package/src/Feed.ts +214 -97
- package/src/FeedsClient.ts +22 -12
- package/src/common/ActivitySearchSource.ts +5 -5
- package/src/common/ApiClient.ts +1 -1
- package/src/common/FeedSearchSource.ts +1 -1
- package/src/common/Poll.ts +35 -10
- package/src/common/TokenManager.ts +2 -3
- package/src/common/UserSearchSource.ts +1 -1
- package/src/common/real-time/StableWSConnection.ts +4 -1
- package/src/state-updates/bookmark-utils.test.ts +134 -8
- package/src/state-updates/bookmark-utils.ts +17 -7
- package/src/types.ts +12 -1
- package/src/utils.ts +25 -1
- package/dist/@react-bindings/hooks/clientStateHooks.d.ts +0 -10
- package/dist/@react-bindings/hooks/useComments.d.ts +0 -12
- package/dist/@react-bindings/hooks/useOwnCapabilities.d.ts +0 -33
|
@@ -20,7 +20,7 @@ export class ActivitySearchSource extends BaseSearchSource<ActivityResponse> {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
protected async query(searchQuery: string) {
|
|
23
|
-
const { connectedUser } = this.client.state.getLatestValue();
|
|
23
|
+
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
24
24
|
if (!connectedUser) return { items: [] };
|
|
25
25
|
|
|
26
26
|
const { activities: items, next } = await this.client.queryActivities({
|
|
@@ -39,8 +39,8 @@ export class ActivitySearchSource extends BaseSearchSource<ActivityResponse> {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
// filter: {
|
|
43
|
-
// 'feed.name': { $autocomplete: searchQuery }
|
|
44
|
-
// 'feed.description': { $autocomplete: searchQuery }
|
|
45
|
-
// 'created_by.name': { $autocomplete: searchQuery }
|
|
42
|
+
// filter: {
|
|
43
|
+
// 'feed.name': { $autocomplete: searchQuery }
|
|
44
|
+
// 'feed.description': { $autocomplete: searchQuery }
|
|
45
|
+
// 'created_by.name': { $autocomplete: searchQuery }
|
|
46
46
|
// },
|
package/src/common/ApiClient.ts
CHANGED
|
@@ -21,7 +21,7 @@ export class ApiClient {
|
|
|
21
21
|
private readonly connectionIdManager: ConnectionIdManager,
|
|
22
22
|
options?: FeedsClientOptions,
|
|
23
23
|
) {
|
|
24
|
-
this.baseUrl = options?.base_url ?? 'https://
|
|
24
|
+
this.baseUrl = options?.base_url ?? 'https://feeds.stream-io-api.com';
|
|
25
25
|
this.timeout = options?.timeout ?? 3000;
|
|
26
26
|
this.axiosInstance = axios.create({
|
|
27
27
|
baseURL: this.baseUrl,
|
|
@@ -20,7 +20,7 @@ export class FeedSearchSource extends BaseSearchSource<Feed> {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
protected async query(searchQuery: string) {
|
|
23
|
-
const { connectedUser } = this.client.state.getLatestValue();
|
|
23
|
+
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
24
24
|
if (!connectedUser) return { items: [] };
|
|
25
25
|
|
|
26
26
|
// const channelFilters: ChannelFilters = {
|
package/src/common/Poll.ts
CHANGED
|
@@ -36,7 +36,10 @@ const isPollVoteRemovedEvent = (
|
|
|
36
36
|
|
|
37
37
|
export const isVoteAnswer = (vote: PollVote) => !!vote?.answer_text;
|
|
38
38
|
|
|
39
|
-
export type PollAnswersQueryParams = QueryPollVotesRequest & {
|
|
39
|
+
export type PollAnswersQueryParams = QueryPollVotesRequest & {
|
|
40
|
+
poll_id: string;
|
|
41
|
+
user_id?: string;
|
|
42
|
+
};
|
|
40
43
|
|
|
41
44
|
type OptionId = string;
|
|
42
45
|
|
|
@@ -66,7 +69,9 @@ export class StreamPoll {
|
|
|
66
69
|
);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
private readonly getInitialStateFromPollResponse = (
|
|
72
|
+
private readonly getInitialStateFromPollResponse = (
|
|
73
|
+
poll: PollInitOptions['poll'],
|
|
74
|
+
) => {
|
|
70
75
|
const { own_votes, id, ...pollResponseForState } = poll;
|
|
71
76
|
const { ownAnswer, ownVotes } = own_votes?.reduce<{
|
|
72
77
|
ownVotes: PollVote[];
|
|
@@ -126,7 +131,9 @@ export class StreamPoll {
|
|
|
126
131
|
if (event.poll?.id && event.poll.id !== this.id) return;
|
|
127
132
|
if (!isPollVoteCastedEvent(event as WSEvent)) return;
|
|
128
133
|
const currentState = this.data;
|
|
129
|
-
const isOwnVote =
|
|
134
|
+
const isOwnVote =
|
|
135
|
+
event.poll_vote.user_id ===
|
|
136
|
+
this.client.state.getLatestValue().connected_user?.id;
|
|
130
137
|
let latestAnswers = [...currentState.latest_answers];
|
|
131
138
|
let ownAnswer = currentState.own_answer;
|
|
132
139
|
const ownVotesByOptionId = currentState.own_votes_by_option_id;
|
|
@@ -153,7 +160,12 @@ export class StreamPoll {
|
|
|
153
160
|
);
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
const {
|
|
163
|
+
const {
|
|
164
|
+
answers_count,
|
|
165
|
+
latest_votes_by_option,
|
|
166
|
+
vote_count,
|
|
167
|
+
vote_counts_by_option,
|
|
168
|
+
} = event.poll;
|
|
157
169
|
this.state.partialNext({
|
|
158
170
|
answers_count,
|
|
159
171
|
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
|
|
@@ -173,7 +185,9 @@ export class StreamPoll {
|
|
|
173
185
|
if (event.poll?.id && event.poll.id !== this.id) return;
|
|
174
186
|
if (!isPollVoteChangedEvent(event as WSEvent)) return;
|
|
175
187
|
const currentState = this.data;
|
|
176
|
-
const isOwnVote =
|
|
188
|
+
const isOwnVote =
|
|
189
|
+
event.poll_vote.user_id ===
|
|
190
|
+
this.client.state.getLatestValue().connected_user?.id;
|
|
177
191
|
let latestAnswers = [...currentState.latest_answers];
|
|
178
192
|
let ownAnswer = currentState.own_answer;
|
|
179
193
|
let ownVotesByOptionId = currentState.own_votes_by_option_id;
|
|
@@ -227,7 +241,12 @@ export class StreamPoll {
|
|
|
227
241
|
);
|
|
228
242
|
}
|
|
229
243
|
|
|
230
|
-
const {
|
|
244
|
+
const {
|
|
245
|
+
answers_count,
|
|
246
|
+
latest_votes_by_option,
|
|
247
|
+
vote_count,
|
|
248
|
+
vote_counts_by_option,
|
|
249
|
+
} = event.poll;
|
|
231
250
|
this.state.partialNext({
|
|
232
251
|
answers_count,
|
|
233
252
|
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
|
|
@@ -237,7 +256,7 @@ export class StreamPoll {
|
|
|
237
256
|
latest_answers: latestAnswers,
|
|
238
257
|
last_activity_at: new Date(event.created_at),
|
|
239
258
|
own_answer: ownAnswer,
|
|
240
|
-
own_votes_by_option_id:ownVotesByOptionId,
|
|
259
|
+
own_votes_by_option_id: ownVotesByOptionId,
|
|
241
260
|
max_voted_option_ids: maxVotedOptionIds,
|
|
242
261
|
});
|
|
243
262
|
};
|
|
@@ -246,7 +265,9 @@ export class StreamPoll {
|
|
|
246
265
|
if (event.poll?.id && event.poll.id !== this.id) return;
|
|
247
266
|
if (!isPollVoteRemovedEvent(event as WSEvent)) return;
|
|
248
267
|
const currentState = this.data;
|
|
249
|
-
const isOwnVote =
|
|
268
|
+
const isOwnVote =
|
|
269
|
+
event.poll_vote.user_id ===
|
|
270
|
+
this.client.state.getLatestValue().connected_user?.id;
|
|
250
271
|
let latestAnswers = [...currentState.latest_answers];
|
|
251
272
|
let ownAnswer = currentState.own_answer;
|
|
252
273
|
const ownVotesByOptionId = { ...currentState.own_votes_by_option_id };
|
|
@@ -265,12 +286,16 @@ export class StreamPoll {
|
|
|
265
286
|
event.poll.vote_counts_by_option,
|
|
266
287
|
);
|
|
267
288
|
if (isOwnVote && event.poll_vote.option_id) {
|
|
268
|
-
|
|
269
289
|
delete ownVotesByOptionId[event.poll_vote.option_id];
|
|
270
290
|
}
|
|
271
291
|
}
|
|
272
292
|
|
|
273
|
-
const {
|
|
293
|
+
const {
|
|
294
|
+
answers_count,
|
|
295
|
+
latest_votes_by_option,
|
|
296
|
+
vote_count,
|
|
297
|
+
vote_counts_by_option,
|
|
298
|
+
} = event.poll;
|
|
274
299
|
this.state.partialNext({
|
|
275
300
|
answers_count,
|
|
276
301
|
// @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFunction,
|
|
1
|
+
import { isFunction, sleep } from './utils';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* TokenManager
|
|
@@ -67,12 +67,11 @@ export class TokenManager {
|
|
|
67
67
|
this.token = await tokenProvider();
|
|
68
68
|
} catch (e) {
|
|
69
69
|
const numberOfFailures = ++previousFailuresCount;
|
|
70
|
-
await sleep(
|
|
70
|
+
await sleep(1000);
|
|
71
71
|
if (numberOfFailures === 3) {
|
|
72
72
|
this.loadTokenPromise = null;
|
|
73
73
|
return reject(
|
|
74
74
|
new Error(
|
|
75
|
-
|
|
76
75
|
`Stream error: tried to get token ${numberOfFailures} times, but it failed with ${e}. Check your token provider`,
|
|
77
76
|
{ cause: e },
|
|
78
77
|
),
|
|
@@ -20,7 +20,7 @@ export class UserSearchSource extends BaseSearchSource<UserResponse> {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
protected async query(searchQuery: string) {
|
|
23
|
-
const { connectedUser } = this.client.state.getLatestValue();
|
|
23
|
+
const { connected_user: connectedUser } = this.client.state.getLatestValue();
|
|
24
24
|
if (!connectedUser) return { items: [] };
|
|
25
25
|
|
|
26
26
|
// const channelFilters: ChannelFilters = {
|
|
@@ -487,7 +487,10 @@ export class StableWSConnection {
|
|
|
487
487
|
onmessage = (wsID: number, event: MessageEvent) => {
|
|
488
488
|
if (this.wsID !== wsID) return;
|
|
489
489
|
|
|
490
|
-
this._log('onmessage() - onmessage callback', {
|
|
490
|
+
this._log('onmessage() - onmessage callback', {
|
|
491
|
+
event: { ...event, data: JSON.parse(event.data) },
|
|
492
|
+
wsID,
|
|
493
|
+
});
|
|
491
494
|
let data = typeof event.data === 'string' ? JSON.parse(event.data) : null;
|
|
492
495
|
this.decoders.forEach((decode) => {
|
|
493
496
|
data = decode(data);
|
|
@@ -37,6 +37,7 @@ const createMockActivity = (id: string): ActivityResponse => ({
|
|
|
37
37
|
visibility: 'public',
|
|
38
38
|
bookmark_count: 0,
|
|
39
39
|
comment_count: 0,
|
|
40
|
+
reaction_count: 0,
|
|
40
41
|
share_count: 0,
|
|
41
42
|
attachments: [],
|
|
42
43
|
comments: [],
|
|
@@ -181,6 +182,71 @@ describe('bookmark-utils', () => {
|
|
|
181
182
|
expect(result.own_bookmarks).toHaveLength(1);
|
|
182
183
|
expect(result.own_bookmarks[0]).toEqual(bookmark2);
|
|
183
184
|
});
|
|
185
|
+
|
|
186
|
+
it('should correctly identify bookmarks by activity_id + folder_id + user_id', () => {
|
|
187
|
+
const activity = createMockActivity('activity1');
|
|
188
|
+
const user = createMockUser('user1');
|
|
189
|
+
|
|
190
|
+
// Create two bookmarks with same activity and user but different folders
|
|
191
|
+
const bookmark1 = {
|
|
192
|
+
...createMockBookmark(user, activity),
|
|
193
|
+
folder: {
|
|
194
|
+
id: 'folder1',
|
|
195
|
+
name: 'Folder 1',
|
|
196
|
+
created_at: new Date(),
|
|
197
|
+
updated_at: new Date(),
|
|
198
|
+
custom: {},
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
const bookmark2 = {
|
|
202
|
+
...createMockBookmark(user, activity),
|
|
203
|
+
folder: {
|
|
204
|
+
id: 'folder2',
|
|
205
|
+
name: 'Folder 2',
|
|
206
|
+
created_at: new Date(),
|
|
207
|
+
updated_at: new Date(),
|
|
208
|
+
custom: {},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
activity.own_bookmarks = [bookmark1, bookmark2];
|
|
213
|
+
|
|
214
|
+
// Try to remove bookmark1
|
|
215
|
+
const event = createMockDeletedEvent(bookmark1);
|
|
216
|
+
const result = removeBookmarkFromActivity(event, activity, true);
|
|
217
|
+
|
|
218
|
+
expect(result.changed).toBe(true);
|
|
219
|
+
expect(result.own_bookmarks).toHaveLength(1);
|
|
220
|
+
expect(result.own_bookmarks[0]).toEqual(bookmark2);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should handle bookmarks without folders correctly', () => {
|
|
224
|
+
const activity = createMockActivity('activity1');
|
|
225
|
+
const user = createMockUser('user1');
|
|
226
|
+
|
|
227
|
+
// Create two bookmarks: one with folder, one without
|
|
228
|
+
const bookmarkWithFolder = {
|
|
229
|
+
...createMockBookmark(user, activity),
|
|
230
|
+
folder: {
|
|
231
|
+
id: 'folder1',
|
|
232
|
+
name: 'Folder 1',
|
|
233
|
+
created_at: new Date(),
|
|
234
|
+
updated_at: new Date(),
|
|
235
|
+
custom: {},
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
const bookmarkWithoutFolder = createMockBookmark(user, activity);
|
|
239
|
+
|
|
240
|
+
activity.own_bookmarks = [bookmarkWithFolder, bookmarkWithoutFolder];
|
|
241
|
+
|
|
242
|
+
// Try to remove bookmark without folder
|
|
243
|
+
const event = createMockDeletedEvent(bookmarkWithoutFolder);
|
|
244
|
+
const result = removeBookmarkFromActivity(event, activity, true);
|
|
245
|
+
|
|
246
|
+
expect(result.changed).toBe(true);
|
|
247
|
+
expect(result.own_bookmarks).toHaveLength(1);
|
|
248
|
+
expect(result.own_bookmarks[0]).toEqual(bookmarkWithFolder);
|
|
249
|
+
});
|
|
184
250
|
});
|
|
185
251
|
|
|
186
252
|
describe('updateBookmarkInActivity', () => {
|
|
@@ -220,20 +286,80 @@ describe('bookmark-utils', () => {
|
|
|
220
286
|
expect(result.own_bookmarks[0]).toEqual(bookmark); // unchanged
|
|
221
287
|
});
|
|
222
288
|
|
|
223
|
-
|
|
289
|
+
// Test for the bug: updating bookmarks with same activity_id but different folder_id
|
|
290
|
+
it('should correctly update bookmark by activity_id + folder_id + user_id', () => {
|
|
224
291
|
const activity = createMockActivity('activity1');
|
|
225
292
|
const user = createMockUser('user1');
|
|
226
|
-
const bookmark = createMockBookmark(user, activity);
|
|
227
|
-
activity.own_bookmarks = [bookmark];
|
|
228
293
|
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
294
|
+
// Create two bookmarks with same activity and user but different folders
|
|
295
|
+
const bookmark1 = {
|
|
296
|
+
...createMockBookmark(user, activity),
|
|
297
|
+
folder: {
|
|
298
|
+
id: 'folder1',
|
|
299
|
+
name: 'Folder 1',
|
|
300
|
+
created_at: new Date(),
|
|
301
|
+
updated_at: new Date(),
|
|
302
|
+
custom: {},
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
const bookmark2 = {
|
|
306
|
+
...createMockBookmark(user, activity),
|
|
307
|
+
folder: {
|
|
308
|
+
id: 'folder2',
|
|
309
|
+
name: 'Folder 2',
|
|
310
|
+
created_at: new Date(),
|
|
311
|
+
updated_at: new Date(),
|
|
312
|
+
custom: {},
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
activity.own_bookmarks = [bookmark1, bookmark2];
|
|
317
|
+
|
|
318
|
+
// Update bookmark1
|
|
319
|
+
const updatedBookmark1 = {
|
|
320
|
+
...bookmark1,
|
|
321
|
+
custom: { updated: true },
|
|
322
|
+
};
|
|
323
|
+
const event = createMockUpdatedEvent(updatedBookmark1);
|
|
232
324
|
const result = updateBookmarkInActivity(event, activity, true);
|
|
233
325
|
|
|
234
326
|
expect(result.changed).toBe(true);
|
|
235
|
-
expect(result.own_bookmarks).toHaveLength(
|
|
236
|
-
expect(result.own_bookmarks
|
|
327
|
+
expect(result.own_bookmarks).toHaveLength(2);
|
|
328
|
+
expect(result.own_bookmarks).toContain(updatedBookmark1);
|
|
329
|
+
expect(result.own_bookmarks).toContain(bookmark2);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should handle updating bookmarks without folders correctly', () => {
|
|
333
|
+
const activity = createMockActivity('activity1');
|
|
334
|
+
const user = createMockUser('user1');
|
|
335
|
+
|
|
336
|
+
// Create two bookmarks: one with folder, one without
|
|
337
|
+
const bookmarkWithFolder = {
|
|
338
|
+
...createMockBookmark(user, activity),
|
|
339
|
+
folder: {
|
|
340
|
+
id: 'folder1',
|
|
341
|
+
name: 'Folder 1',
|
|
342
|
+
created_at: new Date(),
|
|
343
|
+
updated_at: new Date(),
|
|
344
|
+
custom: {},
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
const bookmarkWithoutFolder = createMockBookmark(user, activity);
|
|
348
|
+
|
|
349
|
+
activity.own_bookmarks = [bookmarkWithFolder, bookmarkWithoutFolder];
|
|
350
|
+
|
|
351
|
+
// Update bookmark without folder
|
|
352
|
+
const updatedBookmarkWithoutFolder = {
|
|
353
|
+
...bookmarkWithoutFolder,
|
|
354
|
+
custom: { updated: true },
|
|
355
|
+
};
|
|
356
|
+
const event = createMockUpdatedEvent(updatedBookmarkWithoutFolder);
|
|
357
|
+
const result = updateBookmarkInActivity(event, activity, true);
|
|
358
|
+
|
|
359
|
+
expect(result.changed).toBe(true);
|
|
360
|
+
expect(result.own_bookmarks).toHaveLength(2);
|
|
361
|
+
expect(result.own_bookmarks).toContain(bookmarkWithFolder);
|
|
362
|
+
expect(result.own_bookmarks).toContain(updatedBookmarkWithoutFolder);
|
|
237
363
|
});
|
|
238
364
|
});
|
|
239
365
|
|
|
@@ -3,9 +3,23 @@ import {
|
|
|
3
3
|
BookmarkDeletedEvent,
|
|
4
4
|
BookmarkUpdatedEvent,
|
|
5
5
|
ActivityResponse,
|
|
6
|
+
BookmarkResponse,
|
|
6
7
|
} from '../gen/models';
|
|
7
8
|
import { UpdateStateResult } from '../types-internal';
|
|
8
9
|
|
|
10
|
+
// Helper function to check if two bookmarks are the same
|
|
11
|
+
// A bookmark is identified by activity_id + folder_id + user_id
|
|
12
|
+
const isSameBookmark = (
|
|
13
|
+
bookmark1: BookmarkResponse,
|
|
14
|
+
bookmark2: BookmarkResponse,
|
|
15
|
+
): boolean => {
|
|
16
|
+
return (
|
|
17
|
+
bookmark1.user.id === bookmark2.user.id &&
|
|
18
|
+
bookmark1.activity.id === bookmark2.activity.id &&
|
|
19
|
+
bookmark1.folder?.id === bookmark2.folder?.id
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
9
23
|
const updateActivityInActivities = (
|
|
10
24
|
updatedActivity: ActivityResponse,
|
|
11
25
|
activities: ActivityResponse[],
|
|
@@ -46,9 +60,7 @@ export const removeBookmarkFromActivity = (
|
|
|
46
60
|
// Update own_bookmarks if the bookmark is from the current user
|
|
47
61
|
const ownBookmarks = isCurrentUser
|
|
48
62
|
? (activity.own_bookmarks || []).filter(
|
|
49
|
-
(bookmark) =>
|
|
50
|
-
bookmark.user.id !== event.bookmark.user.id ||
|
|
51
|
-
bookmark.activity.id !== event.bookmark.activity.id,
|
|
63
|
+
(bookmark) => !isSameBookmark(bookmark, event.bookmark),
|
|
52
64
|
)
|
|
53
65
|
: activity.own_bookmarks;
|
|
54
66
|
|
|
@@ -67,10 +79,8 @@ export const updateBookmarkInActivity = (
|
|
|
67
79
|
// Update own_bookmarks if the bookmark is from the current user
|
|
68
80
|
let ownBookmarks = activity.own_bookmarks || [];
|
|
69
81
|
if (isCurrentUser) {
|
|
70
|
-
const bookmarkIndex = ownBookmarks.findIndex(
|
|
71
|
-
(bookmark)
|
|
72
|
-
bookmark.user.id === event.bookmark.user.id &&
|
|
73
|
-
bookmark.activity.id === event.bookmark.activity.id,
|
|
82
|
+
const bookmarkIndex = ownBookmarks.findIndex((bookmark) =>
|
|
83
|
+
isSameBookmark(bookmark, event.bookmark),
|
|
74
84
|
);
|
|
75
85
|
if (bookmarkIndex !== -1) {
|
|
76
86
|
ownBookmarks = [...ownBookmarks];
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { ConnectionChangedEvent } from './common/real-time/event-models';
|
|
2
2
|
import { NetworkChangedEvent } from './common/types';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PagerResponse,
|
|
5
|
+
WSEvent,
|
|
6
|
+
} from './gen/models';
|
|
7
|
+
import type {
|
|
8
|
+
ActivityResponse,
|
|
9
|
+
CommentResponse,
|
|
10
|
+
} from './gen/models';
|
|
4
11
|
import { FeedsClient } from './FeedsClient';
|
|
5
12
|
|
|
6
13
|
export type FeedsEvent = WSEvent | ConnectionChangedEvent | NetworkChangedEvent;
|
|
@@ -22,3 +29,7 @@ export type LoadingStates = {
|
|
|
22
29
|
export type TokenOrProvider = string | TokenProvider;
|
|
23
30
|
|
|
24
31
|
export type TokenProvider = () => Promise<string>;
|
|
32
|
+
|
|
33
|
+
export type StreamFile = File | { name: string, uri: string, type: string }
|
|
34
|
+
|
|
35
|
+
export type CommentParent = ActivityResponse | CommentResponse;
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
import { CommentParent, StreamFile } from './types';
|
|
2
|
+
import type { CommentResponse } from './gen/models';
|
|
3
|
+
|
|
4
|
+
export const isImageFile = (file: StreamFile) => {
|
|
2
5
|
// photoshop files begin with 'image/'
|
|
3
6
|
return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
|
|
4
7
|
};
|
|
8
|
+
|
|
9
|
+
export const isVideoFile = (file: StreamFile) => {
|
|
10
|
+
return file.type.startsWith('video/');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const checkHasAnotherPage = <T extends unknown | undefined>(
|
|
14
|
+
v: T,
|
|
15
|
+
cursor: string | undefined,
|
|
16
|
+
) =>
|
|
17
|
+
(typeof v === 'undefined' && typeof cursor === 'undefined') ||
|
|
18
|
+
typeof cursor === 'string';
|
|
19
|
+
|
|
20
|
+
export const isCommentResponse = (
|
|
21
|
+
entity: CommentParent,
|
|
22
|
+
): entity is CommentResponse => {
|
|
23
|
+
return typeof (entity as CommentResponse)?.object_id === 'string';
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Constants = {
|
|
27
|
+
DEFAULT_COMMENT_PAGINATION: 'first',
|
|
28
|
+
} as const;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A React hook that returns the currently connected user on a `FeedsClient` instance and null otherwise.
|
|
3
|
-
*/
|
|
4
|
-
export declare const useClientConnectedUser: () => import("../..").OwnUser | undefined;
|
|
5
|
-
/**
|
|
6
|
-
* A React hook that returns the websocket connection state of `FeedsClient`.
|
|
7
|
-
*/
|
|
8
|
-
export declare const useWsConnectionState: () => {
|
|
9
|
-
isHealthy: boolean | undefined;
|
|
10
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ActivityResponse, CommentResponse } from '../../src/gen/models';
|
|
2
|
-
import type { Feed } from '../../src/Feed';
|
|
3
|
-
export declare const useComments: (feed: Feed,
|
|
4
|
-
/**
|
|
5
|
-
* The parent (activity or comment) for which to fetch comments.
|
|
6
|
-
*/
|
|
7
|
-
parent: ActivityResponse | CommentResponse) => {
|
|
8
|
-
comments: CommentResponse[];
|
|
9
|
-
comment_pagination: (import("../../src/gen/models").PagerResponse & import("../..").LoadingStates & {
|
|
10
|
-
sort?: string;
|
|
11
|
-
}) | undefined;
|
|
12
|
-
};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Feed } from '../../src/Feed';
|
|
2
|
-
export declare const useOwnCapabilities: (feed: Feed | undefined) => {
|
|
3
|
-
canAddActivity: boolean;
|
|
4
|
-
canAddActivityReaction: boolean;
|
|
5
|
-
canAddComment: boolean;
|
|
6
|
-
canAddCommentReaction: boolean;
|
|
7
|
-
canBookmarkActivity: boolean;
|
|
8
|
-
canCreateFeed: boolean;
|
|
9
|
-
canDeleteBookmark: boolean;
|
|
10
|
-
canDeleteComment: boolean;
|
|
11
|
-
canDeleteFeed: boolean;
|
|
12
|
-
canEditBookmark: boolean;
|
|
13
|
-
canFollow: boolean;
|
|
14
|
-
canRemoveActivity: boolean;
|
|
15
|
-
canRemoveActivityReaction: boolean;
|
|
16
|
-
canRemoveCommentReaction: boolean;
|
|
17
|
-
canUnfollow: boolean;
|
|
18
|
-
canUpdateFeed: boolean;
|
|
19
|
-
canInviteFeed: boolean;
|
|
20
|
-
canJoinFeed: boolean;
|
|
21
|
-
canLeaveFeed: boolean;
|
|
22
|
-
canManageFeedGroup: boolean;
|
|
23
|
-
canMarkActivity: boolean;
|
|
24
|
-
canPinActivity: boolean;
|
|
25
|
-
canQueryFeedMembers: boolean;
|
|
26
|
-
canQueryFollows: boolean;
|
|
27
|
-
canReadActivities: boolean;
|
|
28
|
-
canReadFeed: boolean;
|
|
29
|
-
canUpdateActivity: boolean;
|
|
30
|
-
canUpdateComment: boolean;
|
|
31
|
-
canUpdateFeedFollowers: boolean;
|
|
32
|
-
canUpdateFeedMembers: boolean;
|
|
33
|
-
};
|