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.
- package/lib/commonjs/components/Chat/Chat.js +4 -0
- package/lib/commonjs/components/Chat/Chat.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +3 -3
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/hooks/index.js +22 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useMessageReminder.js +20 -0
- package/lib/commonjs/hooks/useMessageReminder.js.map +1 -0
- package/lib/commonjs/hooks/useQueryReminders.js +145 -0
- package/lib/commonjs/hooks/useQueryReminders.js.map +1 -0
- package/lib/commonjs/store/SqliteClient.js +1 -1
- package/lib/commonjs/store/apis/getChannelMessages.js +9 -1
- package/lib/commonjs/store/apis/getChannelMessages.js.map +1 -1
- package/lib/commonjs/store/apis/upsertMessages.js +8 -0
- package/lib/commonjs/store/apis/upsertMessages.js.map +1 -1
- package/lib/commonjs/store/mappers/mapMessageToStorable.js +2 -1
- package/lib/commonjs/store/mappers/mapMessageToStorable.js.map +1 -1
- package/lib/commonjs/store/mappers/mapReminderToStorable.js +22 -0
- package/lib/commonjs/store/mappers/mapReminderToStorable.js.map +1 -0
- package/lib/commonjs/store/mappers/mapStorableToMessage.js +6 -2
- package/lib/commonjs/store/mappers/mapStorableToMessage.js.map +1 -1
- package/lib/commonjs/store/mappers/mapStorableToReminder.js +21 -0
- package/lib/commonjs/store/mappers/mapStorableToReminder.js.map +1 -0
- package/lib/commonjs/store/schema.js +16 -0
- package/lib/commonjs/store/schema.js.map +1 -1
- package/lib/commonjs/utils/i18n/Streami18n.js +2 -0
- package/lib/commonjs/utils/i18n/Streami18n.js.map +1 -1
- package/lib/commonjs/utils/i18n/predefinedFormatters.js +14 -3
- package/lib/commonjs/utils/i18n/predefinedFormatters.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Chat/Chat.js +4 -0
- package/lib/module/components/Chat/Chat.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +3 -3
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/hooks/index.js +22 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useMessageReminder.js +20 -0
- package/lib/module/hooks/useMessageReminder.js.map +1 -0
- package/lib/module/hooks/useQueryReminders.js +145 -0
- package/lib/module/hooks/useQueryReminders.js.map +1 -0
- package/lib/module/store/SqliteClient.js +1 -1
- package/lib/module/store/apis/getChannelMessages.js +9 -1
- package/lib/module/store/apis/getChannelMessages.js.map +1 -1
- package/lib/module/store/apis/upsertMessages.js +8 -0
- package/lib/module/store/apis/upsertMessages.js.map +1 -1
- package/lib/module/store/mappers/mapMessageToStorable.js +2 -1
- package/lib/module/store/mappers/mapMessageToStorable.js.map +1 -1
- package/lib/module/store/mappers/mapReminderToStorable.js +22 -0
- package/lib/module/store/mappers/mapReminderToStorable.js.map +1 -0
- package/lib/module/store/mappers/mapStorableToMessage.js +6 -2
- package/lib/module/store/mappers/mapStorableToMessage.js.map +1 -1
- package/lib/module/store/mappers/mapStorableToReminder.js +21 -0
- package/lib/module/store/mappers/mapStorableToReminder.js.map +1 -0
- package/lib/module/store/schema.js +16 -0
- package/lib/module/store/schema.js.map +1 -1
- package/lib/module/utils/i18n/Streami18n.js +2 -0
- package/lib/module/utils/i18n/Streami18n.js.map +1 -1
- package/lib/module/utils/i18n/predefinedFormatters.js +14 -3
- package/lib/module/utils/i18n/predefinedFormatters.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Chat/Chat.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +2 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useMessageReminder.d.ts +2 -0
- package/lib/typescript/hooks/useMessageReminder.d.ts.map +1 -0
- package/lib/typescript/hooks/useQueryReminders.d.ts +15 -0
- package/lib/typescript/hooks/useQueryReminders.d.ts.map +1 -0
- package/lib/typescript/store/OfflineDB.d.ts.map +1 -1
- package/lib/typescript/store/apis/getChannelMessages.d.ts.map +1 -1
- package/lib/typescript/store/apis/upsertMessages.d.ts.map +1 -1
- package/lib/typescript/store/mappers/mapMessageToStorable.d.ts.map +1 -1
- package/lib/typescript/store/mappers/mapReminderToStorable.d.ts +4 -0
- package/lib/typescript/store/mappers/mapReminderToStorable.d.ts.map +1 -0
- package/lib/typescript/store/mappers/mapStorableToMessage.d.ts +2 -1
- package/lib/typescript/store/mappers/mapStorableToMessage.d.ts.map +1 -1
- package/lib/typescript/store/mappers/mapStorableToReminder.d.ts +4 -0
- package/lib/typescript/store/mappers/mapStorableToReminder.d.ts.map +1 -0
- package/lib/typescript/store/schema.d.ts +8 -0
- package/lib/typescript/store/schema.d.ts.map +1 -1
- package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
- package/lib/typescript/utils/i18n/predefinedFormatters.d.ts.map +1 -1
- package/lib/typescript/utils/i18n/types.d.ts +55 -0
- package/lib/typescript/utils/i18n/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Chat/Chat.tsx +4 -0
- package/src/components/MessageList/MessageList.tsx +5 -3
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useMessageReminder.ts +19 -0
- package/src/hooks/useQueryReminders.ts +145 -0
- package/src/store/SqliteClient.ts +1 -1
- package/src/store/apis/getChannelMessages.ts +12 -0
- package/src/store/apis/upsertMessages.ts +9 -0
- package/src/store/mappers/mapMessageToStorable.ts +2 -0
- package/src/store/mappers/mapReminderToStorable.ts +18 -0
- package/src/store/mappers/mapStorableToMessage.ts +4 -0
- package/src/store/mappers/mapStorableToReminder.ts +16 -0
- package/src/store/schema.ts +26 -0
- package/src/utils/i18n/Streami18n.ts +2 -0
- package/src/utils/i18n/predefinedFormatters.ts +13 -1
- package/src/utils/i18n/types.ts +57 -0
- 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 =
|
package/src/hooks/index.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
|
@@ -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
|
+
};
|
package/src/store/schema.ts
CHANGED
|
@@ -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
|
(
|
package/src/utils/i18n/types.ts
CHANGED
|
@@ -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