stream-chat 9.0.1 → 9.1.0

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.
@@ -0,0 +1,3 @@
1
+ import type { MessageCompositionMiddleware } from './types';
2
+ import type { MessageComposer } from '../../messageComposer';
3
+ export declare const createPollOnlyCompositionMiddleware: (composer: MessageComposer) => MessageCompositionMiddleware;
@@ -43,6 +43,7 @@ export type PollComposerStateChangeMiddlewareValue = {
43
43
  targetFields: Partial<{
44
44
  [K in keyof PollComposerState['data']]: K extends 'options' ? PollComposerOptionUpdate : PollComposerState['data'][K];
45
45
  }>;
46
+ injectedFieldErrors?: PollComposerFieldErrors;
46
47
  };
47
48
  export type PollComposerStateMiddlewareValue = MiddlewareExecutionResult<PollComposerStateChangeMiddlewareValue>;
48
49
  export {};
@@ -1,5 +1,5 @@
1
- import type { ExecuteParams, MiddlewareExecutionResult, MiddlewareHandler } from '../../../middleware';
2
1
  import { MiddlewareExecutor } from '../../../middleware';
2
+ import type { ExecuteParams, MiddlewareExecutionResult, MiddlewareHandler } from '../../../middleware';
3
3
  import type { Suggestion, TextComposerMiddlewareExecutorOptions, TextComposerState } from './types';
