stream-chat-react-native-core 8.0.1-beta.5 → 8.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.
Files changed (102) hide show
  1. package/lib/commonjs/components/Chat/Chat.js +4 -0
  2. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  3. package/lib/commonjs/components/MessageList/MessageList.js +3 -3
  4. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  5. package/lib/commonjs/hooks/index.js +22 -0
  6. package/lib/commonjs/hooks/index.js.map +1 -1
  7. package/lib/commonjs/hooks/useMessageReminder.js +20 -0
  8. package/lib/commonjs/hooks/useMessageReminder.js.map +1 -0
  9. package/lib/commonjs/hooks/useQueryReminders.js +145 -0
  10. package/lib/commonjs/hooks/useQueryReminders.js.map +1 -0
  11. package/lib/commonjs/store/SqliteClient.js +1 -1
  12. package/lib/commonjs/store/apis/getChannelMessages.js +9 -1
  13. package/lib/commonjs/store/apis/getChannelMessages.js.map +1 -1
  14. package/lib/commonjs/store/apis/upsertMessages.js +8 -0
  15. package/lib/commonjs/store/apis/upsertMessages.js.map +1 -1
  16. package/lib/commonjs/store/mappers/mapMessageToStorable.js +2 -1
  17. package/lib/commonjs/store/mappers/mapMessageToStorable.js.map +1 -1
  18. package/lib/commonjs/store/mappers/mapReminderToStorable.js +22 -0
  19. package/lib/commonjs/store/mappers/mapReminderToStorable.js.map +1 -0
  20. package/lib/commonjs/store/mappers/mapStorableToMessage.js +6 -2
  21. package/lib/commonjs/store/mappers/mapStorableToMessage.js.map +1 -1
  22. package/lib/commonjs/store/mappers/mapStorableToReminder.js +21 -0
  23. package/lib/commonjs/store/mappers/mapStorableToReminder.js.map +1 -0
  24. package/lib/commonjs/store/schema.js +16 -0
  25. package/lib/commonjs/store/schema.js.map +1 -1
  26. package/lib/commonjs/utils/i18n/Streami18n.js +2 -0
  27. package/lib/commonjs/utils/i18n/Streami18n.js.map +1 -1
  28. package/lib/commonjs/utils/i18n/predefinedFormatters.js +14 -3
  29. package/lib/commonjs/utils/i18n/predefinedFormatters.js.map +1 -1
  30. package/lib/commonjs/version.json +1 -1
  31. package/lib/module/components/Chat/Chat.js +4 -0
  32. package/lib/module/components/Chat/Chat.js.map +1 -1
  33. package/lib/module/components/MessageList/MessageList.js +3 -3
  34. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  35. package/lib/module/hooks/index.js +22 -0
  36. package/lib/module/hooks/index.js.map +1 -1
  37. package/lib/module/hooks/useMessageReminder.js +20 -0
  38. package/lib/module/hooks/useMessageReminder.js.map +1 -0
  39. package/lib/module/hooks/useQueryReminders.js +145 -0
  40. package/lib/module/hooks/useQueryReminders.js.map +1 -0
  41. package/lib/module/store/SqliteClient.js +1 -1
  42. package/lib/module/store/apis/getChannelMessages.js +9 -1
  43. package/lib/module/store/apis/getChannelMessages.js.map +1 -1
  44. package/lib/module/store/apis/upsertMessages.js +8 -0
  45. package/lib/module/store/apis/upsertMessages.js.map +1 -1
  46. package/lib/module/store/mappers/mapMessageToStorable.js +2 -1
  47. package/lib/module/store/mappers/mapMessageToStorable.js.map +1 -1
  48. package/lib/module/store/mappers/mapReminderToStorable.js +22 -0
  49. package/lib/module/store/mappers/mapReminderToStorable.js.map +1 -0
  50. package/lib/module/store/mappers/mapStorableToMessage.js +6 -2
  51. package/lib/module/store/mappers/mapStorableToMessage.js.map +1 -1
  52. package/lib/module/store/mappers/mapStorableToReminder.js +21 -0
  53. package/lib/module/store/mappers/mapStorableToReminder.js.map +1 -0
  54. package/lib/module/store/schema.js +16 -0
  55. package/lib/module/store/schema.js.map +1 -1
  56. package/lib/module/utils/i18n/Streami18n.js +2 -0
  57. package/lib/module/utils/i18n/Streami18n.js.map +1 -1
  58. package/lib/module/utils/i18n/predefinedFormatters.js +14 -3
  59. package/lib/module/utils/i18n/predefinedFormatters.js.map +1 -1
  60. package/lib/module/version.json +1 -1
  61. package/lib/typescript/components/Chat/Chat.d.ts.map +1 -1
  62. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  63. package/lib/typescript/hooks/index.d.ts +2 -0
  64. package/lib/typescript/hooks/index.d.ts.map +1 -1
  65. package/lib/typescript/hooks/useMessageReminder.d.ts +2 -0
  66. package/lib/typescript/hooks/useMessageReminder.d.ts.map +1 -0
  67. package/lib/typescript/hooks/useQueryReminders.d.ts +15 -0
  68. package/lib/typescript/hooks/useQueryReminders.d.ts.map +1 -0
  69. package/lib/typescript/store/OfflineDB.d.ts.map +1 -1
  70. package/lib/typescript/store/apis/getChannelMessages.d.ts.map +1 -1
  71. package/lib/typescript/store/apis/upsertMessages.d.ts.map +1 -1
  72. package/lib/typescript/store/mappers/mapMessageToStorable.d.ts.map +1 -1
  73. package/lib/typescript/store/mappers/mapReminderToStorable.d.ts +4 -0
  74. package/lib/typescript/store/mappers/mapReminderToStorable.d.ts.map +1 -0
  75. package/lib/typescript/store/mappers/mapStorableToMessage.d.ts +2 -1
  76. package/lib/typescript/store/mappers/mapStorableToMessage.d.ts.map +1 -1
  77. package/lib/typescript/store/mappers/mapStorableToReminder.d.ts +4 -0
  78. package/lib/typescript/store/mappers/mapStorableToReminder.d.ts.map +1 -0
  79. package/lib/typescript/store/schema.d.ts +8 -0
  80. package/lib/typescript/store/schema.d.ts.map +1 -1
  81. package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
  82. package/lib/typescript/utils/i18n/predefinedFormatters.d.ts.map +1 -1
  83. package/lib/typescript/utils/i18n/types.d.ts +55 -0
  84. package/lib/typescript/utils/i18n/types.d.ts.map +1 -1
  85. package/package.json +1 -1
  86. package/src/components/Chat/Chat.tsx +4 -0
  87. package/src/components/MessageList/MessageList.tsx +5 -3
  88. package/src/hooks/index.ts +2 -0
  89. package/src/hooks/useMessageReminder.ts +19 -0
  90. package/src/hooks/useQueryReminders.ts +145 -0
  91. package/src/store/SqliteClient.ts +1 -1
  92. package/src/store/apis/getChannelMessages.ts +12 -0
  93. package/src/store/apis/upsertMessages.ts +9 -0
  94. package/src/store/mappers/mapMessageToStorable.ts +2 -0
  95. package/src/store/mappers/mapReminderToStorable.ts +18 -0
  96. package/src/store/mappers/mapStorableToMessage.ts +4 -0
  97. package/src/store/mappers/mapStorableToReminder.ts +16 -0
  98. package/src/store/schema.ts +26 -0
  99. package/src/utils/i18n/Streami18n.ts +2 -0
  100. package/src/utils/i18n/predefinedFormatters.ts +13 -1
  101. package/src/utils/i18n/types.ts +57 -0
  102. package/src/version.json +1 -1
