tuikit-atomicx-vue3 4.5.0 → 4.5.2

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 (182) hide show
  1. package/dist/{MessageInput.vue_vue_type_script_setup_true_lang-jPzZ5INK.js → MessageInput.vue_vue_type_script_setup_true_lang-3RVYOdkv.js} +38 -38
  2. package/dist/{PopoverTrigger-L8abAry7.js → PopoverPortal-DV6zFXcf.js} +91 -136
  3. package/dist/PopoverTrigger-DIjW4PKa.js +54 -0
  4. package/dist/{PopperContent-XdhqL8Y2.js → PopperContent-D__dbwpA.js} +6 -6
  5. package/dist/{Teleport-CSEuZbpM.js → Teleport-98QrIYDI.js} +280 -270
  6. package/dist/baseComp/Modal/Modal.js +3 -3
  7. package/dist/components/AudioSettingPanel/index.js +14 -14
  8. package/dist/components/BarrageInput/EmojiPicker/EmojiPicker.js +10 -9
  9. package/dist/components/BarrageInput/TextEditor/CharacterCountExtension.js +1 -1
  10. package/dist/components/BarrageInput/TextEditor/EditorCore.js +16 -15
  11. package/dist/components/ConversationList/ConversationActions/ConversationActions.js +53 -50
  12. package/dist/components/ConversationList/ConversationActions/ConversationActions.vue.d.ts +2 -0
  13. package/dist/components/ConversationList/ConversationCreate/ConversationCreate.js +25 -25
  14. package/dist/components/ConversationList/ConversationList.vue.d.ts +16 -0
  15. package/dist/components/ConversationList/ConversationPreview/ConversationPreview.vue.d.ts +16 -0
  16. package/dist/components/ConversationList/ConversationPreview/ConversationPreviewAbstract.js +62 -40
  17. package/dist/components/ConversationList/ConversationPreview/ConversationPreviewTimestamp.js +28 -26
  18. package/dist/components/ConversationList/ConversationPreview/ConversationPreviewTitle.js +27 -23
  19. package/dist/components/ConversationList/ConversationPreview/ConversationPreviewTitle.vue.d.ts +1 -1
  20. package/dist/components/ConversationList/ConversationPreview/ConversationPreviewUI.js +81 -70
  21. package/dist/components/ConversationList/ConversationPreview/ConversationPreviewUI.vue.d.ts +4 -0
  22. package/dist/components/ConversationList/ConversationPreview/ConversationPreviewUnread.js +33 -31
  23. package/dist/components/ConversationList/ConversationPreview/utils.d.ts +2 -2
  24. package/dist/components/ConversationList/ConversationPreview/utils.js +81 -35
  25. package/dist/components/ConversationList/i18n/en-US.d.ts +11 -1
  26. package/dist/components/ConversationList/i18n/en-US.js +12 -2
  27. package/dist/components/ConversationList/i18n/zh-CN.d.ts +11 -1
  28. package/dist/components/ConversationList/i18n/zh-CN.js +12 -2
  29. package/dist/components/ConversationList/index.d.ts +48 -0
  30. package/dist/components/MessageInput/AttachmentPicker/index.js +10 -9
  31. package/dist/components/MessageInput/EmojiPicker/EmojiPicker.js +17 -16
  32. package/dist/components/MessageInput/MessageInput.js +1 -1
  33. package/dist/components/MessageInput/QuotedMessagePreview/index.js +35 -35
  34. package/dist/components/MessageInput/TextEditor/EditorCore.d.ts +13 -12
  35. package/dist/components/MessageInput/TextEditor/EditorCore.js +54 -89
  36. package/dist/components/MessageInput/TextEditor/extensions/MentionSuggestion.js +229 -0
  37. package/dist/components/MessageInput/TextEditor/extensions/MentionSuggestion.vue.d.ts +15 -0
  38. package/dist/components/MessageInput/TextEditor/extensions/characterCountExtension.js +1 -1
  39. package/dist/components/MessageInput/TextEditor/extensions/emojiExtension.d.ts +1 -0
  40. package/dist/components/MessageInput/TextEditor/extensions/emojiExtension.js +22 -0
  41. package/dist/components/MessageInput/TextEditor/extensions/enterKeyExtension.d.ts +3 -0
  42. package/dist/components/MessageInput/TextEditor/extensions/enterKeyExtension.js +15 -0
  43. package/dist/components/MessageInput/TextEditor/extensions/imageExtension.js +2 -2
  44. package/dist/components/MessageInput/TextEditor/extensions/index.d.ts +8 -0
  45. package/dist/components/MessageInput/TextEditor/extensions/index.js +12 -0
  46. package/dist/components/MessageInput/TextEditor/extensions/mentionExtension.d.ts +5 -0
  47. package/dist/components/MessageInput/TextEditor/extensions/mentionExtension.js +330 -0
  48. package/dist/components/MessageInput/TextEditor/index.js +62 -62
  49. package/dist/components/MessageInput/i18n/en-US.d.ts +3 -0
  50. package/dist/components/MessageInput/i18n/en-US.js +4 -1
  51. package/dist/components/MessageInput/i18n/index.d.ts +6 -0
  52. package/dist/components/MessageInput/i18n/zh-CN.d.ts +3 -0
  53. package/dist/components/MessageInput/i18n/zh-CN.js +4 -1
  54. package/dist/components/MessageInput/index.js +1 -1
  55. package/dist/components/MessageList/Message/ImageMessage/ImageMessage.js +119 -89
  56. package/dist/components/MessageList/Message/ImageMessage/ImagePreview.js +142 -0
  57. package/dist/components/MessageList/Message/ImageMessage/ImagePreview.vue.d.ts +20 -0
  58. package/dist/components/MessageList/Message/Message.vue.d.ts +8 -0
  59. package/dist/components/MessageList/Message/MessageLayout/MessageActionDropdown/MessageActionDropdown.js +3 -3
  60. package/dist/components/MessageList/Message/MessageLayout/MessageLayout.js +69 -58
  61. package/dist/components/MessageList/Message/MessageLayout/MessageLayout.vue.d.ts +8 -0
  62. package/dist/components/MessageList/Message/MessageLayout/MessageMeta/MessageMeta.js +37 -28
  63. package/dist/components/MessageList/Message/index.js +8 -4
  64. package/dist/components/MessageList/MessageList.js +109 -91
  65. package/dist/components/MessageList/MessageList.vue.d.ts +1 -1
  66. package/dist/components/MessageList/index.d.ts +21 -3
  67. package/dist/components/ScheduleRoomPanel/RoomDetail.js +22 -22
  68. package/dist/components/ScheduleRoomPanel/RoomEdit.js +1 -1
  69. package/dist/components/ScheduleRoomPanel/RoomShare.js +4 -4
  70. package/dist/components/ScheduleRoomPanel/ScheduleRoomPanel.js +1 -1
  71. package/dist/components/ScheduleRoomPanel/ScheduledRoomList.js +1 -1
  72. package/dist/components/Search/SearchResults/SearchResultsItem/Message/Message.js +6 -6
  73. package/dist/components/Search/SearchResults/SearchResultsItem/Message/Message.vue.d.ts +1 -1
  74. package/dist/components/UIKitModal/chatErrorModal/chatErrorModal.d.ts +72 -0
  75. package/dist/components/UIKitModal/chatErrorModal/chatErrorModal.js +95 -0
  76. package/dist/components/UIKitModal/chatErrorModal/i18n/en-US/index.d.ts +40 -0
  77. package/dist/components/UIKitModal/chatErrorModal/i18n/en-US/index.js +51 -0
  78. package/dist/components/UIKitModal/chatErrorModal/i18n/index.d.ts +4 -0
  79. package/dist/components/UIKitModal/chatErrorModal/i18n/index.js +6 -0
  80. package/dist/components/UIKitModal/chatErrorModal/i18n/zh-CN/index.d.ts +40 -0
  81. package/dist/components/UIKitModal/chatErrorModal/i18n/zh-CN/index.js +51 -0
  82. package/dist/components/UIKitModal/chatErrorModal/index.d.ts +3 -0
  83. package/dist/components/UIKitModal/chatErrorModal/index.js +11 -0
  84. package/dist/components/VideoSettingPanel/index.js +1 -1
  85. package/dist/hooks/useReadReceipt/useReadReceipt.js +44 -41
  86. package/dist/index-CTthrJb2.js +1461 -0
  87. package/dist/index-DXC5bPY4.js +2174 -0
  88. package/dist/{index-Do-2CngU.js → index-DuAffztD.js} +115 -142
  89. package/dist/{index-7vNB_Vx8.js → index-hHVD-MG2.js} +1 -1
  90. package/dist/index.d.ts +1 -1
  91. package/dist/index.js +150 -149
  92. package/dist/states/GroupSettingState/GroupSettingState.js +109 -102
  93. package/dist/states/LoginState.js +43 -43
  94. package/dist/states/MessageActionState/MessageActionState.js +83 -223
  95. package/dist/states/MessageInputState/MessageInputState.js +111 -82
  96. package/dist/states/MessageInputState/type.d.ts +36 -10
  97. package/dist/states/MessageInputState/utils.d.ts +1 -5
  98. package/dist/states/MessageListState/MessageListState.d.ts +1 -1
  99. package/dist/states/MessageListState/MessageListState.js +26 -23
  100. package/dist/states/RoomParticipantState/index.js +83 -24
  101. package/dist/states/RoomParticipantState/participantEventManager.d.ts +2 -2
  102. package/dist/states/RoomParticipantState/participantEventManager.js +217 -205
  103. package/dist/states/RoomParticipantState/participantManager.d.ts +4 -0
  104. package/dist/states/RoomParticipantState/participantManager.js +159 -127
  105. package/dist/states/RoomState/callManager.d.ts +3 -3
  106. package/dist/states/RoomState/callManager.js +20 -20
  107. package/dist/states/RoomState/common.d.ts +3 -2
  108. package/dist/states/RoomState/common.js +34 -24
  109. package/dist/states/RoomState/roomManager.d.ts +0 -1
  110. package/dist/states/RoomState/roomManager.js +21 -27
  111. package/dist/states/RoomState/scheduleManager.js +2 -2
  112. package/dist/styles/index.css +1 -1
  113. package/dist/{chat/index.d.ts → subEntry/chat/chat.d.ts} +2160 -2087
  114. package/dist/subEntry/chat/chat.js +89 -0
  115. package/dist/subEntry/chat/index.d.ts +11 -0
  116. package/dist/subEntry/chat/index.js +81 -0
  117. package/dist/{chat → subEntry/chat}/server.js +4 -4
  118. package/dist/subEntry/live/index.js +46 -45
  119. package/dist/subEntry/room/index.js +39 -38
  120. package/dist/types/beauty.d.ts +20 -0
  121. package/dist/types/index.js +37 -36
  122. package/dist/types/participant.d.ts +2 -0
  123. package/dist/types/room.d.ts +45 -1
  124. package/dist/types/room.js +4 -3
  125. package/dist/{useId-CtirfF0W.js → useId-B1VwPJLm.js} +1 -1
  126. package/dist/utils/call.js +77 -71
  127. package/dist/{utils-DaB7eSu5.js → utils-BU8IkP_V.js} +1 -1
  128. package/package.json +8 -7
  129. package/src/components/AudioSettingPanel/index.vue +4 -5
  130. package/src/components/ConversationList/ConversationActions/ConversationActions.vue +7 -1
  131. package/src/components/ConversationList/ConversationCreate/ConversationCreate.vue +5 -1
  132. package/src/components/ConversationList/ConversationList.vue +0 -1
  133. package/src/components/ConversationList/ConversationPreview/ConversationPreview.scss +10 -2
  134. package/src/components/ConversationList/ConversationPreview/ConversationPreviewAbstract.vue +32 -1
  135. package/src/components/ConversationList/ConversationPreview/ConversationPreviewTitle.vue +3 -2
  136. package/src/components/ConversationList/ConversationPreview/ConversationPreviewUI.vue +15 -0
  137. package/src/components/ConversationList/ConversationPreview/utils.ts +98 -28
  138. package/src/components/ConversationList/i18n/en-US.ts +11 -1
  139. package/src/components/ConversationList/i18n/zh-CN.ts +11 -1
  140. package/src/components/MessageInput/MessageInput.module.scss +1 -0
  141. package/src/components/MessageInput/QuotedMessagePreview/QuotedMessagePreview.vue +19 -22
  142. package/src/components/MessageInput/TextEditor/Editor.scss +25 -0
  143. package/src/components/MessageInput/TextEditor/EditorCore.ts +79 -99
  144. package/src/components/MessageInput/TextEditor/TextEditor.vue +64 -68
  145. package/src/components/MessageInput/TextEditor/extensions/MentionSuggestion.vue +449 -0
  146. package/src/components/MessageInput/TextEditor/extensions/emojiExtension.ts +22 -0
  147. package/src/components/MessageInput/TextEditor/extensions/enterKeyExtension.ts +22 -0
  148. package/src/components/MessageInput/TextEditor/extensions/index.ts +8 -0
  149. package/src/components/MessageInput/TextEditor/extensions/mentionExtension.ts +87 -0
  150. package/src/components/MessageInput/i18n/en-US.ts +3 -0
  151. package/src/components/MessageInput/i18n/zh-CN.ts +3 -0
  152. package/src/components/MessageList/Message/ImageMessage/ImageMessage.vue +49 -0
  153. package/src/components/MessageList/Message/ImageMessage/ImagePreview.vue +344 -0
  154. package/src/components/MessageList/Message/Message.vue +6 -0
  155. package/src/components/MessageList/Message/MessageLayout/MessageLayout.vue +8 -1
  156. package/src/components/MessageList/Message/MessageLayout/MessageMeta/MessageMeta.vue +12 -3
  157. package/src/components/MessageList/MessageList.vue +50 -14
  158. package/src/components/ScheduleRoomPanel/RoomDetail.vue +1 -0
  159. package/src/components/ScheduleRoomPanel/RoomEdit.vue +2 -1
  160. package/src/components/ScheduleRoomPanel/RoomShare.vue +1 -0
  161. package/src/components/ScheduleRoomPanel/ScheduleRoomPanel.vue +1 -0
  162. package/src/components/ScheduleRoomPanel/ScheduledRoomList.vue +1 -0
  163. package/src/components/Search/SearchResults/SearchResultsItem/Message/Message.vue +30 -31
  164. package/src/components/UIKitModal/chatErrorModal/chatErrorModal.ts +205 -0
  165. package/src/components/UIKitModal/chatErrorModal/i18n/en-US/index.ts +56 -0
  166. package/src/components/UIKitModal/chatErrorModal/i18n/index.ts +4 -0
  167. package/src/components/UIKitModal/chatErrorModal/i18n/zh-CN/index.ts +56 -0
  168. package/src/components/UIKitModal/chatErrorModal/index.ts +16 -0
  169. package/src/components/VideoSettingPanel/index.vue +1 -0
  170. package/src/hooks/useReadReceipt/useReadReceipt.ts +5 -4
  171. package/src/index.ts +1 -1
  172. package/src/{chat/index.ts → subEntry/chat/chat.ts} +25 -18
  173. package/src/subEntry/chat/index.ts +13 -0
  174. package/src/{chat → subEntry/chat}/server.ts +3 -3
  175. package/src/types/beauty.ts +20 -0
  176. package/src/types/participant.ts +3 -0
  177. package/src/types/room.ts +49 -1
  178. package/src/utils/call.ts +8 -0
  179. package/dist/chat/index.js +0 -59
  180. package/dist/index-ZILx4LYk.js +0 -4826
  181. package/dist/states/SearchState.d.ts +0 -314
  182. /package/dist/{chat → subEntry/chat}/server.d.ts +0 -0
