jiuyiUniapp/jiuyi/TUIKit/components/TUIChat/message-input/message-input-editor.vue

286 lines
6.3 KiB
Vue

<template>
<div
:class="{
'message-input-container': true,
'message-input-container-h5': !isPC,
}"
>
<div
v-if="props.isMuted"
class="message-input-mute"
>
{{ props.muteText }}
</div>
<input
id="editor"
ref="inputRef"
v-model="inputText"
:adjust-position="true"
cursor-spacing="20"
confirm-type="send"
:confirm-hold="true"
maxlength="140"
type="text"
placeholder-class="input-placeholder"
class="message-input-area"
:placeholder="props.placeholder"
auto-blur
@confirm="handleSendMessage"
@input="onInput"
@blur="onBlur"
@focus="onFocus"
>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
import { TUIStore, StoreName, IConversationModel, IMessageModel } from '@tencentcloud/chat-uikit-engine';
import { TUIGlobal } from '@tencentcloud/universal-api';
import DraftManager from '../utils/conversationDraft';
import { transformTextWithEmojiNamesToKeys } from '../emoji-config';
import { isPC } from '../../../utils/env';
import { sendMessages } from '../utils/sendMessage';
import { ISendMessagePayload } from '../../../interface';
const props = defineProps({
placeholder: {
type: String,
default: 'this is placeholder',
},
replayOrReferenceMessage: {
type: Object,
default: () => ({}),
required: false,
},
isMuted: {
type: Boolean,
default: true,
},
muteText: {
type: String,
default: '',
},
enableInput: {
type: Boolean,
default: true,
},
enableAt: {
type: Boolean,
default: true,
},
enableTyping: {
type: Boolean,
default: true,
},
isGroup: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['onTyping', 'onFocus', 'onAt']);
const inputText = ref('');
const inputRef = ref();
const inputBlur = ref(true);
const inputContentEmpty = ref(true);
const allInsertedAtInfo = new Map();
const currentConversation = ref<IConversationModel>();
const currentConversationID = ref<string>('');
const currentQuoteMessage = ref<{ message: IMessageModel; type: string }>();
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.watch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
uni.$on('insert-emoji', (data) => {
inputText.value += data?.emoji?.name;
});
uni.$on('send-message-in-emoji-picker', () => {
handleSendMessage();
});
});
onUnmounted(() => {
if (currentConversationID.value) {
DraftManager.setStore(currentConversationID.value, inputText.value, inputText.value, currentQuoteMessage.value);
}
uni.$off('insertEmoji');
uni.$off('send-message-in-emoji-picker');
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.unwatch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
reset();
});
const handleSendMessage = () => {
const messageList = getEditorContent();
resetEditor();
sendMessages(messageList as any, currentConversation.value!);
};
const insertAt = (atInfo: any) => {
if (!allInsertedAtInfo?.has(atInfo?.id)) {
allInsertedAtInfo?.set(atInfo?.id, atInfo?.label);
}
inputText.value += atInfo?.label;
};
const getEditorContent = () => {
let text = inputText.value;
text = transformTextWithEmojiNamesToKeys(text);
const atUserList: string[] = [];
allInsertedAtInfo?.forEach((value: string, key: string) => {
if (text?.includes('@' + value)) {
atUserList.push(key);
}
});
const payload: ISendMessagePayload = {
text,
};
if (atUserList?.length) {
payload.atUserList = atUserList;
}
return [
{
type: 'text',
payload,
},
];
};
const resetEditor = () => {
inputText.value = '';
inputContentEmpty.value = true;
allInsertedAtInfo?.clear();
};
const setEditorContent = (content: any) => {
inputText.value = content;
};
const onBlur = () => {
inputBlur.value = true;
};
const onFocus = (e: any) => {
inputBlur.value = false;
emits('onFocus', e?.detail?.height);
};
const isEditorContentEmpty = () => {
inputContentEmpty.value = inputText?.value?.length ? false : true;
};
const onInput = (e: any) => {
// uni-app recognizes mention messages
const text = e?.detail?.value;
isEditorContentEmpty();
if (props.isGroup && (text.endsWith('@') || text.endsWith('@\n'))) {
TUIGlobal?.hideKeyboard();
emits('onAt', true);
}
};
watch(
() => [inputContentEmpty.value, inputBlur.value],
(newVal: any, oldVal: any) => {
if (newVal !== oldVal) {
emits('onTyping', inputContentEmpty.value, inputBlur.value);
}
},
{
immediate: true,
deep: true,
},
);
function onCurrentConversationUpdated(conversation: IConversationModel) {
const prevConversationID = currentConversationID.value;
currentConversation.value = conversation;
currentConversationID.value = conversation?.conversationID;
if (prevConversationID !== currentConversationID.value) {
if (prevConversationID) {
DraftManager.setStore(
prevConversationID,
inputText.value,
inputText.value,
currentQuoteMessage.value,
);
}
resetEditor();
if (currentConversationID.value) {
DraftManager.getStore(currentConversationID.value, setEditorContent);
}
}
}
function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
currentQuoteMessage.value = options;
}
function reset() {
inputBlur.value = true;
currentConversation.value = null;
currentConversationID.value = '';
currentQuoteMessage.value = null;
resetEditor();
}
defineExpose({
insertAt,
resetEditor,
setEditorContent,
getEditorContent,
});
</script>
<style lang="scss" scoped>
@import "../../../assets/styles/common";
.message-input-container {
display: flex;
flex-direction: column;
flex: 1;
padding: 3px 10px 10px;
overflow: hidden;
&-h5 {
flex: 1;
height: auto;
background: #fff;
border-radius: 10px;
padding: 7px 0 7px 10px;
font-size: 16px !important;
max-height: 86px;
}
.message-input-mute {
flex: 1;
display: flex;
color: #999;
font-size: 14px;
justify-content: center;
align-items: center;
}
.message-input-area {
flex: 1;
overflow-y: scroll;
min-height: 25px;
}
}
</style>