@sendbird/uikit-utils 2.1.0 → 2.3.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 (75) hide show
  1. package/lib/commonjs/hooks/index.js +14 -1
  2. package/lib/commonjs/hooks/index.js.map +1 -1
  3. package/lib/commonjs/hooks/react-native.js +56 -0
  4. package/lib/commonjs/hooks/react-native.js.map +1 -0
  5. package/lib/commonjs/index.js +38 -7
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/sendbird/channel.js +7 -1
  8. package/lib/commonjs/sendbird/channel.js.map +1 -1
  9. package/lib/commonjs/sendbird/message.js +12 -2
  10. package/lib/commonjs/sendbird/message.js.map +1 -1
  11. package/lib/commonjs/sendbird/user.js +5 -0
  12. package/lib/commonjs/sendbird/user.js.map +1 -1
  13. package/lib/commonjs/shared/index.js +74 -0
  14. package/lib/commonjs/shared/index.js.map +1 -0
  15. package/lib/commonjs/shared/regex.js +52 -6
  16. package/lib/commonjs/shared/regex.js.map +1 -1
  17. package/lib/commonjs/types.js +3 -1
  18. package/lib/commonjs/types.js.map +1 -1
  19. package/lib/commonjs/ui-format/common.js +17 -14
  20. package/lib/commonjs/ui-format/common.js.map +1 -1
  21. package/lib/commonjs/ui-format/groupChannel.js +7 -10
  22. package/lib/commonjs/ui-format/groupChannel.js.map +1 -1
  23. package/lib/module/hooks/index.js +10 -0
  24. package/lib/module/hooks/index.js.map +1 -1
  25. package/lib/module/hooks/react-native.js +39 -0
  26. package/lib/module/hooks/react-native.js.map +1 -0
  27. package/lib/module/index.js +3 -2
  28. package/lib/module/index.js.map +1 -1
  29. package/lib/module/sendbird/channel.js +3 -0
  30. package/lib/module/sendbird/channel.js.map +1 -1
  31. package/lib/module/sendbird/message.js +9 -3
  32. package/lib/module/sendbird/message.js.map +1 -1
  33. package/lib/module/sendbird/user.js +3 -0
  34. package/lib/module/sendbird/user.js.map +1 -1
  35. package/lib/module/shared/index.js +57 -0
  36. package/lib/module/shared/index.js.map +1 -0
  37. package/lib/module/shared/regex.js +43 -3
  38. package/lib/module/shared/regex.js.map +1 -1
  39. package/lib/module/types.js +1 -4
  40. package/lib/module/types.js.map +1 -1
  41. package/lib/module/ui-format/common.js +12 -7
  42. package/lib/module/ui-format/common.js.map +1 -1
  43. package/lib/module/ui-format/groupChannel.js +5 -4
  44. package/lib/module/ui-format/groupChannel.js.map +1 -1
  45. package/lib/typescript/src/hooks/index.d.ts +3 -2
  46. package/lib/typescript/src/hooks/react-native.d.ts +12 -0
  47. package/lib/typescript/src/index.d.ts +4 -3
  48. package/lib/typescript/src/sendbird/channel.d.ts +1 -0
  49. package/lib/typescript/src/sendbird/message.d.ts +4 -2
  50. package/lib/typescript/src/sendbird/user.d.ts +2 -0
  51. package/lib/typescript/src/shared/bufferedRequest.d.ts +1 -1
  52. package/lib/typescript/src/shared/index.d.ts +26 -0
  53. package/lib/typescript/src/shared/logger.d.ts +1 -1
  54. package/lib/typescript/src/shared/pubsub.d.ts +2 -2
  55. package/lib/typescript/src/shared/regex.d.ts +8 -0
  56. package/lib/typescript/src/types.d.ts +42 -36
  57. package/lib/typescript/src/ui-format/common.d.ts +7 -6
  58. package/package.json +4 -3
  59. package/src/hooks/index.ts +11 -0
  60. package/src/hooks/react-native.ts +56 -0
  61. package/src/index.ts +10 -1
  62. package/src/sendbird/channel.ts +4 -0
  63. package/src/sendbird/message.ts +13 -3
  64. package/src/sendbird/user.ts +6 -0
  65. package/src/shared/index.ts +56 -0
  66. package/src/shared/regex.ts +35 -0
  67. package/src/types.ts +20 -3
  68. package/src/ui-format/common.ts +9 -7
  69. package/src/ui-format/groupChannel.ts +5 -4
  70. package/lib/commonjs/shared/conditionChaining.js +0 -15
  71. package/lib/commonjs/shared/conditionChaining.js.map +0 -1
  72. package/lib/module/shared/conditionChaining.js +0 -7
  73. package/lib/module/shared/conditionChaining.js.map +0 -1
  74. package/lib/typescript/src/shared/conditionChaining.d.ts +0 -2
  75. package/src/shared/conditionChaining.ts +0 -6
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export { Logger } from './shared/logger';
2
2
  export { default as arrayToMap, arrayToMapWithGetter } from './shared/arrayToMap';