@@ -1,11 +1,12 @@
1
1
  <template>
2
2
  <div :class="$style.conversationPreview__abstract">
3
3
  <template v-if="draftTextAbstract">
4
- <label :class="$style.conversationPreview__drafts">[{{ t('TUIConversation.Drafts') }}]</label>
4
+ <label :class="$style.conversationPreview__abstract__drafts">[{{ t('TUIConversation.Drafts') }}]</label>
5
5
  {{ ' ' }}
6
6
  {{ draftTextAbstract }}
7
7
  </template>
8
8
  <template v-else>
9
+ <label v-if="atInfoPreview" :class="$style['conversationPreview__abstract__at-info']">{{ atInfoPreview }}</label>
9
10
  {{ latestMessagePreview }}
10
11
  </template>
11
12
  </div>
@@ -14,6 +15,7 @@
14
15
  <script lang="ts" setup>
15
16
  import { computed } from 'vue';
16
17
  import { useUIKit } from '@tencentcloud/uikit-base-component-vue3';
18
+ import { ConversationType } from '../../../types';
17
19
  import { JSONStringToParse } from '../../../utils';
18
20
  import { getLatestMessagePreview } from './utils';
19
21
  import type { ConversationModel } from '../../../types';
@@ -30,6 +32,35 @@ const draftTextAbstract = computed(() => {
30
32
  });
