@droppii-org/chat-mobile 0.2.80 → 0.2.82

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 (228) hide show
  1. package/lib/module/components/ThreadCard/MentionBadge.js +24 -0
  2. package/lib/module/components/ThreadCard/MentionBadge.js.map +1 -0
  3. package/lib/module/components/ThreadCard/ThreadCard.js +5 -1
  4. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  5. package/lib/module/components/messages/linkMessage/LinkPreviewCard.js +67 -0
  6. package/lib/module/components/messages/linkMessage/LinkPreviewCard.js.map +1 -0
  7. package/lib/module/components/messages/linkMessage/index.js +21 -68
  8. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  9. package/lib/module/components/messages/quotedMessage/QuotedMessagePreviewStrip.js +152 -0
  10. package/lib/module/components/messages/quotedMessage/QuotedMessagePreviewStrip.js.map +1 -0
  11. package/lib/module/components/messages/textMessage/index.js +102 -8
  12. package/lib/module/components/messages/textMessage/index.js.map +1 -1
  13. package/lib/module/config/api-endpoints.js +2 -1
  14. package/lib/module/config/api-endpoints.js.map +1 -1
  15. package/lib/module/core/useChatListener.js +1 -1
  16. package/lib/module/core/useChatListener.js.map +1 -1
  17. package/lib/module/hooks/contacts/useSearchContacts.js +50 -0
  18. package/lib/module/hooks/contacts/useSearchContacts.js.map +1 -0
  19. package/lib/module/hooks/message/useSendMessage.js +64 -3
  20. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  21. package/lib/module/hooks/query-keys.js +8 -0
  22. package/lib/module/hooks/query-keys.js.map +1 -1
  23. package/lib/module/hooks/useChatCompose.js +3 -2
  24. package/lib/module/hooks/useChatCompose.js.map +1 -1
  25. package/lib/module/screens/SearchConversation/ContactResultItem.js +119 -0
  26. package/lib/module/screens/SearchConversation/ContactResultItem.js.map +1 -0
  27. package/lib/module/screens/SearchConversation/SearchAllResult.js +26 -20
  28. package/lib/module/screens/SearchConversation/SearchAllResult.js.map +1 -1
  29. package/lib/module/screens/SearchConversation/SearchCommunityResult.js +79 -0
  30. package/lib/module/screens/SearchConversation/SearchCommunityResult.js.map +1 -0
  31. package/lib/module/screens/SearchConversation/SearchContactsResult.js +117 -0
  32. package/lib/module/screens/SearchConversation/SearchContactsResult.js.map +1 -0
  33. package/lib/module/screens/SearchConversation/SearchEmpty.js +2 -0
  34. package/lib/module/screens/SearchConversation/SearchEmpty.js.map +1 -1
  35. package/lib/module/screens/SearchConversation/SearchMessagesResult.js +1 -8
  36. package/lib/module/screens/SearchConversation/SearchMessagesResult.js.map +1 -1
  37. package/lib/module/screens/SearchConversation/SearchResultItem.js +2 -0
  38. package/lib/module/screens/SearchConversation/SearchResultItem.js.map +1 -1
  39. package/lib/module/screens/SearchConversation/TabResult.js +24 -7
  40. package/lib/module/screens/SearchConversation/TabResult.js.map +1 -1
  41. package/lib/module/screens/SearchConversation/index.js +6 -2
  42. package/lib/module/screens/SearchConversation/index.js.map +1 -1
  43. package/lib/module/screens/SearchConversation/types.js +1 -0
  44. package/lib/module/screens/SearchConversation/types.js.map +1 -1
  45. package/lib/module/screens/chat-detail/ChatComposer.js +170 -48
  46. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  47. package/lib/module/screens/chat-detail/ChatListLegend.js +1 -0
  48. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  49. package/lib/module/screens/chat-detail/components/ChatMessageInput.js +23 -2
  50. package/lib/module/screens/chat-detail/components/ChatMessageInput.js.map +1 -1
  51. package/lib/module/screens/chat-detail/components/MentionHighlightAdapter.js +51 -0
  52. package/lib/module/screens/chat-detail/components/MentionHighlightAdapter.js.map +1 -0
  53. package/lib/module/screens/chat-detail/components/MentionSuggestionItem.js +49 -0
  54. package/lib/module/screens/chat-detail/components/MentionSuggestionItem.js.map +1 -0
  55. package/lib/module/screens/chat-detail/components/MentionSuggestionList.js +114 -0
  56. package/lib/module/screens/chat-detail/components/MentionSuggestionList.js.map +1 -0
  57. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +8 -0
  58. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
  59. package/lib/module/screens/chat-detail/hooks/useMentionComposer.js +251 -0
  60. package/lib/module/screens/chat-detail/hooks/useMentionComposer.js.map +1 -0
  61. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +5 -1
  62. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  63. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +11 -1
  64. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -1
  65. package/lib/module/services/endpoints.js +2 -1
  66. package/lib/module/services/endpoints.js.map +1 -1
  67. package/lib/module/services/mentionSearch.js +49 -0
  68. package/lib/module/services/mentionSearch.js.map +1 -0
  69. package/lib/module/services/message.js +6 -1
  70. package/lib/module/services/message.js.map +1 -1
  71. package/lib/module/translation/resources/i18n.js +11 -0
  72. package/lib/module/translation/resources/i18n.js.map +1 -1
  73. package/lib/module/types/chat.js +8 -0
  74. package/lib/module/types/chat.js.map +1 -1
  75. package/lib/module/types/mention.js +2 -0
  76. package/lib/module/types/mention.js.map +1 -0
  77. package/lib/module/utils/dateTimeUtils.js +10 -0
  78. package/lib/module/utils/dateTimeUtils.js.map +1 -1
  79. package/lib/module/utils/highlightSearch.js +6 -2
  80. package/lib/module/utils/highlightSearch.js.map +1 -1
  81. package/lib/module/utils/mentionSerializer.js +113 -0
  82. package/lib/module/utils/mentionSerializer.js.map +1 -0
  83. package/lib/module/utils/mentions/index.js +7 -0
  84. package/lib/module/utils/mentions/index.js.map +1 -0
  85. package/lib/module/utils/mentions/mentionDetector.js +49 -0
  86. package/lib/module/utils/mentions/mentionDetector.js.map +1 -0
  87. package/lib/module/utils/mentions/mentionDiff.js +66 -0
  88. package/lib/module/utils/mentions/mentionDiff.js.map +1 -0
  89. package/lib/module/utils/mentions/mentionRange.js +188 -0
  90. package/lib/module/utils/mentions/mentionRange.js.map +1 -0
  91. package/lib/module/utils/mentions/mentionTokenize.js +50 -0
  92. package/lib/module/utils/mentions/mentionTokenize.js.map +1 -0
  93. package/lib/module/utils/messageUtils.js +16 -3
  94. package/lib/module/utils/messageUtils.js.map +1 -1
  95. package/lib/module/utils/url.js +33 -0
  96. package/lib/module/utils/url.js.map +1 -1
  97. package/lib/typescript/src/components/ThreadCard/MentionBadge.d.ts +7 -0
  98. package/lib/typescript/src/components/ThreadCard/MentionBadge.d.ts.map +1 -0
  99. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  100. package/lib/typescript/src/components/messages/linkMessage/LinkPreviewCard.d.ts +16 -0
  101. package/lib/typescript/src/components/messages/linkMessage/LinkPreviewCard.d.ts.map +1 -0
  102. package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
  103. package/lib/typescript/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.d.ts +13 -0
  104. package/lib/typescript/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.d.ts.map +1 -0
  105. package/lib/typescript/src/components/messages/textMessage/index.d.ts +3 -1
  106. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  107. package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
  108. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  109. package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts +221 -0
  110. package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts.map +1 -0
  111. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -2
  112. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  113. package/lib/typescript/src/hooks/query-keys.d.ts +8 -0
  114. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  115. package/lib/typescript/src/hooks/useChatCompose.d.ts +2 -1
  116. package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
  117. package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts +9 -0
  118. package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts.map +1 -0
  119. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts +3 -2
  120. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts.map +1 -1
  121. package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts +7 -0
  122. package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts.map +1 -0
  123. package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts +8 -0
  124. package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts.map +1 -0
  125. package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts.map +1 -1
  126. package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts.map +1 -1
  127. package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts.map +1 -1
  128. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts +4 -2
  129. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts.map +1 -1
  130. package/lib/typescript/src/screens/SearchConversation/index.d.ts +4 -2
  131. package/lib/typescript/src/screens/SearchConversation/index.d.ts.map +1 -1
  132. package/lib/typescript/src/screens/SearchConversation/types.d.ts +2 -1
  133. package/lib/typescript/src/screens/SearchConversation/types.d.ts.map +1 -1
  134. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  135. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  136. package/lib/typescript/src/screens/chat-detail/components/ChatMessageInput.d.ts +10 -2
  137. package/lib/typescript/src/screens/chat-detail/components/ChatMessageInput.d.ts.map +1 -1
  138. package/lib/typescript/src/screens/chat-detail/components/MentionHighlightAdapter.d.ts +29 -0
  139. package/lib/typescript/src/screens/chat-detail/components/MentionHighlightAdapter.d.ts.map +1 -0
  140. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionItem.d.ts +8 -0
  141. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionItem.d.ts.map +1 -0
  142. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionList.d.ts +12 -0
  143. package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionList.d.ts.map +1 -0
  144. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +1 -1
  145. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
  146. package/lib/typescript/src/screens/chat-detail/hooks/useMentionComposer.d.ts +48 -0
  147. package/lib/typescript/src/screens/chat-detail/hooks/useMentionComposer.d.ts.map +1 -0
  148. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  149. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -1
  150. package/lib/typescript/src/services/endpoints.d.ts +1 -0
  151. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  152. package/lib/typescript/src/services/mentionSearch.d.ts +15 -0
  153. package/lib/typescript/src/services/mentionSearch.d.ts.map +1 -0
  154. package/lib/typescript/src/services/message.d.ts +1 -1
  155. package/lib/typescript/src/services/message.d.ts.map +1 -1
  156. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  157. package/lib/typescript/src/types/chat.d.ts +50 -0
  158. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  159. package/lib/typescript/src/types/mention.d.ts +65 -0
  160. package/lib/typescript/src/types/mention.d.ts.map +1 -0
  161. package/lib/typescript/src/utils/dateTimeUtils.d.ts +2 -0
  162. package/lib/typescript/src/utils/dateTimeUtils.d.ts.map +1 -1
  163. package/lib/typescript/src/utils/highlightSearch.d.ts.map +1 -1
  164. package/lib/typescript/src/utils/mentionSerializer.d.ts +78 -0
  165. package/lib/typescript/src/utils/mentionSerializer.d.ts.map +1 -0
  166. package/lib/typescript/src/utils/mentions/index.d.ts +5 -0
  167. package/lib/typescript/src/utils/mentions/index.d.ts.map +1 -0
  168. package/lib/typescript/src/utils/mentions/mentionDetector.d.ts +18 -0
  169. package/lib/typescript/src/utils/mentions/mentionDetector.d.ts.map +1 -0
  170. package/lib/typescript/src/utils/mentions/mentionDiff.d.ts +20 -0
  171. package/lib/typescript/src/utils/mentions/mentionDiff.d.ts.map +1 -0
  172. package/lib/typescript/src/utils/mentions/mentionRange.d.ts +86 -0
  173. package/lib/typescript/src/utils/mentions/mentionRange.d.ts.map +1 -0
  174. package/lib/typescript/src/utils/mentions/mentionTokenize.d.ts +29 -0
  175. package/lib/typescript/src/utils/mentions/mentionTokenize.d.ts.map +1 -0
  176. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  177. package/lib/typescript/src/utils/url.d.ts +12 -0
  178. package/lib/typescript/src/utils/url.d.ts.map +1 -1
  179. package/package.json +3 -1
  180. package/src/build-ignore.d.ts +2 -0
  181. package/src/components/ThreadCard/MentionBadge.tsx +28 -0
  182. package/src/components/ThreadCard/ThreadCard.tsx +3 -0
  183. package/src/components/messages/linkMessage/LinkPreviewCard.tsx +92 -0
  184. package/src/components/messages/linkMessage/index.tsx +25 -89
  185. package/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.tsx +168 -0
  186. package/src/components/messages/textMessage/index.tsx +124 -4
  187. package/src/config/api-endpoints.ts +2 -0
  188. package/src/core/useChatListener.ts +1 -1
  189. package/src/hooks/contacts/useSearchContacts.ts +66 -0
  190. package/src/hooks/message/useSendMessage.ts +60 -1
  191. package/src/hooks/query-keys.ts +12 -0
  192. package/src/hooks/useChatCompose.ts +115 -107
  193. package/src/screens/SearchConversation/ContactResultItem.tsx +128 -0
  194. package/src/screens/SearchConversation/SearchAllResult.tsx +38 -24
  195. package/src/screens/SearchConversation/SearchCommunityResult.tsx +96 -0
  196. package/src/screens/SearchConversation/SearchContactsResult.tsx +155 -0
  197. package/src/screens/SearchConversation/SearchEmpty.tsx +2 -0
  198. package/src/screens/SearchConversation/SearchMessagesResult.tsx +1 -6
  199. package/src/screens/SearchConversation/SearchResultItem.tsx +6 -1
  200. package/src/screens/SearchConversation/TabResult.tsx +40 -5
  201. package/src/screens/SearchConversation/index.tsx +10 -2
  202. package/src/screens/SearchConversation/types.tsx +1 -0
  203. package/src/screens/chat-detail/ChatComposer.tsx +201 -67
  204. package/src/screens/chat-detail/ChatListLegend.tsx +1 -0
  205. package/src/screens/chat-detail/components/ChatMessageInput.tsx +35 -2
  206. package/src/screens/chat-detail/components/MentionHighlightAdapter.tsx +57 -0
  207. package/src/screens/chat-detail/components/MentionSuggestionItem.tsx +53 -0
  208. package/src/screens/chat-detail/components/MentionSuggestionList.tsx +133 -0
  209. package/src/screens/chat-detail/hooks/useChatComposerState.ts +7 -1
  210. package/src/screens/chat-detail/hooks/useMentionComposer.ts +344 -0
  211. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +7 -1
  212. package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +22 -4
  213. package/src/services/endpoints.ts +1 -0
  214. package/src/services/mentionSearch.ts +57 -0
  215. package/src/services/message.ts +9 -1
  216. package/src/translation/resources/i18n.ts +12 -0
  217. package/src/types/chat.ts +52 -0
  218. package/src/types/mention.ts +70 -0
  219. package/src/utils/dateTimeUtils.ts +15 -0
  220. package/src/utils/highlightSearch.ts +12 -2
  221. package/src/utils/mentionSerializer.ts +155 -0
  222. package/src/utils/mentions/index.ts +16 -0
  223. package/src/utils/mentions/mentionDetector.ts +58 -0
  224. package/src/utils/mentions/mentionDiff.ts +95 -0
  225. package/src/utils/mentions/mentionRange.ts +207 -0
  226. package/src/utils/mentions/mentionTokenize.ts +53 -0
  227. package/src/utils/messageUtils.ts +21 -10
  228. package/src/utils/url.ts +30 -0