3
3
  export { createPubSub } from './shared/pubsub';
4
- export { default as conditionChaining } from './shared/conditionChaining';
5
4
  export {
6
5
  urlRegexStrict,
7
6
  urlRegexRough,
@@ -15,10 +14,15 @@ export {
15
14
  normalizeFileName,
16
15
  emailRegex,
17
16
  phoneRegex,
17
+ replaceWithRegex,
18
+ createMentionTemplateRegex,
18
19
  } from './shared/regex';
19
20
  export { BufferedRequest } from './shared/bufferedRequest';
20
21
 
22
+ export * from './shared';
23
+
21
24
  export * from './hooks';
25
+ export * from './hooks/react-native';
22
26
  export * from './ui-format/groupChannel';
23
27
  export * from './ui-format/common';
24
28
  export * from './sendbird/channel';
@@ -43,6 +47,7 @@ export const SBErrorMessage = {
43
47
  };
44
48
 
45
49
  export type {
50
+ UserStruct,
46
51
  FilterByValueType,
47
52
  UnionToIntersection,
48
53
  OmittedValues,
@@ -80,4 +85,8 @@ export type {
80
85
  SendbirdFileMessageUpdateParams,
81
86
  SendbirdUserUpdateParams,
82
87
  SendbirdRestrictedUser,
88
+ SendbirdReaction,
89
+ SendbirdEmojiContainer,
90
+ SendbirdEmoji,
91
+ SendbirdEmojiCategory,
83
92
  } from './types';
@@ -46,3 +46,7 @@ export function getGroupChannels(channels: SendbirdChannel[]): SendbirdGroupChan
46
46
  export function getOpenChannels(channels: SendbirdChannel[]): SendbirdOpenChannel[] {
47
47
  return channels.filter((c): c is SendbirdOpenChannel => c.isOpenChannel());
48
48
  }
49
+
50
+ export function getChannelUniqId(channel: SendbirdChannel) {
51
+ return channel.url;
52
+ }
@@ -1,11 +1,13 @@
1
1
  import type {
2
+ SendbirdBaseChannel,
2
3
  SendbirdBaseMessage,
3
4
  SendbirdDataPayload,
4
5
  SendbirdFileMessage,
5
6
  SendbirdMessage,
7
+ SendbirdReaction,
6
8
  SendbirdSendableMessage,
7
9
  } from '../types';
8
- import { messageTime } from '../ui-format/common';
10
+ import { getMessageTimeFormat } from '../ui-format/common';
9
11
 
10
12
  export function isNewMessage(msg: SendbirdMessage, currentUserId?: string) {
11
13
  const myMessage = isMyMessage(msg, currentUserId);
@@ -67,7 +69,7 @@ export function calcMessageGrouping(
67
69
  if (!prev) return false;
68
70
  if (curr.isAdminMessage()) return false;
69
71
  if (!hasSameSender(curr, prev)) return false;
70
- if (messageTime(new Date(curr.createdAt)) !== messageTime(new Date(prev.createdAt))) return false;
72
+ if (getMessageTimeFormat(new Date(curr.createdAt)) !== getMessageTimeFormat(new Date(prev.createdAt))) return false;
71
73
  return true;
72
74
  };
73
75
 
@@ -76,7 +78,7 @@ export function calcMessageGrouping(
76
78
  if (!next) return false;
77
79
  if (curr.isAdminMessage()) return false;
78
80
  if (!hasSameSender(curr, next)) return false;
79
- if (messageTime(new Date(curr.createdAt)) !== messageTime(new Date(next.createdAt))) return false;
81
+ if (getMessageTimeFormat(new Date(curr.createdAt)) !== getMessageTimeFormat(new Date(next.createdAt))) return false;
80
82
  return true;
81
83
  };
82
84
 
@@ -110,3 +112,11 @@ export function isSendbirdNotification(dataPayload?: {
110
112
  export function parseSendbirdNotification(dataPayload: RawSendbirdDataPayload): SendbirdDataPayload {
111
113
  return typeof dataPayload.sendbird === 'string' ? JSON.parse(dataPayload.sendbird) : dataPayload.sendbird;
112
114
  }
115
+
116
+ export function shouldRenderReaction(channel: SendbirdBaseChannel, reactionEnabled: boolean) {
117
+ return channel.isGroupChannel() && !channel.isBroadcast && !channel.isSuper && reactionEnabled;
118
+ }
119
+
120
+ export function getReactionCount(reaction: SendbirdReaction) {
121
+ return reaction.userIds.length;
122
+ }
@@ -1,5 +1,7 @@
1
1
  import type { Role } from '@sendbird/chat';
2
2
 
3
+ import type { UserStruct } from '../types';
4
+
3
5
  export function ifOperator<T>(role: Role, then: T): T | undefined;
4
6
  export function ifOperator<T, V>(role: Role, then: T, or: V): T | V;
5
7
  export function ifOperator(role: Role, then: unknown, or?: unknown) {
@@ -13,3 +15,7 @@ export function ifMuted(muted: boolean, then: unknown, or?: unknown) {
13
15
  if (muted) return then;
14
16
  return or;
15
17
  }
18
+
19
+ export function getUserUniqId<T extends UserStruct>(user: T) {
20
+ return user.userId;
21
+ }
@@ -0,0 +1,56 @@
1
+ import { getFileExtension, getFileType } from './regex';
2
+
3
+ /**
4
+ * Converts a given string to a hashed string.
5
+ * */
6
+ export function hash(str: string) {
7
+ return String(Math.abs(str.split('').reduce((a, c) => ((a << 5) - a + c.charCodeAt(0)) | 0, 0)));
8
+ }
9
+
10
+ /**
11
+ * Replace a specific range of text in the string with another text.
12
+ * */
13
+ export function replace(str: string, start: number, end: number, text: string) {
14
+ return str.slice(0, start) + text + str.slice(end);
15
+ }
16
+
17
+ /**
18
+ * Returns the value corresponding to the first true index of a given condition array.
19
+ * */
20
+ export function conditionChaining<T, V>(conditions: T[], values: V[]) {
21
+ const idx = conditions.findIndex((state) => Boolean(state));
22
+ return idx > -1 ? values[idx] : values[values.length - 1];
23
+ }
24
+
25
+ /**
26
+ * Calculates the downscale size.
27
+ * */
28
+ type Size = { width: number; height: number };
29
+ export function getDownscaleSize(origin: Size, resizing: Partial<Size>) {
30
+ let ratio;
31
+
32
+ const maxWidth = resizing.width || origin.width,
33
+ maxHeight = resizing.height || origin.height;
34
+
35
+ if (origin.width <= maxWidth && origin.height <= maxHeight) {
36
+ ratio = 1;
37
+ } else if (origin.width > maxWidth && origin.height <= maxHeight) {
38
+ ratio = maxWidth / origin.width;
39
+ } else if (origin.width <= maxWidth && origin.height > maxHeight) {
40
+ ratio = maxHeight / origin.height;
41
+ } else {
42
+ ratio = Math.max(maxWidth / origin.width, maxHeight / origin.height);
43
+ }
44
+
45
+ return { width: origin.width * ratio, height: origin.height * ratio };
46
+ }
47
+
48
+ export function isImage(filePath: string, mimeType?: string) {
49
+ const type = getFileType(mimeType || getFileExtension(filePath));
50
+ return type === 'image';
51
+ }
52
+
53
+ export function shouldCompressImage(filePath: string, compressionEnabled = true) {
54
+ const extension = getFileExtension(filePath);
55
+ return Boolean(extension.match(/jpg|jpeg|png/i) && compressionEnabled);
56
+ }
@@ -9,6 +9,8 @@ export const phoneRegex = /[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,7}/
9
9
  export const emailRegex = /\S+@\S+\.\S+/;
10
10
  export const newLineRegex = /\r\n|\r|\n/g;
11
11
 
12
+ export const createMentionTemplateRegex = (trigger: string) => new RegExp(`(${trigger}[{])(.*?)([}])`, 'g');
13
+
12
14
  // const cases = [
13
15
  // {
14
16
  // type: 'urlStrict',
@@ -28,6 +30,39 @@ export const newLineRegex = /\r\n|\r|\n/g;
28
30
  // },
29
31
  // ];
30
32
 
33
+ export const replaceWithRegex = <T>(
34
+ text: string,
35
+ regex: RegExp,
36
+ replacer: (params: {
37
+ match: string;
38
+ groups: string[];
39
+ matchIndex: number | undefined;
40
+ index: number;
41
+ keyPrefix: string;
42
+ }) => T,
43
+ keyPrefix: string,
44
+ ) => {
45
+ const matches = [...text.matchAll(regex)];
46
+ const founds = matches.map((value) => {
47
+ const text = value[0];
48
+ const start = value.index ?? 0;
49
+ const end = start + text.length;
50
+ return { text, start, end, groups: value, matchIndex: value.index };
51
+ });
52
+
53
+ const items: Array<T | string> = [text];
54
+ let cursor = 0;
55
+ founds.forEach(({ text, start, end, groups, matchIndex }, index) => {
56
+ const restText = items.pop() as string;
57
+ const head = restText.slice(0, start - cursor);
58
+ const mid = replacer({ match: text, groups, matchIndex, index, keyPrefix });
59
+ const tail = restText.slice(end - cursor);
60
+ items.push(head, mid, tail);
61
+ cursor = end;
62
+ });
63
+ return items;
64
+ };
65
+
31
66
  export const replaceUrlAsComponents = <T>(originText: string, replacer: (url: string) => T, strict?: boolean) => {
32
67
  const matches = [...originText.matchAll(strict ? urlRegexStrict : urlRegexRough)];
33
68
  const founds = matches.map((value) => {
package/src/types.ts CHANGED
@@ -4,7 +4,16 @@
4
4
  import type React from 'react';
5
5
 
6
6
  import type SendbirdChat from '@sendbird/chat';
7
- import type { BaseChannel, RestrictedUser, SendbirdError as SBError, User, UserUpdateParams } from '@sendbird/chat';
7
+ import type {
8
+ BaseChannel,
9
+ Emoji,
10
+ EmojiCategory,
11
+ EmojiContainer,
12
+ RestrictedUser,
13
+ SendbirdError as SBError,
14
+ User,
15
+ UserUpdateParams,
16
+ } from '@sendbird/chat';
8
17
  import type {
9
18
  GroupChannel,
10
19
  GroupChannelCollection,
@@ -23,14 +32,13 @@ import type {
23
32
  FileMessageCreateParams,
24
33
  FileMessageUpdateParams,
25
34
  PreviousMessageListQuery,
35
+ Reaction,
26
36
  UserMessage,
27
37
  UserMessageCreateParams,
28
38
  UserMessageUpdateParams,
29
39
  } from '@sendbird/chat/message';
30
40
  import type { OpenChannel, OpenChannelModule } from '@sendbird/chat/openChannel';
31
41
 
32
- import './';
33
-
34
42
  export type FilterByValueType<T extends object, Type> = {
35
43
  [K in keyof T as T[K] extends Type ? K : never]: T[K];
36
44
  };
@@ -57,6 +65,10 @@ export type Optional<T> = T | undefined;
57
65
 
58
66
  export type ContextValue<T extends React.Context<any>> = T extends React.Context<infer V> ? V : never;
59
67
 
68
+ export interface UserStruct {
69
+ userId: string;
70
+ }
71
+
60
72
  export type SendbirdChatSDK = SendbirdChat & ModuleNamespaces<[GroupChannelModule, OpenChannelModule]>;
61
73
  export type SendbirdMessage = BaseMessage | FileMessage | UserMessage | AdminMessage | SendableMessage;
62
74
  export type SendbirdChannel = BaseChannel | GroupChannel | OpenChannel;
@@ -79,6 +91,11 @@ export type SendbirdGroupChannel = GroupChannel;
79
91
  export type SendbirdBaseChannel = BaseChannel;
80
92
  export type SendbirdOpenChannel = OpenChannel;
81
93
 
94
+ export type SendbirdReaction = Reaction;
95
+ export type SendbirdEmoji = Emoji;
96
+ export type SendbirdEmojiCategory = EmojiCategory;
97
+ export type SendbirdEmojiContainer = EmojiContainer;
98
+
82
99
  export type SendbirdGroupChannelCollection = GroupChannelCollection;
83
100
  export type SendbirdGroupChannelListQuery = GroupChannelListQuery;
84
101
  export type SendbirdMessageCollection = MessageCollection;
@@ -1,5 +1,5 @@
1
1
  import type { Locale } from 'date-fns';
2
- import format from 'date-fns/format';
2
+ import { format, isThisYear } from 'date-fns';
3
3
 
4
4
  type TruncateMode = 'head' | 'mid' | 'tail';
5
5
  type TruncateOption = { mode: TruncateMode; maxLen: number; separator: string };
@@ -38,15 +38,16 @@ export const truncate = (str: string, opts: Partial<TruncateOption> = defaultOpt
38
38
  };
39
39
 
40
40
  /**
41
- * Badge count truncate util
41
+ * Count truncate util
42
42
  * If count exceed the limit, it comes in the form of "MAX+"
43
43
  *
44
44
  * @param {number} count
45
45
  * @param {number} MAX default 99
46
+ * @param {string} MAX_SUFFIX default +
46
47
  * @returns {string}
47
48
  * */
48
- export const truncatedBadgeCount = (count: number, MAX = 99) => {
49
- if (count >= MAX) return `${MAX}+`;
49
+ export const truncatedCount = (count: number, MAX = 99, MAX_SUFFIX = '+') => {
50
+ if (count >= MAX) return `${MAX}${MAX_SUFFIX}`;
50
51
  return `${count}`;
51
52
  };
52
53
 
@@ -57,8 +58,9 @@ export const truncatedBadgeCount = (count: number, MAX = 99) => {
57
58
  * @param {Locale} [locale]
58
59
  * @returns {string}
59
60
  * */
60
- export const dateSeparator = (date: Date, locale?: Locale): string => {
61
- return format(date, 'E, LLL dd', { locale });
61
+ export const getDateSeparatorFormat = (date: Date, locale?: Locale): string => {
62
+ if (isThisYear(date)) return format(date, 'MMM dd, yyyy', { locale });
63
+ return format(date, 'E, MMM dd', { locale });
62
64
  };
63
65
 
64
66
  /**
@@ -68,6 +70,6 @@ export const dateSeparator = (date: Date, locale?: Locale): string => {
68
70
  * @param {Locale} [locale]
69
71
  * @returns {string}
70
72
  * */
71
- export const messageTime = (date: Date, locale?: Locale): string => {
73
+ export const getMessageTimeFormat = (date: Date, locale?: Locale): string => {
72
74
  return format(date, 'p', { locale });
73
75
  };
@@ -1,7 +1,5 @@
1
1
  import type { Locale } from 'date-fns';
2
- import format from 'date-fns/format';
3
- import isToday from 'date-fns/isToday';
4
- import isYesterday from 'date-fns/isYesterday';
2
+ import { format, isThisYear, isToday, isYesterday } from 'date-fns';
5
3
 
6
4
  import type { SendbirdGroupChannel } from '../types';
7
5
  import { truncate } from './common';
@@ -23,9 +21,12 @@ export const getGroupChannelTitle = (
23
21
 
24
22
  export const getGroupChannelPreviewTime = (channel: SendbirdGroupChannel, locale?: Locale) => {
25
23
  const timestamp = channel.lastMessage?.createdAt || channel.joinedAt * 1000 || channel.createdAt;
24
+
26
25
  if (isToday(timestamp)) return format(timestamp, 'p', { locale });
27
26
  if (isYesterday(timestamp)) return 'Yesterday';
28
- return format(timestamp, 'MMM dd', { locale });
27
+ if (isThisYear(timestamp)) return format(timestamp, 'MMM dd', { locale });
28
+
29
+ return format(timestamp, 'yyyy/MM/dd', { locale });
29
30
  };
30
31
 
31
32
  export const getGroupChannelLastMessage = (channel: SendbirdGroupChannel, EMPTY_MESSAGE = '', MAX_LEN = 15) => {
@@ -1,15 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- const conditionChaining = (conditions, values) => {
9
- const idx = conditions.findIndex(state => Boolean(state));
10
- return idx > -1 ? values[idx] : values[values.length - 1];
11
- };
12
-
13
- var _default = conditionChaining;
14
- exports.default = _default;
15
- //# sourceMappingURL=conditionChaining.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["conditionChaining","conditions","values","idx","findIndex","state","Boolean","length"],"sources":["conditionChaining.ts"],"sourcesContent":["const conditionChaining = <T, V>(conditions: T[], values: V[]) => {\n const idx = conditions.findIndex((state) => Boolean(state));\n return idx > -1 ? values[idx] : values[values.length - 1];\n};\n\nexport default conditionChaining;\n"],"mappings":";;;;;;;AAAA,MAAMA,iBAAiB,GAAG,CAAOC,UAAP,EAAwBC,MAAxB,KAAwC;EAChE,MAAMC,GAAG,GAAGF,UAAU,CAACG,SAAX,CAAsBC,KAAD,IAAWC,OAAO,CAACD,KAAD,CAAvC,CAAZ;EACA,OAAOF,GAAG,GAAG,CAAC,CAAP,GAAWD,MAAM,CAACC,GAAD,CAAjB,GAAyBD,MAAM,CAACA,MAAM,CAACK,MAAP,GAAgB,CAAjB,CAAtC;AACD,CAHD;;eAKeP,iB"}
@@ -1,7 +0,0 @@
1
- const conditionChaining = (conditions, values) => {
2
- const idx = conditions.findIndex(state => Boolean(state));
3
- return idx > -1 ? values[idx] : values[values.length - 1];
4
- };
5
-
6
- export default conditionChaining;
7
- //# sourceMappingURL=conditionChaining.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["conditionChaining","conditions","values","idx","findIndex","state","Boolean","length"],"sources":["conditionChaining.ts"],"sourcesContent":["const conditionChaining = <T, V>(conditions: T[], values: V[]) => {\n const idx = conditions.findIndex((state) => Boolean(state));\n return idx > -1 ? values[idx] : values[values.length - 1];\n};\n\nexport default conditionChaining;\n"],"mappings":"AAAA,MAAMA,iBAAiB,GAAG,CAAOC,UAAP,EAAwBC,MAAxB,KAAwC;EAChE,MAAMC,GAAG,GAAGF,UAAU,CAACG,SAAX,CAAsBC,KAAD,IAAWC,OAAO,CAACD,KAAD,CAAvC,CAAZ;EACA,OAAOF,GAAG,GAAG,CAAC,CAAP,GAAWD,MAAM,CAACC,GAAD,CAAjB,GAAyBD,MAAM,CAACA,MAAM,CAACK,MAAP,GAAgB,CAAjB,CAAtC;AACD,CAHD;;AAKA,eAAeP,iBAAf"}
@@ -1,2 +0,0 @@
1
- declare const conditionChaining: <T, V>(conditions: T[], values: V[]) => V;
2
- export default conditionChaining;
@@ -1,6 +0,0 @@
1
- const conditionChaining = <T, V>(conditions: T[], values: V[]) => {
2
- const idx = conditions.findIndex((state) => Boolean(state));
3
- return idx > -1 ? values[idx] : values[values.length - 1];
4
- };
5
-
6
- export default conditionChaining;