31
33
 
32
34
  const latestMessagePreview = computed(() => getLatestMessagePreview(props.conversation, t));
35
+
36
+ /**
37
+ * Get group @ info preview text.
38
+ * atTypeArray[0] values: 1 = someone @me, 2 = @all, 3 = @all + someone @me
39
+ */
40
+ const atInfoPreview = computed(() => {
41
+ const { type, groupAtInfoList } = props?.conversation || {};
42
+
43
+ // Only show @ info for group conversations with valid groupAtInfoList
44
+ if (type !== ConversationType.GROUP || !groupAtInfoList?.length) {
45
+ return '';
46
+ }
47
+
48
+ const atInfoTextList: string[] = [
49
+ `[${t('TUIConversation.someone_at_me')}]`,
50
+ `[${t('TUIConversation.at_all')}]`,
51
+ `[${t('TUIConversation.at_all')}][${t('TUIConversation.someone_at_me')}]`,
52
+ ];
53
+
54
+ let atInfo = '';
55
+ groupAtInfoList.forEach((item) => {
56
+ const atType = item?.atTypeArray?.[0];
57
+ if (atType && atType >= 1 && atType <= 3) {
58
+ atInfo = atInfoTextList[atType - 1];
59
+ }
60
+ });
61
+
62
+ return atInfo;
63
+ });
33
64
  </script>