@@ -1 +1 @@
1
- {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../../../src/utils/url.ts"],"names":[],"mappings":"AA+BA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAElD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,UAKvC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD"}
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../../../src/utils/url.ts"],"names":[],"mappings":"AA+BA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAElD;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAgB3E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,UAKvC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@droppii-org/chat-mobile",
3
- "version": "0.2.80",
3
+ "version": "0.2.82",
4
4
  "description": "Droppii chat mobile",
5
5
  "keywords": [
6
6
  "react-native",
@@ -100,6 +100,7 @@
100
100
  "@types/react-native-snap-carousel": "^3.8.5",
101
101
  "@types/react-query": "^1.2.9",
102
102
  "commitlint": "^20.5.0",
103
+ "dayjs": "1.11.18",
103
104
  "del-cli": "^7.0.0",
104
105
  "deprecated-react-native-prop-types": "5.0.0",
105
106
  "eslint": "^9.39.4",
@@ -143,6 +144,7 @@
143
144
  "@react-native-community/netinfo": "*",
144
145
  "@react-native-documents/picker": "*",
145
146
  "@tanstack/react-query": "*",
147
+ "dayjs": "*",
146
148
  "react": "*",
147
149
  "react-native": "*",
148
150
  "react-native-blob-util": "*",
@@ -8,6 +8,7 @@ declare module '@droppii/libs' {
8
8
  export type KToastBarProps = any;
9
9
  export type KListItemBaseItemProps = any;
10
10
  export type KSpacing = any;
11
+ export type KTagsChoiceSlideBarItemProps = any;
11
12
 
12
13
  export const KColors: any;
13
14
  export const KSpacingValue: any;
@@ -26,4 +27,5 @@ declare module '@droppii/libs' {
26
27
  export const KFonts: any;
27
28
  export const KBadge: any;
28
29
  export const KNavigation: any;
30
+ export const KTagsChoiceSlideBar: any;
29
31
  }
@@ -0,0 +1,28 @@
1
+ import { memo } from 'react';
2
+ import { KLabel, KColors } from '@droppii/libs';
3
+ import { GroupAtType } from '@droppii/openim-rn-client-sdk';
4
+
5
+ interface MentionBadgeProps {
6
+ groupAtType?: GroupAtType;
7
+ }
8
+
9
+ // Figma (node 15177-302069): "@" renders as a standalone bold glyph, no
10
+ // background — same color token as the composer/bubble mention highlight
11
+ // (KColors.palette.primary.w400), not a new one-off value.
12
+ export const MentionBadge = memo(({ groupAtType }: MentionBadgeProps) => {
13
+ if (groupAtType === undefined || groupAtType === GroupAtType.AtNormal) {
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <KLabel.Text
19
+ typo="TextNmBold"
20
+ color={KColors.palette.primary.w400}
21
+ marginL="0.5rem"
22
+ >
23
+ @
24
+ </KLabel.Text>
25
+ );
26
+ });
27
+
28
+ MentionBadge.displayName = 'MentionBadge';
@@ -6,6 +6,7 @@ import { KContainer, KImage, KLabel, KColors } from '@droppii/libs';
6
6
  import { AvatarSection } from './AvatarSection';
7
7
  import { NamePrefixIcon } from './NamePrefixIcon';
8
8
  import { UnreadBadge } from './UnreadBadge';
9
+ import { MentionBadge } from './MentionBadge';
9
10
  import { formatTimestamp } from '../../utils/dateTimeUtils';
10
11
  import {
11
12
  getLastMessageText,
@@ -44,6 +45,7 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
44
45
  latestMsgSendTime,
45
46
  latestMsg,
46
47
  groupID,
48
+ groupAtType,
47
49
  } = conversation || {};
48
50
 
49
51
  const { data: isJoinedGroup } = useIsJoinedGroup(groupID);
@@ -183,6 +185,7 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
183
185
  />
184
186
  </KContainer.View>
185
187
  </KContainer.VisibleView>
188
+ <MentionBadge groupAtType={groupAtType} />
186
189
  <UnreadBadge count={unreadCount} />
187
190
  </KContainer.View>
188
191
  </KContainer.View>
@@ -0,0 +1,92 @@
1
+ import { memo } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import {
4
+ KContainer,
5
+ KImage,
6
+ KLabel,
7
+ KColors,
8
+ KSpacingValue,
9
+ } from '@droppii/libs';
10
+ import type { IUrlMetadata } from '../../../types/common';
11
+
12
+ interface LinkPreviewCardProps {
13
+ metadata: IUrlMetadata;
14
+ onPressUrl?: (url: string) => void;
15
+ }
16
+
17
+ /**
18
+ * The rich link-preview card (thumbnail/title/url/description) — shared by
19
+ * LinkMessage (message.urlTextElem, a plain URL message) and TextMessage
20
+ * (mention+URL combo: message.ex.urlMetadata attached to an AtTextMessage,
21
+ * since OpenIM's AtTextMessage/UrlTextMessage content types are mutually
22
+ * exclusive — see useSendMessage.ts's doc comment for why the mention wins
23
+ * the content type and the URL card rides along in `ex` instead).
24
+ */
25
+ export const LinkPreviewCard = memo(
26
+ ({ metadata, onPressUrl }: LinkPreviewCardProps) => (
27
+ <KContainer.Touchable
28
+ style={styles.card}
29
+ onPress={() => metadata.url && onPressUrl?.(metadata.url)}
30
+ >
31
+ {!!metadata.image && (
32
+ <KContainer.View style={styles.cardThumb}>
33
+ <KImage.Base
34
+ uri={metadata.image}
35
+ style={StyleSheet.absoluteFill}
36
+ resizeMode="cover"
37
+ />
38
+ </KContainer.View>
39
+ )}
40
+ <KContainer.View style={styles.cardBody}>
41
+ {!!metadata.title && (
42
+ <KLabel.Text
43
+ typo="TextNmMedium"
44
+ color={KColors.palette.gray.w900}
45
+ numberOfLines={1}
46
+ >
47
+ {metadata.title}
48
+ </KLabel.Text>
49
+ )}
50
+ {!!metadata.url && (
51
+ <KLabel.Text
52
+ typo="TextXsMedium"
53
+ color={KColors.palette.primary.w400}
54
+ numberOfLines={1}
55
+ >
56
+ {metadata.url}
57
+ </KLabel.Text>
58
+ )}
59
+ {!!metadata.description && (
60
+ <KLabel.Text
61
+ typo="TextXsNormal"
62
+ color={KColors.gray.normal}
63
+ numberOfLines={2}
64
+ >
65
+ {metadata.description}
66
+ </KLabel.Text>
67
+ )}
68
+ </KContainer.View>
69
+ </KContainer.Touchable>
70
+ )
71
+ );
72
+
73
+ LinkPreviewCard.displayName = 'LinkPreviewCard';
74
+
75
+ const styles = StyleSheet.create({
76
+ card: {
77
+ borderRadius: KSpacingValue['0.75rem'],
78
+ borderWidth: 1,
79
+ borderColor: 'rgba(57,62,64,0.1)',
80
+ backgroundColor: KColors.white,
81
+ overflow: 'hidden',
82
+ },
83
+ cardThumb: {
84
+ width: '100%',
85
+ aspectRatio: 16 / 9,
86
+ },
87
+ cardBody: {
88
+ paddingHorizontal: KSpacingValue['0.75rem'],
89
+ paddingVertical: KSpacingValue['0.5rem'],
90
+ gap: 2,
91
+ },
92
+ });
@@ -1,16 +1,12 @@
1
1
  import { memo } from 'react';
2
2
  import { StyleSheet } from 'react-native';
3
- import {
4
- KContainer,
5
- KImage,
6
- KLabel,
7
- KColors,
8
- KSpacingValue,
9
- } from '@droppii/libs';
3
+ import { KContainer, KLabel, KColors, KSpacingValue } from '@droppii/libs';
10
4
  import type { BaseLegendMessageProps } from '../types';
11
5
  import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
12
6
  import { parseUrlMetadata } from '../../../utils/messageUtils';
7
+ import { splitTextByUrls } from '../../../utils/url';
13
8
  import { messageStyles } from '../styles';
9
+ import { LinkPreviewCard } from './LinkPreviewCard';
14
10
  import { getTailCornerStyle } from '../bubbleTail.utils';
15
11
  import { BubbleTailRow } from '../BubbleTailRow';
16
12
 
@@ -18,45 +14,26 @@ const renderTextWithLinks = (
18
14
  content: string,
19
15
  urls: string[],
20
16
  onPressUrl?: (url: string) => void
21
- ) => {
22
- if (!urls.length) {
23
- return <KLabel.Text typo="TextMdNormal">{content}</KLabel.Text>;
24
- }
25
-
26
- const parts: { text: string; isUrl: boolean }[] = [];
27
- let remaining = content;
28
-
29
- for (const url of urls) {
30
- const idx = remaining.indexOf(url);
31
- if (idx === -1) continue;
32
- if (idx > 0) parts.push({ text: remaining.slice(0, idx), isUrl: false });
33
- parts.push({ text: url, isUrl: true });
34
- remaining = remaining.slice(idx + url.length);
35
- }
36
-
37
- if (remaining) parts.push({ text: remaining, isUrl: false });
38
-
39
- return (
40
- <KLabel.Text typo="TextMdNormal">
41
- {parts.map((part, i) =>
42
- part.isUrl ? (
43
- <KLabel.Text
44
- key={i}
45
- onPress={() => onPressUrl?.(part.text)}
46
- color={KColors.primary.normal}
47
- typo="TextMdMedium"
48
- >
49
- {part.text}
50
- </KLabel.Text>
51
- ) : (
52
- <KLabel.Text typo="TextMdNormal" key={i}>
53
- {part.text}
54
- </KLabel.Text>
55
- )
56
- )}
57
- </KLabel.Text>
58
- );
59
- };
17
+ ) => (
18
+ <KLabel.Text typo="TextMdNormal">
19
+ {splitTextByUrls(content, urls).map((part, i) =>
20
+ part.isUrl ? (
21
+ <KLabel.Text
22
+ key={i}
23
+ onPress={() => onPressUrl?.(part.text)}
24
+ color={KColors.primary.normal}
25
+ typo="TextMdMedium"
26
+ >
27
+ {part.text}
28
+ </KLabel.Text>
29
+ ) : (
30
+ <KLabel.Text typo="TextMdNormal" key={i}>
31
+ {part.text}
32
+ </KLabel.Text>
33
+ )
34
+ )}
35
+ </KLabel.Text>
36
+ );
60
37
 
61
38
  export const LinkMessage = memo(
62
39
  ({
@@ -87,49 +64,7 @@ export const LinkMessage = memo(
87
64
  )}
88
65
 
89
66
  {!!metadata && (
90
- <KContainer.Touchable
91
- style={styles.card}
92
- onPress={() => metadata.url && onPressUrl?.(metadata.url)}
93
- >
94
- {!!metadata.image && (
95
- <KContainer.View style={styles.cardThumb}>
96
- <KImage.Base
97
- uri={metadata.image}
98
- style={StyleSheet.absoluteFill}
99
- resizeMode="cover"
100
- />
101
- </KContainer.View>
102
- )}
103
- <KContainer.View style={styles.cardBody}>
104
- {!!metadata.title && (
105
- <KLabel.Text
106
- typo="TextNmMedium"
107
- color={KColors.palette.gray.w900}
108
- numberOfLines={1}
109
- >
110
- {metadata.title}
111
- </KLabel.Text>
112
- )}
113
- {!!metadata.url && (
114
- <KLabel.Text
115
- typo="TextXsMedium"
116
- color={KColors.palette.primary.w400}
117
- numberOfLines={1}
118
- >
119
- {metadata.url}
120
- </KLabel.Text>
121
- )}
122
- {!!metadata.description && (
123
- <KLabel.Text
124
- typo="TextXsNormal"
125
- color={KColors.gray.normal}
126
- numberOfLines={2}
127
- >
128
- {metadata.description}
129
- </KLabel.Text>
130
- )}
131
- </KContainer.View>
132
- </KContainer.Touchable>
67
+ <LinkPreviewCard metadata={metadata} onPressUrl={onPressUrl} />
133
68
  )}
134
69
  </KContainer.View>
135
70
  </BubbleTailRow>
@@ -157,6 +92,7 @@ const styles = StyleSheet.create({
157
92
  textRow: {
158
93
  paddingHorizontal: KSpacingValue['0.25rem'],
159
94
  },
95
+ timeLabel: { gap: 4, marginTop: 2 },
160
96
  card: {
161
97
  borderRadius: KSpacingValue['0.75rem'],
162
98
  borderWidth: 1,
@@ -0,0 +1,168 @@
1
+ import { memo, useCallback, useMemo } from 'react';
2
+ import { DeviceEventEmitter, StyleSheet } from 'react-native';
3
+ import {
4
+ KColors,
5
+ KContainer,
6
+ KImage,
7
+ KLabel,
8
+ KRadiusValue,
9
+ KSpacingValue,
10
+ } from '@droppii/libs';
11
+ import { MessageType, type MessageItem } from '@droppii/openim-rn-client-sdk';
12
+ import {
13
+ getMessagePreviewParts,
14
+ getReplySenderName,
15
+ parseUrlMetadata,
16
+ } from '../../../utils/messageUtils';
17
+ import { getFileIcon, getMimeType } from '../../../utils/imageUtils';
18
+ import { useUserStore } from '../../../store';
19
+ import { useChatDetailContext } from '../../../context';
20
+ import { ChatEvent } from '../../../types/events';
21
+
22
+ interface QuotedMessagePreviewStripProps {
23
+ quotedMessage?: MessageItem;
24
+ }
25
+
26
+ /**
27
+ * The "replying to ..." strip — shared by QuotedMessage (message.quoteElem)
28
+ * and TextMessage (message.atTextElem?.quoteMessage, for the mention+quote
29
+ * combo). Extracted so both content types render the exact same preview
30
+ * instead of two copies drifting apart.
31
+ */
32
+ export const QuotedMessagePreviewStrip = memo(
33
+ ({ quotedMessage }: QuotedMessagePreviewStripProps) => {
34
+ const { conversation } = useChatDetailContext();
35
+ const currentUserId = useUserStore((s) => s.user?.userID);
36
+ const messagePreview = getMessagePreviewParts(quotedMessage);
37
+
38
+ const imageUrl = useMemo(() => {
39
+ switch (quotedMessage?.contentType) {
40
+ case MessageType.PictureMessage:
41
+ return quotedMessage?.pictureElem?.snapshotPicture?.url || '';
42
+ case MessageType.VideoMessage:
43
+ return quotedMessage?.videoElem?.snapshotUrl || '';
44
+ case MessageType.UrlTextMessage: {
45
+ const metadata = parseUrlMetadata(quotedMessage?.ex);
46
+ return metadata?.image || '';
47
+ }
48
+ default:
49
+ return '';
50
+ }
51
+ }, [quotedMessage]);
52
+
53
+ const iconSource = useMemo(() => {
54
+ if (quotedMessage?.contentType !== MessageType.FileMessage) return '';
55
+ const mimeType = getMimeType(quotedMessage?.fileElem?.fileName || '');
56
+ return getFileIcon(mimeType);
57
+ }, [quotedMessage]);
58
+
59
+ const handlePress = useCallback(() => {
60
+ DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
61
+ clientMessageId: quotedMessage?.clientMsgID || '',
62
+ });
63
+ }, [quotedMessage]);
64
+
65
+ return (
66
+ <KContainer.Touchable
67
+ style={styles.quotedContainer}
68
+ onPress={handlePress}
69
+ >
70
+ <KContainer.View style={styles.dot} />
71
+ <KContainer.VisibleView visible={!!imageUrl}>
72
+ <KContainer.View style={styles.imageContainer}>
73
+ <KImage.Base uri={imageUrl} style={styles.imageContainer} />
74
+ <KContainer.VisibleView
75
+ visible={quotedMessage?.contentType === MessageType.VideoMessage}
76
+ >
77
+ <KContainer.View style={styles.playIconContainer}>
78
+ <KContainer.View style={styles.playIcon}>
79
+ <KImage.VectorIcons
80
+ name="play-b"
81
+ size={12}
82
+ color={KColors.white}
83
+ />
84
+ </KContainer.View>
85
+ </KContainer.View>
86
+ </KContainer.VisibleView>
87
+ </KContainer.View>
88
+ </KContainer.VisibleView>
89
+ <KContainer.VisibleView visible={!!iconSource}>
90
+ <KImage.Base
91
+ uri={iconSource}
92
+ style={styles.icon}
93
+ resizeMode="contain"
94
+ />
95
+ </KContainer.VisibleView>
96
+ <KContainer.View flexS={1} style={styles.content}>
97
+ <KLabel.Text typo="TextNmMedium" numberOfLines={1}>
98
+ {getReplySenderName(
99
+ quotedMessage,
100
+ currentUserId,
101
+ conversation,
102
+ true
103
+ )}
104
+ </KLabel.Text>
105
+ <KLabel.Text
106
+ typo="TextSmNormal"
107
+ color={KColors.gray.light}
108
+ numberOfLines={1}
109
+ {...messagePreview.textProps}
110
+ >
111
+ {messagePreview.text}
112
+ </KLabel.Text>
113
+ </KContainer.View>
114
+ </KContainer.Touchable>
115
+ );
116
+ }
117
+ );
118
+
119
+ QuotedMessagePreviewStrip.displayName = 'QuotedMessagePreviewStrip';
120
+
121
+ const styles = StyleSheet.create({
122
+ quotedContainer: {
123
+ backgroundColor: KColors.white,
124
+ flexDirection: 'row',
125
+ alignItems: 'center',
126
+ gap: KSpacingValue['0.5rem'],
127
+ borderTopRightRadius: KRadiusValue['3x'],
128
+ borderBottomRightRadius: KRadiusValue['3x'],
129
+ paddingRight: KSpacingValue['0.5rem'],
130
+ maxWidth: '100%',
131
+ },
132
+ dot: {
133
+ width: 3,
134
+ height: '100%',
135
+ backgroundColor: KColors.palette.grayCool.w200,
136
+ borderRadius: KRadiusValue.x,
137
+ marginRight: -2,
138
+ },
139
+ content: {
140
+ paddingHorizontal: KSpacingValue['0.25rem'],
141
+ paddingVertical: KSpacingValue['0.25rem'],
142
+ },
143
+ icon: {
144
+ width: 24,
145
+ height: 24,
146
+ },
147
+ imageContainer: {
148
+ width: 42,
149
+ height: 42,
150
+ borderRadius: KRadiusValue['2x'],
151
+ },
152
+ playIconContainer: {
153
+ position: 'absolute',
154
+ top: 0,
155
+ left: 0,
156
+ right: 0,
157
+ bottom: 0,
158
+ justifyContent: 'center',
159
+ alignItems: 'center',
160
+ },
161
+ playIcon: {
162
+ borderRadius: 8,
163
+ padding: KSpacingValue['0.25rem'],
164
+ backgroundColor: KColors.hexToRgba(KColors.black, 0.6),
165
+ justifyContent: 'center',
166
+ alignItems: 'center',
167
+ },
168
+ });
@@ -1,29 +1,116 @@
1
- import { memo } from 'react';
2
- import { KContainer, KLabel } from '@droppii/libs';
1
+ import { memo, useMemo } from 'react';
2
+ import { Text } from 'react-native';
3
+ import { MessageType } from '@droppii/openim-rn-client-sdk';
4
+ import { KContainer, KLabel, KColors } from '@droppii/libs';
3
5
  import { getMessageText } from '../../../utils/legendListMessage';
4
6
  import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
5
7
  import { messageStyles } from '../styles';
6
8
  import { getTailCornerStyle } from '../bubbleTail.utils';
7
9
  import { BubbleTailRow } from '../BubbleTailRow';
8
10
  import type { BaseLegendMessageProps } from '../types';
11
+ import { StyleSheet } from 'react-native';
12
+ import { tokenize } from '../../../utils/mentions';
13
+ import { deserializeMentionRanges } from '../../../utils/mentionSerializer';
14
+ import type { IMessageItemEx } from '../../../types/chat';
15
+ import { QuotedMessagePreviewStrip } from '../quotedMessage/QuotedMessagePreviewStrip';
16
+ import { LinkPreviewCard } from '../linkMessage/LinkPreviewCard';
17
+ import { extractUrls, splitTextByUrls } from '../../../utils/url';
18
+
19
+ const parseMessageEx = (ex?: string): IMessageItemEx | undefined => {
20
+ if (!ex) return undefined;
21
+ try {
22
+ return JSON.parse(ex) as IMessageItemEx;
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ };
27
+
9
28
  export const TextMessage = memo(
10
29
  ({
11
30
  message,
12
31
  isOutgoing,
13
32
  isHighlighted,
33
+ onPressUrl,
14
34
  groupPosition,
15
- }: BaseLegendMessageProps) => {
35
+ }: BaseLegendMessageProps & { onPressUrl?: (url: string) => void }) => {
16
36
  const messageText = getMessageText(message);
17
37
 
38
+ const isAtTextMessage = message.contentType === MessageType.AtTextMessage;
39
+
40
+ // Both the reply-preview strip and the link-preview card below piggyback
41
+ // on an AtTextMessage instead of getting their own content type — OpenIM
42
+ // content types are mutually exclusive (AtText vs Quote vs UrlText), so
43
+ // once mention picks AtText (to keep native tag/notify semantics — see
44
+ // useSendMessage.ts), quote rides along via the native
45
+ // AtTextElem.quoteMessage field and the URL card rides along via our own
46
+ // ex.urlMetadata channel (already attached unconditionally on send).
47
+ const quotedMessage = isAtTextMessage
48
+ ? message.atTextElem?.quoteMessage
49
+ : undefined;
50
+
51
+ const parsedEx = useMemo(
52
+ () => (isAtTextMessage ? parseMessageEx(message.ex) : undefined),
53
+ [isAtTextMessage, message.ex]
54
+ );
55
+ const urlMetadata = parsedEx?.urlMetadata;
56
+
57
+ const mentions = useMemo(() => {
58
+ if (!isAtTextMessage || !message.atTextElem) return [];
59
+ // Wrapped under `offsets` — matches the wire shape other Droppii
60
+ // clients (e.g. the web/CRM app) already send (see
61
+ // IMessageItemEx.mentions doc comment for how this was confirmed).
62
+ return deserializeMentionRanges(
63
+ message.atTextElem,
64
+ parsedEx?.mentions?.offsets
65
+ );
66
+ }, [isAtTextMessage, message.atTextElem, parsedEx]);
67
+
68
+ const segments = useMemo(
69
+ () => (mentions.length > 0 ? tokenize(messageText, mentions) : null),
70
+ [mentions, messageText]
71
+ );
72
+
73
+ // Scoped to the mention+URL combo only (`segments` implies mentions are
74
+ // present) — a plain TextMessage with no mention falls through to
75
+ // `messageText` unchanged below, same as before this feature existed.
76
+ // Widening this to auto-linkify every TextMessage is a separate,
77
+ // deliberate product decision, not a side effect of this fix.
78
+ const urls = useMemo(
79
+ () => (segments ? extractUrls(messageText) : []),
80
+ [segments, messageText]
81
+ );
82
+
18
83
  if (!messageText?.trim()) {
19
84
  return null;
20
85
  }
21
86
 
87
+ // A plain-text run (the space between two mentions) may itself contain
88
+ // a URL — split further so it gets the same tappable/colored treatment
89
+ // LinkMessage gives a pure URL message, instead of sitting there as
90
+ // unstyled black text next to a colored mention.
91
+ const renderTextRun = (text: string, keyPrefix: string) => {
92
+ if (!urls.length) return <Text key={keyPrefix}>{text}</Text>;
93
+ return splitTextByUrls(text, urls).map((part, i) =>
94
+ part.isUrl ? (
95
+ <Text
96
+ key={`${keyPrefix}-url-${i}`}
97
+ style={urlTextStyle}
98
+ onPress={() => onPressUrl?.(part.text)}
99
+ >
100
+ {part.text}
101
+ </Text>
102
+ ) : (
103
+ <Text key={`${keyPrefix}-text-${i}`}>{part.text}</Text>
104
+ )
105
+ );
106
+ };
107
+
22
108
  return (
23
109
  <BubbleTailRow isOutgoing={isOutgoing} groupPosition={groupPosition}>
24
110
  <KContainer.View
25
111
  style={[
26
112
  messageStyles.bubble,
113
+ styles.bubbleContent,
27
114
  isOutgoing
28
115
  ? messageStyles.bubbleSent
29
116
  : messageStyles.bubbleReceived,
@@ -31,9 +118,25 @@ export const TextMessage = memo(
31
118
  getTailCornerStyle(isOutgoing, groupPosition),
32
119
  ]}
33
120
  >
121
+ {quotedMessage && (
122
+ <QuotedMessagePreviewStrip quotedMessage={quotedMessage} />
123
+ )}
34
124
  <KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
35
- {messageText}
125
+ {segments
126
+ ? segments.map((segment, index) =>
127
+ segment.type === 'mention' ? (
128
+ <Text key={segment.mention.id} style={mentionTextStyle}>
129
+ {`@${segment.mention.displayName}`}
130
+ </Text>
131
+ ) : (
132
+ renderTextRun(segment.text, `seg-${index}`)
133
+ )
134
+ )
135
+ : messageText}
36
136
  </KLabel.Text>
137
+ {!!urlMetadata && (
138
+ <LinkPreviewCard metadata={urlMetadata} onPressUrl={onPressUrl} />
139
+ )}
37
140
  </KContainer.View>
38
141
  </BubbleTailRow>
39
142
  );
@@ -41,3 +144,20 @@ export const TextMessage = memo(
41
144
  );
42
145
 
43
146
  TextMessage.displayName = 'TextMessage';
147
+
148
+ const styles = StyleSheet.create({
149
+ bubbleContent: { gap: 4 },
150
+ timeLabel: { gap: 4, marginTop: 2 },
151
+ });
152
+
153
+ // Same highlight color as the composer (MentionHighlightAdapter) and the
154
+ // Figma screenshots. This is a read-only <Text>, not an editable
155
+ // TextInput, so none of the nested-Text-in-TextInput bug class applies —
156
+ // nesting styled Text here is unconditionally safe.
157
+ const mentionTextStyle = { color: KColors.palette.primary.w400 };
158
+
159
+ // Same color LinkMessage uses for a URL substring — a mention message's
160
+ // plain-text run can contain a URL too (mention+URL combo, see
161
+ // useSendMessage.ts's doc comment), and it should look tappable/colored
162
+ // the same way there as it does in a pure UrlTextMessage.
163
+ const urlTextStyle = { color: KColors.primary.normal };
@@ -22,6 +22,7 @@ export interface ApiEndpoints {
22
22
  stickerHistory: string;
23
23
  messagesResources: string;
24
24
  messages: string;
25
+ contacts: string;
25
26
  }
26
27
 
27
28
  const DEFAULT_ENDPOINTS: ApiEndpoints = {
@@ -35,6 +36,7 @@ const DEFAULT_ENDPOINTS: ApiEndpoints = {
35
36
  stickerHistory: '/chat-service/v1/app/stickers/history',
36
37
  messagesResources: '/chat-service/v1/app/messages/resources',
37
38
  messages: '/chat-service/v1/app/messages',
39
+ contacts: '/chat-service/v1/app/contacts',
38
40
  };
39
41
 
40
42
  let endpoints: ApiEndpoints = { ...DEFAULT_ENDPOINTS };