@@ -235,10 +235,14 @@ const ChatWithContext = (props: PropsWithChildren<ChatProps>) => {
235
235
 
236
236
  client.threads.registerSubscriptions();
237
237
  client.polls.registerSubscriptions();
238
+ client.reminders.registerSubscriptions();
239
+ client.reminders.initTimers();
238
240
 
239
241
  return () => {
240
242
  client.threads.unregisterSubscriptions();
241
243
  client.polls.unregisterSubscriptions();
244
+ client.reminders.unregisterSubscriptions();
245
+ client.reminders.clearTimers();
242
246
  };
243
247
  }, [client]);
244
248
 
@@ -626,13 +626,15 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
626
626
  }, [threadList, messageListLengthAfterUpdate, topMessageAfterUpdate?.id]);
627
627
 
628
628
  useEffect(() => {
629
- if (!processedMessageList.length) {
630
- return;
631
- }
632
629
  if (threadList) {
633
630
  setAutoscrollToRecent(true);
634
631
  return;
635
632
  }
633
+
634
+ if (!processedMessageList.length) {
635
+ return;
636
+ }
637
+
636
638
  const notLatestSet = channel.state.messages !== channel.state.latestMessages;
637
639
  if (notLatestSet) {
638
640
  latestNonCurrentMessageBeforeUpdateRef.current =
@@ -5,3 +5,5 @@ export * from './useScreenDimensions';
5
5
  export * from './useStateStore';
6
6
  export * from './useStableCallback';
7
7
  export * from './useLoadingImage';
8
+ export * from './useMessageReminder';
9
+ export * from './useQueryReminders';
@@ -0,0 +1,19 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import type { ReminderManagerState } from 'stream-chat';
4
+
5
+ import { useStateStore } from './useStateStore';
6
+
7
+ import { useChatContext } from '../contexts/chatContext/ChatContext';
8
+
9
+ export const useMessageReminder = (messageId: string) => {
10
+ const { client } = useChatContext();
11
+ const reminderSelector = useCallback(
12
+ (state: ReminderManagerState) => ({
13
+ reminder: state.reminders.get(messageId),
14
+ }),
15
+ [messageId],
16
+ );
17
+ const { reminder } = useStateStore(client.reminders.state, reminderSelector);
18
+ return reminder;
19
+ };
@@ -0,0 +1,145 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ import { Event, PaginatorState, ReminderFilters, ReminderResponse } from 'stream-chat';
4
+
5
+ import { useStateStore } from './useStateStore';
6
+
7
+ import { useChatContext } from '../contexts/chatContext/ChatContext';
8
+
9
+ const selector = (nextValue: PaginatorState<ReminderResponse>) =>
10
+ ({
11
+ isLoading: nextValue.isLoading,
12
+ items: nextValue.items,
13
+ }) as const;
14
+
15
+ // Utility to sort reminders by remind_at date in ascending order
16
+ const sortRemindersByDate = (reminders: ReminderResponse[]) => {
17
+ return reminders.sort((a, b) => {
18
+ if (!a.remind_at || !b.remind_at) {
19
+ return 0; // If either remind_at is missing, keep original order
20
+ }
21
+ // Sort by remind_at date
22
+ return new Date(a.remind_at).getTime() - new Date(b.remind_at).getTime();
23
+ });
24
+ };
25
+
26
+ // Utility functions to check reminder status
27
+ const isReminderOverdue = (reminder?: ReminderResponse) => {
28
+ return reminder?.remind_at && new Date(reminder.remind_at) < new Date();
29
+ };
30
+
31
+ const isReminderUpcoming = (reminder?: ReminderResponse) => {
32
+ return reminder?.remind_at && new Date(reminder.remind_at) > new Date();
33
+ };
34
+
35
+ // Utility to check if all reminders should be shown based on filters
36
+ const showAllReminders = (filters?: ReminderFilters) => {
37
+ return filters && Object.keys(filters).length === 0;
38
+ };
39
+
40
+ /**
41
+ * Custom hook to query reminders from the Stream Chat client.
42
+ * It handles fetching, updating, and deleting reminders, and provides
43
+ * a way to refresh the list and load more reminders.
44
+ *
45
+ * @returns {Object} - Contains data, isLoading, onEndReached, onRefresh, and setData.
46
+ */
47
+ export const useQueryReminders = () => {
48
+ const { client } = useChatContext();
49
+ const { isLoading, items } = useStateStore(client.reminders.paginator.state, selector);
50
+ const [data, setData] = useState<ReminderResponse[]>(items ?? []);
51
+ // The deletion and updates are not handled by the paginator, so we need to cache them
52
+ // to avoid showing deleted or updated reminders in the list.
53
+ const deletedOrUpdatedRemindersCache = useRef<Record<string, ReminderResponse>>({});
54
+
55
+ useEffect(() => {
56
+ setData((prevData) => {
57
+ if (!items) {
58
+ return prevData;
59
+ }
60
+ const newData: ReminderResponse[] = [];
61
+ items.forEach((reminder) => {
62
+ if (prevData.includes(reminder)) {
63
+ newData.push(reminder);
64
+ } else {
65
+ if (!deletedOrUpdatedRemindersCache.current[reminder.message_id]) {
66
+ newData.push(reminder);
67
+ }
68
+ }
69
+ });
70
+ return newData;
71
+ });
72
+ }, [items, client.reminders.paginator.filters]);
73
+
74
+ useEffect(() => {
75
+ const handleReminderDeleted = (event: Event) => {
76
+ if (!event.reminder?.message_id) {
77
+ return;
78
+ }
79
+ deletedOrUpdatedRemindersCache.current[event.reminder.message_id] = event.reminder;
80
+ setData((prevData) => {
81
+ return prevData.filter((item) => item.message_id !== event.reminder?.message_id);
82
+ });
83
+ };
84
+
85
+ const handleReminderCreated = (event: Event) => {
86
+ setData((prevData) => {
87
+ if (!event.reminder) {
88
+ return prevData;
89
+ }
90
+ const updatedData = [...prevData, event.reminder];
91
+ return sortRemindersByDate(updatedData);
92
+ });
93
+ };
94
+
95
+ const handleReminderUpdated = (event: Event) => {
96
+ const { reminder } = event;
97
+ if (!reminder || showAllReminders(client.reminders.paginator.filters)) {
98
+ return; // No update needed if reminder is undefined or filters is empty
99
+ }
100
+ deletedOrUpdatedRemindersCache.current[reminder.message_id] = reminder;
101
+ setData((prevData) => {
102
+ const existingReminder = prevData.find((item) => item.message_id === reminder?.message_id);
103
+ if (!existingReminder) {
104
+ return prevData; // No update needed if reminder not found
105
+ }
106
+
107
+ if (existingReminder.remind_at && !event.reminder?.remind_at) {
108
+ return prevData.filter((item) => item.message_id !== event.reminder?.message_id);
109
+ }
110
+ if (!existingReminder.remind_at && event.reminder?.remind_at) {
111
+ return prevData.filter((item) => item.message_id !== event.reminder?.message_id);
112
+ }
113
+ if (isReminderOverdue(existingReminder) && !isReminderOverdue(event.reminder)) {
114
+ return prevData.filter((item) => item.message_id !== event.reminder?.message_id);
115
+ }
116
+ if (isReminderUpcoming(existingReminder) && !isReminderUpcoming(event.reminder)) {
117
+ return prevData.filter((item) => item.message_id !== event.reminder?.message_id);
118
+ }
119
+
120
+ return prevData;
121
+ });
122
+ };
123
+
124
+ const listeners = [
125
+ client.on('reminder.created', handleReminderCreated),
126
+ client.on('reminder.deleted', handleReminderDeleted),
127
+ client.on('reminder.updated', handleReminderUpdated),
128
+ ];
129
+
130
+ return () => {
131
+ listeners.forEach((l) => l.unsubscribe());
132
+ };
133
+ }, [client]);
134
+
135
+ const loadNext = useCallback(async () => {
136
+ await client.reminders.queryNextReminders();
137
+ }, [client.reminders]);
138
+
139
+ return {
140
+ data,
141
+ isLoading,
142
+ loadNext,
143
+ setData,
144
+ };
145
+ };
@@ -28,7 +28,7 @@ import type { PreparedBatchQueries, PreparedQueries, Scalar, Table } from './typ
28
28
  * This way usage @op-engineering/op-sqlite package is scoped to a single class/file.
29
29
  */
30
30
  export class SqliteClient {
31
- static dbVersion = 11;
31
+ static dbVersion = 12;
32
32
 
33
33
  static dbName = DB_NAME;
34
34
  static dbLocation = DB_LOCATION;
@@ -51,6 +51,17 @@ export const getChannelMessages = async ({
51
51
  messageIdsVsPolls[message.poll_id] = pollsById[message.poll_id];
52
52
  });
53
53
 
54
+ const messageIdsVsReminders: Record<string, TableRow<'reminders'>> = {};
55
+ const reminders = (await SqliteClient.executeSql.apply(
56
+ null,
57
+ createSelectQuery('reminders', ['*'], {
58
+ messageId: messageIds,
59
+ }),
60
+ )) as unknown as TableRow<'reminders'>[];
61
+ reminders.forEach((reminder) => {
62
+ messageIdsVsReminders[reminder.messageId] = reminder;
63
+ });
64
+
54
65
  // Populate the messages.
55
66
  const cidVsMessages: Record<string, MessageResponse[]> = {};
56
67
  messageRows.forEach((m) => {
@@ -65,6 +76,7 @@ export const getChannelMessages = async ({
65
76
  messageRow: m,
66
77
  pollRow: messageIdsVsPolls[m.poll_id],
67
78
  reactionRows: messageIdVsReactions[m.id],
79
+ reminderRow: messageIdsVsReminders[m.id],
68
80
  }),
69
81
  );
70
82
  }
@@ -3,6 +3,7 @@ import type { LocalMessage, MessageResponse } from 'stream-chat';
3
3
  import { mapMessageToStorable } from '../mappers/mapMessageToStorable';
4
4
  import { mapPollToStorable } from '../mappers/mapPollToStorable';
5
5
  import { mapReactionToStorable } from '../mappers/mapReactionToStorable';
6
+ import { mapReminderToStorable } from '../mappers/mapReminderToStorable';
6
7
  import { mapUserToStorable } from '../mappers/mapUserToStorable';
7
8
  import { createUpsertQuery } from '../sqlite-utils/createUpsertQuery';
8
9
  import { SqliteClient } from '../SqliteClient';
@@ -18,6 +19,7 @@ export const upsertMessages = async ({
18
19
  const storableUsers: Array<ReturnType<typeof mapUserToStorable>> = [];
19
20
  const storableReactions: Array<ReturnType<typeof mapReactionToStorable>> = [];
20
21
  const storablePolls: Array<ReturnType<typeof mapPollToStorable>> = [];
22
+ const storableReminders: Array<ReturnType<typeof mapReminderToStorable>> = [];
21
23
 
22
24
  messages?.forEach((message: MessageResponse | LocalMessage) => {
23
25
  storableMessages.push(mapMessageToStorable(message));
@@ -33,6 +35,9 @@ export const upsertMessages = async ({
33
35
  if (message.poll) {
34
36
  storablePolls.push(mapPollToStorable(message.poll));
35
37
  }
38
+ if (message.reminder) {
39
+ storableReminders.push(mapReminderToStorable(message.reminder));
40
+ }
36
41
  });
37
42
 
38
43
  const finalQueries = [
@@ -42,6 +47,9 @@ export const upsertMessages = async ({
42
47
  createUpsertQuery('reactions', storableReaction),
43
48
  ),
44
49
  ...storablePolls.map((storablePoll) => createUpsertQuery('poll', storablePoll)),
50
+ ...storableReminders.map((storableReminder) =>
51
+ createUpsertQuery('reminders', storableReminder),
52
+ ),
45
53
  ];
46
54
 
47
55
  SqliteClient.logger?.('info', 'upsertMessages', {
@@ -49,6 +57,7 @@ export const upsertMessages = async ({
49
57
  messages: storableMessages,
50
58
  polls: storablePolls,
51
59
  reactions: storableReactions,
60
+ reminders: storableReminders,
52
61
  users: storableUsers,
53
62
  });
54
63
 
@@ -21,6 +21,8 @@ export const mapMessageToStorable = (
21
21
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
22
  poll,
23
23
  poll_id,
24
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
25
+ reminder,
24
26
  reaction_groups,
25
27
  text,
26
28
  type,
@@ -0,0 +1,18 @@
1
+ import type { ReminderResponseBase } from 'stream-chat';
2
+
3
+ import { mapDateTimeToStorable } from './mapDateTimeToStorable';
4
+
5
+ import type { TableRow } from '../types';
6
+
7
+ export const mapReminderToStorable = (reminder: ReminderResponseBase): TableRow<'reminders'> => {
8
+ const { channel_cid, created_at, message_id, remind_at, updated_at, user_id } = reminder;
9
+
10
+ return {
11
+ channelCid: channel_cid,
12
+ createdAt: mapDateTimeToStorable(created_at),
13
+ messageId: message_id,
14
+ remindAt: mapDateTimeToStorable(remind_at),
15
+ updatedAt: mapDateTimeToStorable(updated_at),
16
+ userId: user_id,
17
+ };
18
+ };
@@ -3,6 +3,7 @@ import type { MessageResponse } from 'stream-chat';
3
3
  import { mapStorableToPoll } from './mapStorableToPoll';
4
4
  import { mapStorableToReaction } from './mapStorableToReaction';
5
5
 
6
+ import { mapStorableToReminder } from './mapStorableToReminder';
6
7
  import { mapStorableToUser } from './mapStorableToUser';
7
8
 
8
9
  import type { TableRow, TableRowJoinedUser } from '../types';
@@ -12,11 +13,13 @@ export const mapStorableToMessage = ({
12
13
  messageRow,
13
14
  pollRow,
14
15
  reactionRows,
16
+ reminderRow,
15
17
  }: {
16
18
  currentUserId: string;
17
19
  messageRow: TableRowJoinedUser<'messages'>;
18
20
  pollRow: TableRow<'poll'>;
19
21
  reactionRows?: TableRowJoinedUser<'reactions'>[];
22
+ reminderRow?: TableRow<'reminders'>;
20
23
  }): MessageResponse => {
21
24
  const {
22
25
  createdAt,
@@ -47,5 +50,6 @@ export const mapStorableToMessage = ({
47
50
  user: mapStorableToUser(user),
48
51
  ...(pollRow ? { poll: mapStorableToPoll(pollRow) } : {}),
49
52
  ...(extraData ? JSON.parse(extraData) : {}),
53
+ ...(reminderRow ? { reminder: mapStorableToReminder(reminderRow) } : {}),
50
54
  };
51
55
  };
@@ -0,0 +1,16 @@
1
+ import { ReminderResponseBase } from 'stream-chat';
2
+
3
+ import type { TableRow } from '../types';
4
+
5
+ export const mapStorableToReminder = (row: TableRow<'reminders'>): ReminderResponseBase => {
6
+ const { channelCid, createdAt, messageId, remindAt, updatedAt, userId } = row;
7
+
8
+ return {
9
+ channel_cid: channelCid,
10
+ created_at: createdAt,
11
+ message_id: messageId,
12
+ remind_at: remindAt,
13
+ updated_at: updatedAt,
14
+ user_id: userId,
15
+ };
16
+ };
@@ -249,6 +249,24 @@ export const tables: Tables = {
249
249
  ],
250
250
  primaryKey: ['userId', 'cid'],
251
251
  },
252
+ reminders: {
253
+ columns: {
254
+ channelCid: 'TEXT NOT NULL',
255
+ createdAt: 'TEXT',
256
+ messageId: 'TEXT NOT NULL',
257
+ remindAt: 'TEXT',
258
+ updatedAt: 'TEXT',
259
+ userId: 'TEXT NOT NULL',
260
+ },
261
+ indexes: [
262
+ {
263
+ columns: ['messageId'],
264
+ name: 'index_reminders',
265
+ unique: false,
266
+ },
267
+ ],
268
+ primaryKey: ['messageId'],
269
+ },
252
270
  users: {
253
271
  columns: {
254
272
  banned: 'BOOLEAN DEFAULT FALSE',
@@ -410,6 +428,14 @@ export type Schema = {
410
428
  unreadMessages?: number;
411
429
  userId?: string;
412
430
  };
431
+ reminders: {
432
+ channelCid: string;
433
+ createdAt: string;
434
+ messageId: string;
435
+ updatedAt: string;
436
+ userId: string;
437
+ remindAt?: string;
438
+ };
413
439
  users: {
414
440
  id: string;
415
441
  banned?: boolean;
@@ -1,5 +1,6 @@
1
1
  import Dayjs from 'dayjs';
2
2
  import calendar from 'dayjs/plugin/calendar';
3
+ import duration from 'dayjs/plugin/duration';
3
4
  import localeData from 'dayjs/plugin/localeData';
4
5
  import LocalizedFormat from 'dayjs/plugin/localizedFormat';
5
6
  import relativeTime from 'dayjs/plugin/relativeTime';
@@ -465,6 +466,7 @@ export class Streami18n {
465
466
  * For some reason Dayjs.isDayjs(this.DateTimeParser()) doesn't work.
466
467
  */
467
468
  if (this.DateTimeParser && isDayJs(this.DateTimeParser)) {
469
+ this.DateTimeParser.extend(duration);
468
470
  this.DateTimeParser.extend(LocalizedFormat);
469
471
  this.DateTimeParser.extend(calendar);
470
472
  this.DateTimeParser.extend(localeData);
@@ -1,7 +1,19 @@
1
+ import { isDayjs } from 'dayjs';
2
+
3
+ import type { Duration as DayjsDuration } from 'dayjs/plugin/duration';
4
+
1
5
  import { getDateString } from './getDateString';
2
- import { PredefinedFormatters, TimestampFormatterOptions } from './types';
6
+ import { DurationFormatterOptions, PredefinedFormatters, TimestampFormatterOptions } from './types';
3
7
 
4
8
  export const predefinedFormatters: PredefinedFormatters = {
9
+ durationFormatter:
10
+ (streamI18n) =>
11
+ (value, _, { format, withSuffix }: DurationFormatterOptions) => {
12
+ if (format && isDayjs(streamI18n.DateTimeParser)) {
13
+ return (streamI18n.DateTimeParser.duration(value) as DayjsDuration).format(format);
14
+ }
15
+ return streamI18n.DateTimeParser.duration(value).humanize(!!withSuffix);
16
+ },
5
17
  timestampFormatter:
6
18
  (streamI18n) =>
7
19
  (
@@ -9,6 +9,60 @@ export type FormatterFactory<V> = (
9
9
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
10
10
  export type CustomFormatters = Record<string, FormatterFactory<any>>;
11
11
 
12
+ /**
13
+ * import dayjs from 'dayjs';
14
+ * import duration from 'dayjs/plugin/duration';
15
+ *
16
+ * dayjs.extend(duration);
17
+ *
18
+ * // Basic formatting
19
+ * dayjs.duration(1000).format('HH:mm:ss'); // "00:00:01"
20
+ * dayjs.duration(3661000).format('HH:mm:ss'); // "01:01:01"
21
+ *
22
+ * // Different format tokens
23
+ * dayjs.duration(3661000).format('D[d] H[h] m[m] s[s]'); // "0d 1h 1m 1s"
24
+ * dayjs.duration(3661000).format('D [days] H [hours] m [minutes] s [seconds]'); // "0 days 1 hours 1 minutes 1 seconds"
25
+ *
26
+ * // Zero padding
27
+ * dayjs.duration(1000).format('HH:mm:ss'); // "00:00:01"
28
+ * dayjs.duration(1000).format('H:m:s'); // "0:0:1"
29
+ *
30
+ * // Different units
31
+ * dayjs.duration(3661000).format('D'); // "0"
32
+ * dayjs.duration(3661000).format('H'); // "1"
33
+ * dayjs.duration(3661000).format('m'); // "1"
34
+ * dayjs.duration(3661000).format('s'); // "1"
35
+ *
36
+ * // Complex examples
37
+ * dayjs.duration(3661000).format('DD:HH:mm:ss'); // "00:01:01:01"
38
+ * dayjs.duration(3661000).format('D [days] HH:mm:ss'); // "0 days 01:01:01"
39
+ * dayjs.duration(3661000).format('H[h] m[m] s[s]'); // "1h 1m 1s"
40
+ *
41
+ * // Negative durations
42
+ * dayjs.duration(-3661000).format('HH:mm:ss'); // "-01:01:01"
43
+ *
44
+ * // Long durations
45
+ * dayjs.duration(86400000).format('D [days]'); // "1 days"
46
+ * dayjs.duration(2592000000).format('M [months]'); // "30 months"
47
+ *
48
+ *
49
+ * Format tokens:
50
+ * D - days
51
+ * H - hours
52
+ * m - minutes
53
+ * s - seconds
54
+ * S - milliseconds
55
+ * M - months
56
+ * Y - years
57
+ * You can also use:
58
+ * HH, mm, ss for zero-padded numbers
59
+ * [text] for literal text
60
+ */
61
+ export type DurationFormatterOptions = {
62
+ format?: string;
63
+ withSuffix?: boolean;
64
+ };
65
+
12
66
  export type TimestampFormatterOptions = {
13
67
  /* If true, call the `Day.js` calendar function to get the date string to display (e.g. "Yesterday at 3:58 PM"). */
14
68
  calendar?: boolean | null;
@@ -16,8 +70,11 @@ export type TimestampFormatterOptions = {
16
70
  calendarFormats?: Record<string, string>;
17
71
  /* Overrides the default timestamp format if calendar is disabled. */
18
72
  format?: string;
73
+ /* If true, the formatter will return a humanized string with suffix (e.g. "in 5 minutes" or "5 minutes ago"). */
74
+ withSuffix?: boolean;
19
75
  };
20
76
 
21
77
  export type PredefinedFormatters = {
78
+ durationFormatter: FormatterFactory<string>;
22
79
  timestampFormatter: FormatterFactory<string | Date>;
23
80
  };
package/src/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "8.0.1-beta.5"
2
+ "version": "8.1.0"
3
3
  }