4
4
  export type TextComposerMiddlewareExecutorState<T extends Suggestion = Suggestion> = TextComposerState<T> & {
5
5
  change?: {
@@ -2,7 +2,7 @@ import { PollComposerCompositionMiddlewareExecutor, PollComposerStateMiddlewareE
2
2
  import { StateStore } from '../store';
3
3
  import { VotingVisibility } from '../types';
4
4
  import type { MessageComposer } from './messageComposer';
5
- import type { PollComposerState, UpdateFieldsData } from './middleware/pollComposer';
5
+ import type { PollComposerFieldErrors, PollComposerState, UpdateFieldsData } from './middleware/pollComposer';
6
6
  export type PollComposerOptions = {
7
7
  composer: MessageComposer;
8
8
  };
@@ -25,7 +25,12 @@ export declare class PollComposer {
25
25
  get voting_visibility(): VotingVisibility | undefined;
26
26
  get canCreatePoll(): boolean;
27
27
  initState: () => void;
28
- updateFields: (data: UpdateFieldsData) => Promise<void>;
28
+ /**
29
+ * Updates specified fields and generates relevant errors
30
+ * @param data
31
+ * @param injectedFieldErrors - errors produced externally that will take precedence over the errors generated in the middleware chaing
32
+ */
33
+ updateFields: (data: UpdateFieldsData, injectedFieldErrors?: PollComposerFieldErrors) => Promise<void>;
29
34
  handleFieldBlur: (field: keyof PollComposerState["data"]) => Promise<void>;
30
35
  compose: () => Promise<import("..").PollComposerCompositionMiddlewareValueState | undefined>;
31
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stream-chat",
3
- "version": "9.0.1",
3
+ "version": "9.1.0",
4
4
  "description": "JS SDK for the Stream Chat API",
5
5
  "homepage": "https://getstream.io/chat/",
6
6
  "author": {
@@ -88,16 +88,18 @@ const initState = (
88
88
  const quotedMessage = composition.quoted_message;
89
89
  let message;
90
90
  let draftId = null;
91
+ let id = MessageComposer.generateId(); // do not use draft id for messsage id
91
92
  if (compositionIsDraftResponse(composition)) {
92
93
  message = composition.message;
93
94
  draftId = composition.message.id;
94
95
  } else {
95
96
  message = composition;
97
+ id = composition.id;
96
98
  }
97
99
 
98
100
  return {
99
101
  draftId,
100
- id: message.id,
102
+ id,
101
103
  quotedMessage: quotedMessage
102
104
  ? formatMessage(quotedMessage as MessageResponseBase)
103
105
  : null,
@@ -318,6 +320,7 @@ export class MessageComposer {
318
320
  this.attachmentManager.initState({ message });
319
321
  this.linkPreviewsManager.initState({ message });
320
322
  this.textComposer.initState({ message });
323
+ this.pollComposer.initState();
321
324
  this.customDataManager.initState({ message });
322
325
  this.state.next(initState(composition));
323
326
  if (
@@ -543,11 +546,6 @@ export class MessageComposer {
543
546
  };
544
547
 
545
548
  clear = () => {
546
- this.attachmentManager.initState();
547
- this.linkPreviewsManager.initState();
548
- this.textComposer.initState();
549
- this.pollComposer.initState();
550
- this.customDataManager.initState();
551
549
  this.initState();
552
550
  };
553
551
 
@@ -630,6 +628,7 @@ export class MessageComposer {
630
628
  try {
631
629
  const { poll } = await this.client.createPoll(composition.data);
632
630
  this.state.partialNext({ pollId: poll.id });
631
+ this.pollComposer.initState();
633
632
  } catch (error) {
634
633
  this.client.notifications.add({
635
634
  message: 'Failed to create the poll',
@@ -30,6 +30,7 @@ import {
30
30
  createCustomDataCompositionMiddleware,
31
31
  createDraftCustomDataCompositionMiddleware,
32
32
  } from './customData';
33
+ import { createPollOnlyCompositionMiddleware } from './pollOnly';
33
34
 
34
35
  export class MessageComposerMiddlewareExecutor extends MiddlewareExecutor<
35
36
  MessageComposerMiddlewareState,
@@ -40,6 +41,7 @@ export class MessageComposerMiddlewareExecutor extends MiddlewareExecutor<
40
41
  // todo: document how to add custom data to a composed message using middleware
41
42
  // or adding custom composer components (apart from AttachmentsManager, TextComposer etc.)
42
43
  this.use([
44
+ createPollOnlyCompositionMiddleware(composer),
43
45
  createTextComposerCompositionMiddleware(composer),
44
46
  createAttachmentsCompositionMiddleware(composer),
45
47
  createLinkPreviewsCompositionMiddleware(composer),
@@ -0,0 +1,49 @@
1
+ import type {
2
+ MessageComposerMiddlewareState,
3
+ MessageCompositionMiddleware,
4
+ } from './types';
5
+ import type { MessageComposer } from '../../messageComposer';
6
+ import type { MiddlewareHandlerParams } from '../../../middleware';
7
+ import type { LocalMessage } from '../../../types';
8
+
9
+ const pollLocalMessageNullifiedFields: Pick<
10
+ LocalMessage,
11
+ 'attachments' | 'mentioned_users' | 'parent_id' | 'quoted_message' | 'text'
12
+ > = {
13
+ attachments: [],
14
+ mentioned_users: [],
15
+ parent_id: undefined,
16
+ quoted_message: undefined,
17
+ text: '',
18
+ };
19
+
20
+ export const createPollOnlyCompositionMiddleware = (
21
+ composer: MessageComposer,
22
+ ): MessageCompositionMiddleware => ({
23
+ id: 'stream-io/message-composer-middleware/poll-only',
24
+ handlers: {
25
+ compose: ({
26
+ state,
27
+ complete,
28
+ forward,
29
+ }: MiddlewareHandlerParams<MessageComposerMiddlewareState>) => {
30
+ const pollId = composer.pollId;
31
+ const isEditingMessage = !!composer.editedMessage;
32
+ const isComposingThreadReply = !!composer.threadId;
33
+ if (!pollId || isComposingThreadReply || isEditingMessage) return forward();
34
+
35
+ return complete({
36
+ ...state,
37
+ localMessage: {
38
+ ...state.localMessage,
39
+ ...pollLocalMessageNullifiedFields,
40
+ poll_id: pollId,
41
+ },
42
+ message: {
43
+ id: state.localMessage.id,
44
+ poll_id: pollId,
45
+ },
46
+ });
47
+ },
48
+ },
49
+ });
@@ -31,6 +31,10 @@ export const pollStateChangeValidators: Partial<
31
31
  max_votes_allowed: ({ data, value }) => {
32
32
  if (data.enforce_unique_vote && value)
33
33
  return { max_votes_allowed: 'Enforce unique vote is enabled' };
34
+ const numericMatch = value.match(/^[0-9]+$/);
35
+ if (!numericMatch && value) {
36
+ return { max_votes_allowed: 'Only numbers are allowed' };
37
+ }
34
38
  if (value?.length > 1 && !value.match(VALID_MAX_VOTES_VALUE_REGEX))
35
39
  return { max_votes_allowed: 'Type a number from 2 to 10' };
36
40
  return { max_votes_allowed: undefined };
@@ -225,7 +229,7 @@ export const createPollComposerStateMiddleware = ({
225
229
  forward,
226
230
  }: MiddlewareHandlerParams<PollComposerStateChangeMiddlewareValue>) => {
227
231
  if (!state.targetFields) return forward();
228
- const { previousState } = state;
232
+ const { previousState, injectedFieldErrors } = state;
229
233
  const finalValidators = {
230
234
  ...pollStateChangeValidators,
231
235
  ...defaultPollFieldChangeEventValidators,
@@ -247,7 +251,7 @@ export const createPollComposerStateMiddleware = ({
247
251
  nextState: {
248
252
  ...previousState,
249
253
  data: { ...previousState.data, ...newData },
250
- errors: { ...previousState.errors, ...newErrors },
254
+ errors: { ...previousState.errors, ...newErrors, ...injectedFieldErrors },
251
255
  },
252
256
  });
253
257
  },
@@ -275,7 +279,11 @@ export const createPollComposerStateMiddleware = ({
275
279
  nextState: {
276
280
  ...previousState,
277
281
  data: { ...previousState.data, ...newData },
278
- errors: { ...previousState.errors, ...newErrors },
282
+ errors: {
283
+ ...previousState.errors,
284
+ ...newErrors,
285
+ ...state.injectedFieldErrors,
286
+ },
279
287
  },
280
288
  });
281
289
  },
@@ -60,6 +60,7 @@ export type PollComposerStateChangeMiddlewareValue = {
60
60
  ? PollComposerOptionUpdate
61
61
  : PollComposerState['data'][K];
62
62
  }>;
63
+ injectedFieldErrors?: PollComposerFieldErrors;
63
64
  };
64
65
 
65
66
  export type PollComposerStateMiddlewareValue =
@@ -1,15 +1,15 @@
1
1
  import { createCommandsMiddleware } from './commands';
2
2
  import { createMentionsMiddleware } from './mentions';
3
3
  import { createTextComposerPreValidationMiddleware } from './validation';
4
+ import { MiddlewareExecutor } from '../../../middleware';
4
5
  import type {
5
6
  ExecuteParams,
6
7
  MiddlewareExecutionResult,
7
8
  MiddlewareHandler,
8
9
  } from '../../../middleware';
9
- import { MiddlewareExecutor } from '../../../middleware';
10
- import { withCancellation } from '../../../utils/concurrency';
11
10
  import type {
12
11
  Suggestion,
12
+ Suggestions,
13
13
  TextComposerMiddlewareExecutorOptions,
14
14
  TextComposerState,
15
15
  } from './types';
@@ -58,28 +58,14 @@ export class TextComposerMiddlewareExecutor<
58
58
  initialValue: initialState,
59
59
  });
60
60
 
61
- if (result && result.state.suggestions) {
62
- try {
63
- const searchResult = await withCancellation(
64
- 'textComposer-suggestions-search',
65
- async () => {
66
- await result.state.suggestions?.searchSource.search(
67
- result.state.suggestions?.query,
68
- );
69
- },
70
- );
71
- if (searchResult === 'canceled') return { ...result, status: 'discard' };
72
- } catch (error) {
73
- // Clear suggestions on search error
74
- return {
75
- ...result,
76
- state: {
77
- ...result.state,
78
- suggestions: undefined,
79
- },
80
- };
81
- }
82
- }
61
+ const { query, searchSource } = result.state.suggestions ?? ({} as Suggestions);
62
+ /**
63
+ * Catching error just for sanity purposes.
64
+ * The BaseSearchSource.search() method returns debounced result.
65
+ * That means the result of the previous search call as the debounced call result is unknown at the moment.
66
+ * Custom search source implementation should handle errors meaningfully internally.
67
+ */
68
+ searchSource?.search(query)?.catch(console.error);
83
69
 
84
70
  return result;
85
71
  }
@@ -7,7 +7,11 @@ import { StateStore } from '../store';
7
7
  import { VotingVisibility } from '../types';
8
8
  import { generateUUIDv4 } from '../utils';
9
9
  import type { MessageComposer } from './messageComposer';
10
- import type { PollComposerState, UpdateFieldsData } from './middleware/pollComposer';
10
+ import type {
11
+ PollComposerFieldErrors,
12
+ PollComposerState,
13
+ UpdateFieldsData,
14
+ } from './middleware/pollComposer';
11
15
 
12
16
  export type PollComposerOptions = {
13
17
  composer: MessageComposer;
@@ -102,13 +106,23 @@ export class PollComposer {
102
106
  this.state.next(this.initialState);
103
107
  };
104
108
 
105
- updateFields = async (data: UpdateFieldsData) => {
109
+ /**
110
+ * Updates specified fields and generates relevant errors
111
+ * @param data
112
+ * @param injectedFieldErrors - errors produced externally that will take precedence over the errors generated in the middleware chaing
113
+ */
114
+ // FIXME: change method params to a single object with the next major release
115
+ updateFields = async (
116
+ data: UpdateFieldsData,
117
+ injectedFieldErrors?: PollComposerFieldErrors,
118
+ ) => {
106
119
  const { state, status } = await this.stateMiddlewareExecutor.execute({
107
120
  eventName: 'handleFieldChange',
108
121
  initialValue: {
109
122
  nextState: { ...this.state.getLatestValue() },
110
123
  previousState: { ...this.state.getLatestValue() },
111
124
  targetFields: data,
125
+ injectedFieldErrors,
112
126
  },
113
127
  });
114
128