34
65
 
35
66
  <style lang="scss" module>
@@ -5,13 +5,14 @@
5
5
  </template>
6
6
 
7
7
  <script lang="ts" setup>
8
- import type { ConversationModel } from '../../../types';
8
+ import { computed } from 'vue';
9
+ import type { ConversationModel } from '../../../types/engine';
9
10
 
10
11
  const props = defineProps<{
11
12
  conversation: ConversationModel;
12
13
  }>();
13
14
 
14
- const title =props?.conversation?.getShowName?.() || '';
15
+ const title = computed(() => props?.conversation?.getShowName?.() || '');
15
16
  </script>
16
17
 
17
18
  <style lang="scss" module>
@@ -47,6 +47,7 @@
47
47
  :conversation="conversation"
48
48
  v-bind="actionsConfig"
49
49
  @close="handleCloseActionsModal"
50
+ @dropdown-visible-change="handleDropdownVisibleChange"
50
51
  />
51
52
  <component
52
53
  :is="LastMessageTimestamp"
@@ -103,6 +104,7 @@ const { activeConversation, setActiveConversation } = useConversationListState()
103
104
 
104
105
  const conversationPreviewRef = ref<HTMLElement>();
105
106
  const isActionMenuActive = ref(false);
107
+ const isDropdownOpen = ref(false);
106
108
 
107
109
  const { isHovered } = useMouseHover(conversationPreviewRef);
