@webitel/ui-chats 0.1.49 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/declaration.d.ts +7 -0
- package/src/ui/chat-footer/modules/user-input/components/chat-input-actions-bar.vue +2 -2
- package/src/ui/chat-footer/modules/user-input/components/chat-text-field.vue +4 -4
- package/src/ui/chat-footer/modules/user-input/enums/ChatAction.enum.ts +5 -0
- package/src/ui/messaging/composables/useChatScroll.ts +34 -3
- package/src/ui/messaging/modules/message/components/chat-message.vue +1 -1
- package/src/ui/messaging/modules/message/components/details/chat-message-player.vue +5 -4
- package/src/ui/the-chat-container.vue +2 -2
- package/types/.tsbuildinfo +1 -1
- package/types/ui/chat-footer/modules/user-input/components/actions/attach-files-action.vue.d.ts +1 -1
- package/types/ui/chat-footer/modules/user-input/components/actions/send-message-action.vue.d.ts +1 -1
- package/types/ui/chat-footer/modules/user-input/components/chat-input-actions-bar.vue.d.ts +4 -4
- package/types/ui/chat-footer/modules/user-input/components/chat-text-field.vue.d.ts +4 -5
- package/types/ui/chat-footer/modules/user-input/enums/ChatAction.enum.d.ts +4 -0
- package/types/ui/image-viewer/image-viewer.vue.d.ts +1 -1
- package/types/ui/messaging/components/chat-observer.vue.d.ts +2 -2
- package/types/ui/messaging/components/scroll-to-bottom-btn.vue.d.ts +2 -2
- package/types/ui/messaging/components/the-messages-container.vue.d.ts +1 -1
- package/types/ui/messaging/composables/useChatScroll.d.ts +1 -0
- package/types/ui/messaging/modules/message/components/chat-message.vue.d.ts +2 -2
- package/types/ui/messaging/modules/message/components/details/chat-message-image.vue.d.ts +1 -1
- package/types/ui/messaging/modules/message/components/details/chat-message-player.vue.d.ts +2 -2
- package/types/ui/messaging/modules/message/components/details/chat-message-size-exceeded-error.vue.d.ts +1 -1
- package/types/ui/messaging/modules/message/components/details/chat-message-time.vue.d.ts +1 -1
- package/types/ui/messaging/modules/message/composables/useChatMessageFile.d.ts +19 -19
- package/types/ui/the-chat-container.vue.d.ts +3 -3
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { ComponentSize } from '@webitel/ui-sdk/enums';
|
|
21
21
|
import { computed, inject } from 'vue';
|
|
22
22
|
|
|
23
|
-
import { ChatAction, type
|
|
23
|
+
import { ChatAction, type ChatActionSlots } from '../enums/ChatAction.enum';
|
|
24
24
|
import AttachFilesAction from './actions/attach-files-action.vue';
|
|
25
25
|
import EmojiPickerAction from './actions/emoji-picker-action.vue';
|
|
26
26
|
import SendMessageAction from './actions/send-message-action.vue';
|
|
@@ -36,7 +36,7 @@ const emit = defineEmits<{
|
|
|
36
36
|
(e: typeof ChatAction.AttachFiles, files: File[]): void;
|
|
37
37
|
}>();
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
defineSlots<ChatActionSlots>();
|
|
40
40
|
|
|
41
41
|
const ShownActionComponentsList = computed(() => {
|
|
42
42
|
/**
|
|
@@ -16,14 +16,14 @@ import { WtTextarea } from '@webitel/ui-sdk/components';
|
|
|
16
16
|
import { ComponentSize } from '@webitel/ui-sdk/enums';
|
|
17
17
|
import insertTextAtCursor from 'insert-text-at-cursor';
|
|
18
18
|
import type { Emitter } from 'mitt';
|
|
19
|
-
import { computed, inject,
|
|
19
|
+
import { computed, inject, useTemplateRef } from 'vue';
|
|
20
20
|
import { useI18n } from 'vue-i18n';
|
|
21
21
|
|
|
22
22
|
import type { UiChatsEmitterEvents } from '../../../../utils/emitter';
|
|
23
23
|
|
|
24
24
|
const { t } = useI18n();
|
|
25
25
|
|
|
26
|
-
const textModel = defineModel<
|
|
26
|
+
const textModel = defineModel<string>('text', {
|
|
27
27
|
required: true,
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -46,8 +46,8 @@ const textareaEl = computed(() =>
|
|
|
46
46
|
chatTextFieldInputRef.value?.$el.querySelector('textarea'),
|
|
47
47
|
);
|
|
48
48
|
|
|
49
|
-
function send(text
|
|
50
|
-
textModel.value = text;
|
|
49
|
+
function send(text?: string) {
|
|
50
|
+
textModel.value = text ?? '';
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function focus() {
|
|
@@ -11,3 +11,8 @@ export type ChatAction = (typeof ChatAction)[keyof typeof ChatAction];
|
|
|
11
11
|
export type SharedActionSlots = {
|
|
12
12
|
[key in `action:${ChatAction}`]?: () => unknown;
|
|
13
13
|
};
|
|
14
|
+
|
|
15
|
+
/** slots `chat-input-actions-bar` itself exposes, keyed by the bare action name */
|
|
16
|
+
export type ChatActionSlots = {
|
|
17
|
+
[key in ChatAction]?: (props: Record<string, unknown>) => unknown;
|
|
18
|
+
};
|
|
@@ -56,6 +56,7 @@ export const useChatScroll = ({
|
|
|
56
56
|
let isLoadingNextMessages = false;
|
|
57
57
|
let lastVisibleMessageEl: HTMLElement | null = null;
|
|
58
58
|
let prevScrollHeight = 0;
|
|
59
|
+
let prevScrollTop = 0;
|
|
59
60
|
let resizeObserver: ResizeObserver | null = null;
|
|
60
61
|
let isViewportTransition = false;
|
|
61
62
|
let viewportTransitionTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -72,6 +73,15 @@ export const useChatScroll = ({
|
|
|
72
73
|
const lastMessage = computed(() => messages.value?.at(-1));
|
|
73
74
|
const isLastMessageMy = computed(() => !!lastMessage.value?.member?.self);
|
|
74
75
|
|
|
76
|
+
const isViewingBottom = (el: HTMLElement) =>
|
|
77
|
+
el.clientHeight > 0 &&
|
|
78
|
+
el.scrollHeight - (el.scrollTop + el.clientHeight) <= bottomThreshold;
|
|
79
|
+
|
|
80
|
+
const markSeenIfAtBottom = () => {
|
|
81
|
+
const el = chatContainer.value;
|
|
82
|
+
if (el && isViewingBottom(el)) onSeen?.();
|
|
83
|
+
};
|
|
84
|
+
|
|
75
85
|
const handleBtnAfterNewMessage = () => {
|
|
76
86
|
if (isLastMessageMy.value) {
|
|
77
87
|
scrollToBottom('instant');
|
|
@@ -89,8 +99,14 @@ export const useChatScroll = ({
|
|
|
89
99
|
newUnseenMessagesCount.value = 0;
|
|
90
100
|
|
|
91
101
|
resetScrollToBottomBtn();
|
|
92
|
-
|
|
102
|
+
markSeenIfAtBottom();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleChatScrollWithSeen = () => {
|
|
106
|
+
handleChatScroll();
|
|
107
|
+
markSeenIfAtBottom();
|
|
93
108
|
};
|
|
109
|
+
|
|
94
110
|
const getTopMessageEl = () => {
|
|
95
111
|
// help to fix chat viewing position when new messages was loaded
|
|
96
112
|
if (!chatContainer.value?.children) return;
|
|
@@ -116,6 +132,7 @@ export const useChatScroll = ({
|
|
|
116
132
|
if (!chatContent.value) return;
|
|
117
133
|
|
|
118
134
|
prevScrollHeight = chatContainer.value?.scrollHeight ?? 0;
|
|
135
|
+
prevScrollTop = chatContainer.value?.scrollTop ?? 0;
|
|
119
136
|
|
|
120
137
|
resizeObserver = new ResizeObserver(() => {
|
|
121
138
|
const el = chatContainer.value;
|
|
@@ -125,11 +142,23 @@ export const useChatScroll = ({
|
|
|
125
142
|
|
|
126
143
|
if (isViewportTransition) {
|
|
127
144
|
prevScrollHeight = newScrollHeight;
|
|
145
|
+
prevScrollTop = el.scrollTop;
|
|
128
146
|
updateScrollToBottomBtnVisibility(el);
|
|
129
147
|
return;
|
|
130
148
|
}
|
|
131
149
|
|
|
150
|
+
const heightDelta = newScrollHeight - prevScrollHeight;
|
|
151
|
+
const scrollTopDelta = el.scrollTop - prevScrollTop;
|
|
152
|
+
/**
|
|
153
|
+
* @author PolinaSukhorukova-webitel
|
|
154
|
+
*
|
|
155
|
+
* Prepend after pagination shifts scrollTop by the height it adds on top; bottom
|
|
156
|
+
* growth leaves scrollTop still. Matching deltas = prepend - skip scroll.
|
|
157
|
+
*/
|
|
158
|
+
const isPrepend =
|
|
159
|
+
Math.abs(scrollTopDelta - heightDelta) <= bottomThreshold;
|
|
132
160
|
const wasNearBottom =
|
|
161
|
+
!isPrepend &&
|
|
133
162
|
el.scrollTop + el.clientHeight >= prevScrollHeight - bottomThreshold;
|
|
134
163
|
const contentGrown = newScrollHeight > prevScrollHeight;
|
|
135
164
|
|
|
@@ -147,6 +176,7 @@ export const useChatScroll = ({
|
|
|
147
176
|
}
|
|
148
177
|
|
|
149
178
|
prevScrollHeight = newScrollHeight;
|
|
179
|
+
prevScrollTop = el.scrollTop;
|
|
150
180
|
updateScrollToBottomBtnVisibility(el);
|
|
151
181
|
});
|
|
152
182
|
|
|
@@ -247,7 +277,7 @@ export const useChatScroll = ({
|
|
|
247
277
|
(bottom) => {
|
|
248
278
|
if (bottom) {
|
|
249
279
|
newUnseenMessagesCount.value = 0;
|
|
250
|
-
|
|
280
|
+
markSeenIfAtBottom();
|
|
251
281
|
}
|
|
252
282
|
},
|
|
253
283
|
);
|
|
@@ -262,7 +292,8 @@ export const useChatScroll = ({
|
|
|
262
292
|
showScrollToBottomBtn,
|
|
263
293
|
newUnseenMessagesCount,
|
|
264
294
|
scrollToBottom,
|
|
295
|
+
markSeenIfAtBottom,
|
|
265
296
|
loadNextMessages,
|
|
266
|
-
handleChatScroll,
|
|
297
|
+
handleChatScroll: handleChatScrollWithSeen,
|
|
267
298
|
};
|
|
268
299
|
};
|
|
@@ -143,7 +143,7 @@ const getClientUsername = computed<string | undefined>(() => {
|
|
|
143
143
|
return props.username || props.message.member?.name;
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
function handlePlayerInitialize(player) {
|
|
146
|
+
function handlePlayerInitialize(player: object) {
|
|
147
147
|
emit(MessageAction.InitializedPlayer, {
|
|
148
148
|
player,
|
|
149
149
|
});
|
|
@@ -52,13 +52,14 @@ const isVideo = computed(() => {
|
|
|
52
52
|
});
|
|
53
53
|
const mediaSrc = computed(() => {
|
|
54
54
|
return {
|
|
55
|
-
src: props.file.streamUrl || props.file.url,
|
|
56
|
-
type: props.type,
|
|
55
|
+
src: props.file.streamUrl || props.file.url || '',
|
|
56
|
+
type: props.type ?? '',
|
|
57
57
|
};
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// `initialized` currently carries no payload, so the argument stays optional
|
|
61
|
+
function handlePlayerInitialize(player?: object) {
|
|
62
|
+
emit('initialized', player ?? {});
|
|
62
63
|
}
|
|
63
64
|
</script>
|
|
64
65
|
|
|
@@ -143,10 +143,10 @@ function closeMedia() {
|
|
|
143
143
|
|
|
144
144
|
const draft = ref<string>('');
|
|
145
145
|
|
|
146
|
-
const slottedChatActions = computed(() => {
|
|
146
|
+
const slottedChatActions = computed<ChatAction[]>(() => {
|
|
147
147
|
return Object.keys(slots)
|
|
148
148
|
.filter((key) => key.startsWith('action:'))
|
|
149
|
-
.map((key) => key.replace('action:', ''));
|
|
149
|
+
.map((key) => key.replace('action:', '') as ChatAction);
|
|
150
150
|
});
|
|
151
151
|
const isDropzoneDisabled = computed(
|
|
152
152
|
() => !props.chatActions.includes(ChatAction.AttachFiles),
|