108
110
  const { getEventHandlers } = useLongPress(() => {
@@ -115,6 +117,10 @@ const longPressEvents = getEventHandlers();
115
117
 
116
118
  watch(isHovered, (newValue) => {
117
119
  if (!isH5) {
120
+ // Don't hide when dropdown is open
121
+ if (!newValue && isDropdownOpen.value) {
122
+ return;
123
+ }
118
124
  isActionMenuActive.value = newValue;
119
125
  }
120
126
  });
@@ -127,6 +133,15 @@ const handleClick = () => {
127
133
  const handleCloseActionsModal = () => {
128
134
  isActionMenuActive.value = false;
129
135
  };
136
+
137
+ const handleDropdownVisibleChange = (visible: boolean) => {
138
+ console.log('[ConversationPreview] dropdown visible changed:', visible);
139
+ isDropdownOpen.value = visible;
140
+ // When dropdown closes and mouse is not hovering, hide actions
141
+ if (!visible && !isHovered.value) {
142
+ isActionMenuActive.value = false;
143
+ }
144
+ };
130
145
  </script>
131
146
 
132
147
  <style lang="scss" module>
@@ -1,5 +1,9 @@
1
1
  import TUIChatEngine from '@tencentcloud/chat-uikit-engine';
2
- import type { ConversationModel } from '../../../types';
2
+ import { MessageType, ConversationType } from '../../../types/engine';
3
+ import { transformTextWithEmojiKeyToName, safeJSONParse } from '../../../utils';
4
+ import { parseCallMessageText } from '../../../utils/call';
5
+ import { resolveGroupTipMessage } from '../../MessageList/Message/GroupTipMessage/resolveGroupTipMessage';
6
+ import type { ConversationModel, MessageModel } from '../../../types/engine';
3
7
 
4
8
  export const generateHighlightTitle = (
5
9
  conversation: ConversationModel,
@@ -20,36 +24,102 @@ export const generateHighlightTitle = (
20
24
  };
21
25
 
22
26
  export const getLatestMessagePreview = (conversation: ConversationModel, t: (key: string) => string) => {
23
- const { lastMessage } = conversation || {};
27
+ const { draftText } = conversation || {};
28
+
29
+ // Handle draft message
30
+ if (draftText) {
31
+ if (typeof draftText === 'string') {
32
+ return draftText;
33
+ }
34
+ const draftInfo = safeJSONParse(draftText, { abstract: '' });
35
+ return draftInfo.abstract;
36
+ }
37
+
38
+ // Handle special operation type messages
39
+ const OPERATION_MESSAGES: Record<number, string> = {
40
+ 4: t('TUIConversation.you_have_been_removed_from_the_group'),
41
+ 5: t('TUIConversation.the_group_chat_has_been_disbanded'),
42
+ 8: t('TUIConversation.you_have_left_the_group_chat'),
43
+ };
44
+ if (conversation.operationType && OPERATION_MESSAGES[conversation.operationType]) {
45
+ return OPERATION_MESSAGES[conversation.operationType];
46
+ }
47
+
48
+ const { lastMessage } = conversation;
49
+
24
50
  if (!lastMessage) {
25
51
  return '';
26
52
  }
27
53
 
28
- const { type, payload } = lastMessage;
29
- const { nick, userID } = lastMessage.nick || lastMessage?.from || {};
30
-
31
- const senderName = nick || userID || '';
32
-
33
- switch (type) {
34
- case TUIChatEngine.TYPES.MSG_TEXT:
35
- return payload.text || '';
36
- case TUIChatEngine.TYPES.MSG_IMAGE:
37
- return `[${t('TUIConversation.Image')}]`;
38
- case TUIChatEngine.TYPES.MSG_AUDIO:
39
- return `[${t('TUIConversation.Audio')}]`;
40
- case TUIChatEngine.TYPES.MSG_VIDEO:
41
- return `[${t('TUIConversation.Video')}]`;
42
- case TUIChatEngine.TYPES.MSG_FILE:
43
- return `[${t('TUIConversation.File')}]`;
44
- case TUIChatEngine.TYPES.MSG_CUSTOM:
45
- return `[${t('TUIConversation.Custom')}]`;
46
- case TUIChatEngine.TYPES.MSG_LOCATION:
47
- return `[${t('TUIConversation.Location')}]`;
48
- case TUIChatEngine.TYPES.MSG_FACE:
49
- return `[${t('TUIConversation.Emoji')}]`;
50
- case TUIChatEngine.TYPES.MSG_MERGER:
51
- return `[${t('TUIConversation.Chat History')}]`;
52
- default:
53
- return senderName ? `${senderName}: ${payload?.text || ''}` : payload?.text || '';
54
+ const isGroupConversation = conversation.type === ConversationType.GROUP;
55
+ const typedLastMessage = lastMessage as unknown as MessageModel;
56
+ const { type, payload } = typedLastMessage;
57
+
58
+ let messageContent = '';
59
+
60
+ if (lastMessage?.isRevoked) {
61
+ messageContent = t('TUIConversation.recalled_a_message');
62
+ } else {
63
+ switch (type) {
64
+ case MessageType.TEXT:
65
+ messageContent = transformTextWithEmojiKeyToName(payload.text || '');
66
+ break;
67
+ case MessageType.IMAGE:
68
+ messageContent = `[${t('TUIConversation.Image')}]`;
69
+ break;
70
+ case MessageType.AUDIO:
71
+ messageContent = `[${t('TUIConversation.Audio')}]`;
72
+ break;
73
+ case MessageType.VIDEO:
74
+ messageContent = `[${t('TUIConversation.Video')}]`;
75
+ break;
76
+ case MessageType.FILE:
77
+ messageContent = `[${t('TUIConversation.File')}]`;
78
+ break;
79
+ case MessageType.CUSTOM: {
80
+ const data = safeJSONParse(payload?.data, { businessID: undefined });
81
+ // Handle CallKit signaling message
82
+ if (data?.businessID === 1) {
83
+ try {
84
+ messageContent = parseCallMessageText(typedLastMessage, t);
85
+ } catch {
86
+ messageContent = `[${t('TUIConversation.call_message')}]`;
87
+ }
88
+ } else {
89
+ messageContent = `[${t('TUIConversation.Custom')}]`;
90
+ }
91
+ break;
92
+ }
93
+ case MessageType.LOCATION:
94
+ messageContent = `[${t('TUIConversation.Location')}]`;
95
+ break;
96
+ case MessageType.FACE:
97
+ messageContent = `[${t('TUIConversation.Face')}]`;
98
+ break;
99
+ case MessageType.MERGER:
100
+ messageContent = `[${t('TUIConversation.Chat History')}]`;
101
+ break;
102
+ case MessageType.GRP_TIP:
103
+ return resolveGroupTipMessage(typedLastMessage)?.text;
104
+ default:
105
+ messageContent = `[${t('TUIConversation.unknown_message')}]`;
106
+ break;
107
+ }
54
108
  }
109
+
110
+ if (isGroupConversation) {
111
+ let senderName = '';
112
+ if (lastMessage?.fromAccount === TUIChatEngine.getMyUserID()) {
113
+ senderName = t('TUIConversation.me');
114
+ } else {
115
+ // Priority: friendRemark > nameCard > nick > userID
116
+ senderName = conversation.remark
117
+ || typedLastMessage?.nameCard
118
+ || lastMessage?.nick
119
+ || lastMessage.fromAccount;
120
+ }
121
+ return senderName ? `${senderName}: ${messageContent}` : messageContent;
122
+ }
123
+
124
+ return messageContent;
55
125
  };
@@ -64,7 +64,17 @@ export default {
64
64
  'File': 'File',
65
65
  'Custom': 'Custom',
66
66
  'Location': 'Location',
67
- 'Emoji': 'Emoji',
67
+ 'Face': 'Face',
68
68
  'Chat History': 'Chat History',
69
+ 'call_message': 'Call Message',
70
+ 'unknown_message': 'Unknown Message',
71
+ 'me': 'Me',
72
+ 'recalled_a_message': 'recalled a message',
73
+ 'you_have_been_removed_from_the_group': 'You have been removed from the group by the group administrator',
74
+ 'the_group_chat_has_been_disbanded': 'The group chat has been disbanded',
75
+ 'you_have_left_the_group_chat': 'You have left the group chat',
76
+ 'someone_at_me': 'You were mentioned',
77
+ 'at_all': '@All',
78
+ 'group_id_already_used': 'The group ID for creating the group chat is already in use, please choose a different group ID.',
69
79
  },
70
80
  };
@@ -64,7 +64,17 @@ export default {
64
64
  'File': '文件',
65
65
  'Custom': '自定义消息',
66
66
  'Location': '位置',
67
- 'Emoji': '表情',
67
+ 'Face': '动画表情',
68
68
  'Chat History': '聊天记录',
69
+ 'call_message': '通话消息',
70
+ 'unknown_message': '消息',
71
+ 'me': '我',
72
+ 'recalled_a_message': '撤回了一条消息',
73
+ 'you_have_been_removed_from_the_group': '你已被群管理员移出该群',
74
+ 'the_group_chat_has_been_disbanded': '该群聊已解散',
75
+ 'you_have_left_the_group_chat': '你已退出该群聊',
76
+ 'someone_at_me': '有人@我',
77
+ 'at_all': '@所有人',
78
+ 'group_id_already_used': '当前创建群聊的群ID已被使用,请选择其他群ID',
69
79
  },
70
80
  };
@@ -7,6 +7,7 @@
7
7
  flex-shrink: 0;
8
8
  padding: 10px;
9
9
  gap: 4px;
10
+ text-align: start;
10
11
 
11
12
  &__toolbar {
12
13
  display: flex;
@@ -16,60 +16,57 @@
16
16
  <IconClose
17
17
  :class="styles['quoted__message__preview__close']"
18
18
  size="16"
19
- @click="handleClose"
19
+ @click="handleCloseQuotedMessage"
20
20
  />
21
21
  </div>
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
- import { watch } from 'vue';
26
- import TencentCloudChat from '@tencentcloud/chat';
25
+ import { watch, onUnmounted } from 'vue';
27
26
  import { useUIKit, IconClose } from '@tencentcloud/uikit-base-component-vue3';
28
- import { useConversationListState } from '../../../states/ConversationListState';
29
27
  import { useMessageActionState } from '../../../states/MessageActionState';
30
28
  import { useMessageInputState } from '../../../states/MessageInputState';
29
+ import { MessageType } from '../../../types/engine';
30
+ import { transformTextWithEmojiKeyToName } from '../../../utils';
31
31
  import styles from './QuotedMessagePreview.module.scss';
32
- import type { IMessageModel } from '@tencentcloud/chat-uikit-engine';
32
+ import type { MessageModel } from '../../../types/engine';
33
33
 
34
34
  const { t } = useUIKit();
35
- const { activeConversation } = useConversationListState();
36
35
  const { focusEditor } = useMessageInputState();
37
36
  const { quotedMessage, clearQuotedMessage } = useMessageActionState();
38
37
 
39
- // Watch referencedMessage changes
38
+ onUnmounted(() => {
39
+ clearQuotedMessage();
40
+ });
41
+
40
42
  watch(quotedMessage, (newVal) => {
41
43
  if (newVal) {
42
44
  focusEditor();
43
45
  }
44
46
  });
45
47
 
46
- // Watch conversation changes
47
- watch(activeConversation, () => {
48
- clearQuotedMessage();
49
- });
50
-
51
- const handleClose = () => {
48
+ const handleCloseQuotedMessage = () => {
52
49
  clearQuotedMessage();
53
50
  };
54
51
 
55
- const calculateReferenceContent = (message: IMessageModel | undefined): string => {
52
+ const calculateReferenceContent = (message: MessageModel | undefined): string => {
56
53
  if (!message) {
57
54
  return 'no reference';
58
55
  }
59
56
  switch (message.type) {
60
- case TencentCloudChat.TYPES.MSG_TEXT:
61
- return message.payload?.text;
62
- case TencentCloudChat.TYPES.MSG_IMAGE:
57
+ case MessageType.TEXT:
58
+ return transformTextWithEmojiKeyToName(message.payload?.text || '');
59
+ case MessageType.IMAGE:
63
60
  return t('MessageInput.image');
64
- case TencentCloudChat.TYPES.MSG_AUDIO:
61
+ case MessageType.AUDIO:
65
62
  return t('MessageInput.audio');
66
- case TencentCloudChat.TYPES.MSG_VIDEO:
63
+ case MessageType.VIDEO:
67
64
  return t('MessageInput.video');
68
- case TencentCloudChat.TYPES.MSG_FILE:
65
+ case MessageType.FILE:
69
66
  return t('MessageInput.file');
70
- case TencentCloudChat.TYPES.MSG_LOCATION:
67
+ case MessageType.LOCATION:
71
68
  return t('MessageInput.location');
72
- case TencentCloudChat.TYPES.MSG_CUSTOM:
69
+ case MessageType.CUSTOM:
73
70
  return t('MessageInput.custom_message');
74
71
  default:
75
72
  return t('MessageInput.unknown');
@@ -39,7 +39,9 @@
39
39
  height: 20px;
40
40
  user-select: text;
41
41
  pointer-events: none;
42
+ vertical-align: bottom;
42
43
  }
44
+
43
45
  .message-image {
44
46
  display: inline-block;
45
47
  width: 100px;
@@ -57,3 +59,26 @@
57
59
  pointer-events: none;
58
60
  }
59
61
 
62
+ // Mention tag styles (Telegram-style)
63
+ .uikit-message-input-mention-tag {
64
+ display: inline;
65
+ padding: 2px 4px;
66
+ margin: 0 2px;
67
+ border-radius: 4px;
68
+ background-color: rgba(0, 122, 255, 0.1);
69
+ color: #007AFF;
70
+ font-weight: 500;
71
+ cursor: pointer;
72
+ user-select: none;
73
+ white-space: nowrap;
74
+
75
+ &:hover {
76
+ background-color: rgba(0, 122, 255, 0.15);
77
+ }
78
+
79
+ // Selected state (when backspacing)
80
+ &.ProseMirror-selectednode {
81
+ background-color: rgba(0, 122, 255, 0.2);
82
+ }
83
+ }
84
+
@@ -1,48 +1,72 @@
1
- import Image from '@tiptap/extension-image';
1
+ /**
2
+ * EditorCore - Pure functional utilities for Tiptap editor
3
+ * Provides extension configuration and content conversion
4
+ */
2
5
  import Placeholder from '@tiptap/extension-placeholder';
3
6
  import StarterKit from '@tiptap/starter-kit';
4
- import { Extension, Editor } from '@tiptap/vue-3';
5
7
  import { MessageContentType } from '../../../states/MessageInputState';
6
- import { CharacterCount } from './extensions/characterCountExtension';
7
- import { createImageExtension } from './extensions/imageExtension';
8
+ import {
9
+ CharacterCount,
10
+ createEmojiExtension,
11
+ createEnterKeyExtension,
12
+ createImageExtension,
13
+ createMentionExtension,
14
+ } from './extensions';
8
15
  import type { InputContent } from '../../../states/MessageInputState';
9
- import type { JSONContent, EditorOptions as TiptapEditorOptions } from '@tiptap/vue-3';
16
+ import type { JSONContent, Extensions } from '@tiptap/vue-3';
10
17
  import './Editor.scss';
11
18
 
12
- function createEmojiExtension() {
13
- return Image.extend({
14
- name: MessageContentType.EMOJI,
15
- inline: true,
16
- group: 'inline',
17
- draggable: true,
18
- addOptions() {
19
- return {
20
- ...this.parent?.(),
21
- HTMLAttributes: {
22
- class: 'message-emoji',
23
- },
24
- };
25
- },
26
- });
19
+ // ============================================================================
20
+ // Extension Configuration
21
+ // ============================================================================
22
+
23
+ interface ExtensionOptions {
24
+ placeholder?: string;
25
+ maxLength?: number;
26
+ showPlaceholderOnlyWhenEditable?: boolean;
27
+ onEnter?: () => void;
27
28
  }
28
29
 
29
- function createEnterKeyExtension(options?: { onEnter?: (() => void) | undefined }) {
30
- return Extension.create({
31
- addKeyboardShortcuts() {
32
- return {
33
- 'Enter': () => {
34
- options?.onEnter?.();
35
- return true;
36
- },
37
- 'Mod-Enter': ({ editor }) => {
38
- editor.commands.setHardBreak();
39
- return true;
40
- },
41
- };
42
- },
43
- });
30
+ /**
31
+ * Create all editor extensions with given options
32
+ */
33
+ function createExtensions(options: ExtensionOptions = {}): Extensions {
34
+ const {
35
+ placeholder = '',
36
+ maxLength,
37
+ showPlaceholderOnlyWhenEditable = true,
38
+ onEnter,
39
+ } = options;
40
+
41
+ return [
42
+ StarterKit.configure({
43
+ bold: false,
44
+ italic: false,
45
+ strike: false,
46
+ code: false,
47
+ }),
48
+ CharacterCount.configure({
49
+ limit: maxLength,
50
+ }),
51
+ createEnterKeyExtension(onEnter),
52
+ createEmojiExtension(),
53
+ createImageExtension(),
54
+ createMentionExtension(),
55
+ Placeholder.configure({
56
+ placeholder,
57
+ showOnlyWhenEditable: showPlaceholderOnlyWhenEditable,
58
+ }),
59
+ ];
44
60
  }
45
61
 
62
+ // ============================================================================
63
+ // Content Conversion
64
+ // ============================================================================
65
+
66
+ /**
67
+ * Convert Tiptap JSON content to business InputContent array
68
+ * Uses simple switch-case for clarity (no over-engineered registry pattern)
69
+ */
46
70
  function convertEditorContent(node: JSONContent): InputContent[] {
47
71
  if (!node?.content) {
48
72
  return [];
@@ -57,11 +81,13 @@ function convertEditorContent(node: JSONContent): InputContent[] {
57
81
  content: child.text,
58
82
  }]
59
83
  : [];
84
+
60
85
  case 'image':
61
86
  return [{
62
87
  type: MessageContentType.IMAGE,
63
88
  content: child.attrs?.fileData,
64
89
  }];
90
+
65
91
  case 'emoji':
66
92
  return [{
67
93
  type: MessageContentType.EMOJI,
@@ -71,81 +97,35 @@ function convertEditorContent(node: JSONContent): InputContent[] {
71
97
  text: child.attrs?.title,
72
98
  },
73
99
  }];
100
+
74
101
  case 'hardBreak':
75
102
  return [{
76
103
  type: MessageContentType.TEXT,
77
104
  content: '\n',
78
105
  }];
106
+
107
+ case 'mention':
108
+ return [{
109
+ type: MessageContentType.MENTION,
110
+ content: {
111
+ id: child.attrs?.id,
112
+ label: child.attrs?.label,
113
+ mentionSuggestionChar: child.attrs?.mentionSuggestionChar,
114
+ },
115
+ }];
116
+
79
117
  default:
118
+ // Recursively handle nested content (e.g., paragraph nodes)
80
119
  return convertEditorContent(child);
81
120
  }
82
121
  });
83
122
  }
84
123
 
85
- interface EditorOptions {
86
- element: Element;
87
- placeholder?: string;
88
- autoFocus?: boolean;
89
- disabled?: boolean;
90
- maxLength?: number;
91
- isPlaceholderOnlyShowWhenEditable?: boolean;
92
- onUpdate?: (content: InputContent[]) => void;
93
- onEnter?: () => void;
94
- onFocus?: () => void;
95
- onBlur?: () => void;
96
- }
97
-
98
- function createEditor({
99
- element,
100
- placeholder = '',
101
- autoFocus = false,
102
- disabled = false,
103
- isPlaceholderOnlyShowWhenEditable = true,
104
- maxLength = undefined,
105
- onUpdate,
106
- onEnter,
107
- onFocus,
108
- onBlur,
109
- }: EditorOptions) {
110
- const createBaseExtensions = (enterHandler?: () => void) => [
111
- StarterKit.configure({
112
- bold: false,
113
- italic: false,
114
- strike: false,
115
- code: false,
116
- }),
117
- CharacterCount.configure({
118
- limit: maxLength,
119
- }),
120
- createEnterKeyExtension(enterHandler ? { onEnter: enterHandler } : undefined),
121
- createEmojiExtension(),
122
- createImageExtension(),
123
- Placeholder.configure({
124
- placeholder,
125
- showOnlyWhenEditable: isPlaceholderOnlyShowWhenEditable,
126
- }),
127
- ];
128
-
129
- const editorConfig: Partial<TiptapEditorOptions> = {
130
- element,
131
- autofocus: autoFocus,
132
- editable: !disabled,
133
- extensions: createBaseExtensions(onEnter),
134
- onUpdate: ({ editor }) => {
135
- const content = convertEditorContent(editor.getJSON());
136
- onUpdate?.(content);
137
- },
138
- onFocus,
139
- onBlur,
140
- };
141
-
142
- return new Editor(editorConfig);
143
- }
124
+ // ============================================================================
125
+ // Exports
126
+ // ============================================================================
144
127
 
145
128
  export {
146
- createEditor,
147
- };
148
-
149
- export type {
150
- Editor,
129
+ createExtensions,
130
+ convertEditorContent,
151
131
  };