chore: Improve new conversation form (#10687)
This commit is contained in:
parent
d070743383
commit
a899c2b5a4
@ -41,6 +41,8 @@ const props = defineProps({
|
|||||||
default: 'info',
|
default: 'info',
|
||||||
validator: value => ['info', 'error', 'success'].includes(value),
|
validator: value => ['info', 'error', 'success'].includes(value),
|
||||||
},
|
},
|
||||||
|
enableVariables: { type: Boolean, default: false },
|
||||||
|
enableCannedResponses: { type: Boolean, default: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
@ -116,6 +118,8 @@ watch(
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:focus-on-mount="focusOnMount"
|
:focus-on-mount="focusOnMount"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:enable-variables="enableVariables"
|
||||||
|
:enable-canned-responses="enableCannedResponses"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
@focus="handleFocus"
|
@focus="handleFocus"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { ref, computed, onMounted, watch } from 'vue';
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||||
import { vOnClickOutside } from '@vueuse/components';
|
import { vOnClickOutside } from '@vueuse/components';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||||
@ -30,6 +31,8 @@ const props = defineProps({
|
|||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const { fetchSignatureFlagFromUISettings } = useUISettings();
|
||||||
|
|
||||||
const contacts = ref([]);
|
const contacts = ref([]);
|
||||||
const selectedContact = ref(null);
|
const selectedContact = ref(null);
|
||||||
const targetInbox = ref(null);
|
const targetInbox = ref(null);
|
||||||
@ -43,6 +46,11 @@ const contactsUiFlags = useMapGetter('contacts/getUIFlags');
|
|||||||
const currentUser = useMapGetter('getCurrentUser');
|
const currentUser = useMapGetter('getCurrentUser');
|
||||||
const globalConfig = useMapGetter('globalConfig/get');
|
const globalConfig = useMapGetter('globalConfig/get');
|
||||||
const uiFlags = useMapGetter('contactConversations/getUIFlags');
|
const uiFlags = useMapGetter('contactConversations/getUIFlags');
|
||||||
|
const messageSignature = useMapGetter('getMessageSignature');
|
||||||
|
|
||||||
|
const sendWithSignature = computed(() =>
|
||||||
|
fetchSignatureFlagFromUISettings(targetInbox.value?.channelType)
|
||||||
|
);
|
||||||
|
|
||||||
const directUploadsEnabled = computed(
|
const directUploadsEnabled = computed(
|
||||||
() => globalConfig.value.directUploadsEnabled
|
() => globalConfig.value.directUploadsEnabled
|
||||||
@ -202,6 +210,8 @@ useKeyboardEvents(keyboardEvents);
|
|||||||
:contact-conversations-ui-flags="uiFlags"
|
:contact-conversations-ui-flags="uiFlags"
|
||||||
:contacts-ui-flags="contactsUiFlags"
|
:contacts-ui-flags="contactsUiFlags"
|
||||||
:class="composePopoverClass"
|
:class="composePopoverClass"
|
||||||
|
:message-signature="messageSignature"
|
||||||
|
:send-with-signature="sendWithSignature"
|
||||||
@search-contacts="onContactSearch"
|
@search-contacts="onContactSearch"
|
||||||
@reset-contact-search="resetContacts"
|
@reset-contact-search="resetContacts"
|
||||||
@update-selected-contact="handleSelectedContact"
|
@update-selected-contact="handleSelectedContact"
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent, ref, computed } from 'vue';
|
import { defineAsyncComponent, ref, computed, watch, nextTick } from 'vue';
|
||||||
import { useMapGetter } from 'dashboard/composables/store';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||||
import { useFileUpload } from 'dashboard/composables/useFileUpload';
|
import { useFileUpload } from 'dashboard/composables/useFileUpload';
|
||||||
@ -8,51 +7,24 @@ import { vOnClickOutside } from '@vueuse/components';
|
|||||||
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
|
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
|
||||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||||
import FileUpload from 'vue-upload-component';
|
import FileUpload from 'vue-upload-component';
|
||||||
|
import { extractTextFromMarkdown } from 'dashboard/helper/editorHelper';
|
||||||
|
|
||||||
import Button from 'dashboard/components-next/button/Button.vue';
|
import Button from 'dashboard/components-next/button/Button.vue';
|
||||||
import WhatsAppOptions from './WhatsAppOptions.vue';
|
import WhatsAppOptions from './WhatsAppOptions.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
attachedFiles: {
|
attachedFiles: { type: Array, default: () => [] },
|
||||||
type: Array,
|
isWhatsappInbox: { type: Boolean, default: false },
|
||||||
default: () => [],
|
isEmailOrWebWidgetInbox: { type: Boolean, default: false },
|
||||||
},
|
isTwilioSmsInbox: { type: Boolean, default: false },
|
||||||
isWhatsappInbox: {
|
messageTemplates: { type: Array, default: () => [] },
|
||||||
type: Boolean,
|
channelType: { type: String, default: '' },
|
||||||
default: false,
|
isLoading: { type: Boolean, default: false },
|
||||||
},
|
disableSendButton: { type: Boolean, default: false },
|
||||||
isEmailOrWebWidgetInbox: {
|
hasSelectedInbox: { type: Boolean, default: false },
|
||||||
type: Boolean,
|
hasNoInbox: { type: Boolean, default: false },
|
||||||
default: false,
|
isDropdownActive: { type: Boolean, default: false },
|
||||||
},
|
messageSignature: { type: String, default: '' },
|
||||||
isTwilioSmsInbox: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
messageTemplates: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
channelType: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
isLoading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
disableSendButton: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hasNoInbox: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isDropdownActive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
@ -74,8 +46,11 @@ const EmojiInput = defineAsyncComponent(
|
|||||||
() => import('shared/components/emoji/EmojiInput.vue')
|
() => import('shared/components/emoji/EmojiInput.vue')
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageSignature = useMapGetter('getMessageSignature');
|
const signatureToApply = computed(() =>
|
||||||
const signatureToApply = computed(() => messageSignature.value);
|
props.isEmailOrWebWidgetInbox
|
||||||
|
? props.messageSignature
|
||||||
|
: extractTextFromMarkdown(props.messageSignature)
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fetchSignatureFlagFromUISettings,
|
fetchSignatureFlagFromUISettings,
|
||||||
@ -87,13 +62,9 @@ const sendWithSignature = computed(() => {
|
|||||||
return fetchSignatureFlagFromUISettings(props.channelType);
|
return fetchSignatureFlagFromUISettings(props.channelType);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSignatureEnabledForInbox = computed(() => {
|
|
||||||
return props.isEmailOrWebWidgetInbox && sendWithSignature.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
const setSignature = () => {
|
const setSignature = () => {
|
||||||
if (signatureToApply.value) {
|
if (signatureToApply.value) {
|
||||||
if (isSignatureEnabledForInbox.value) {
|
if (sendWithSignature.value) {
|
||||||
emit('addSignature', signatureToApply.value);
|
emit('addSignature', signatureToApply.value);
|
||||||
} else {
|
} else {
|
||||||
emit('removeSignature', signatureToApply.value);
|
emit('removeSignature', signatureToApply.value);
|
||||||
@ -106,6 +77,18 @@ const toggleMessageSignature = () => {
|
|||||||
setSignature();
|
setSignature();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Added this watch to dynamically set signature.
|
||||||
|
// Only targetInbox has value and is Advance Editor(used by isEmailOrWebWidgetInbox)
|
||||||
|
// Set the signature only if the inbox based flag is true
|
||||||
|
watch(
|
||||||
|
() => props.hasSelectedInbox,
|
||||||
|
newValue => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (newValue && props.isEmailOrWebWidgetInbox) setSignature();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const onClickInsertEmoji = emoji => {
|
const onClickInsertEmoji = emoji => {
|
||||||
emit('insertEmoji', emoji);
|
emit('insertEmoji', emoji);
|
||||||
};
|
};
|
||||||
@ -213,7 +196,7 @@ useKeyboardEvents(keyboardEvents);
|
|||||||
/>
|
/>
|
||||||
</FileUpload>
|
</FileUpload>
|
||||||
<Button
|
<Button
|
||||||
v-if="isEmailOrWebWidgetInbox"
|
v-if="hasSelectedInbox && !isWhatsappInbox"
|
||||||
icon="i-lucide-signature"
|
icon="i-lucide-signature"
|
||||||
color="slate"
|
color="slate"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { INBOX_TYPES } from 'dashboard/helper/inbox';
|
|||||||
import {
|
import {
|
||||||
appendSignature,
|
appendSignature,
|
||||||
removeSignature,
|
removeSignature,
|
||||||
|
extractTextFromMarkdown,
|
||||||
} from 'dashboard/helper/editorHelper';
|
} from 'dashboard/helper/editorHelper';
|
||||||
import {
|
import {
|
||||||
buildContactableInboxesList,
|
buildContactableInboxesList,
|
||||||
@ -33,6 +34,8 @@ const props = defineProps({
|
|||||||
isDirectUploadsEnabled: { type: Boolean, default: false },
|
isDirectUploadsEnabled: { type: Boolean, default: false },
|
||||||
contactConversationsUiFlags: { type: Object, default: null },
|
contactConversationsUiFlags: { type: Object, default: null },
|
||||||
contactsUiFlags: { type: Object, default: null },
|
contactsUiFlags: { type: Object, default: null },
|
||||||
|
messageSignature: { type: String, default: '' },
|
||||||
|
sendWithSignature: { type: Boolean, default: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
@ -184,6 +187,14 @@ const handleInboxAction = ({ value, action, ...rest }) => {
|
|||||||
|
|
||||||
const removeTargetInbox = value => {
|
const removeTargetInbox = value => {
|
||||||
v$.value.$reset();
|
v$.value.$reset();
|
||||||
|
// Remove the signature from message content
|
||||||
|
// Based on the Advance Editor (used in isEmailOrWebWidget) and Plain editor(all other inboxes except WhatsApp)
|
||||||
|
if (props.sendWithSignature) {
|
||||||
|
const signatureToRemove = inboxTypes.value.isEmailOrWebWidget
|
||||||
|
? props.messageSignature
|
||||||
|
: extractTextFromMarkdown(props.messageSignature);
|
||||||
|
state.message = removeSignature(state.message, signatureToRemove);
|
||||||
|
}
|
||||||
emit('updateTargetInbox', value);
|
emit('updateTargetInbox', value);
|
||||||
state.attachedFiles = [];
|
state.attachedFiles = [];
|
||||||
};
|
};
|
||||||
@ -302,6 +313,8 @@ const handleSendWhatsappMessage = async ({ message, templateParams }) => {
|
|||||||
<MessageEditor
|
<MessageEditor
|
||||||
v-if="!inboxTypes.isWhatsapp && !showNoInboxAlert"
|
v-if="!inboxTypes.isWhatsapp && !showNoInboxAlert"
|
||||||
v-model="state.message"
|
v-model="state.message"
|
||||||
|
:message-signature="messageSignature"
|
||||||
|
:send-with-signature="sendWithSignature"
|
||||||
:is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget"
|
:is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget"
|
||||||
:has-errors="validationStates.isMessageInvalid"
|
:has-errors="validationStates.isMessageInvalid"
|
||||||
:has-attachments="state.attachedFiles.length > 0"
|
:has-attachments="state.attachedFiles.length > 0"
|
||||||
@ -322,8 +335,10 @@ const handleSendWhatsappMessage = async ({ message, templateParams }) => {
|
|||||||
:channel-type="inboxChannelType"
|
:channel-type="inboxChannelType"
|
||||||
:is-loading="isCreating"
|
:is-loading="isCreating"
|
||||||
:disable-send-button="isCreating"
|
:disable-send-button="isCreating"
|
||||||
|
:has-selected-inbox="!!targetInbox"
|
||||||
:has-no-inbox="showNoInboxAlert"
|
:has-no-inbox="showNoInboxAlert"
|
||||||
:is-dropdown-active="isAnyDropdownActive"
|
:is-dropdown-active="isAnyDropdownActive"
|
||||||
|
:message-signature="messageSignature"
|
||||||
@insert-emoji="onClickInsertEmoji"
|
@insert-emoji="onClickInsertEmoji"
|
||||||
@add-signature="handleAddSignature"
|
@add-signature="handleAddSignature"
|
||||||
@remove-signature="handleRemoveSignature"
|
@remove-signature="handleRemoveSignature"
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, watch, computed, nextTick } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import {
|
||||||
|
appendSignature,
|
||||||
|
extractTextFromMarkdown,
|
||||||
|
removeSignature,
|
||||||
|
} from 'dashboard/helper/editorHelper';
|
||||||
|
|
||||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||||
import TextArea from 'dashboard/components-next/textarea/TextArea.vue';
|
import TextArea from 'dashboard/components-next/textarea/TextArea.vue';
|
||||||
|
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.vue';
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
isEmailOrWebWidgetInbox: {
|
isEmailOrWebWidgetInbox: { type: Boolean, required: true },
|
||||||
type: Boolean,
|
hasErrors: { type: Boolean, default: false },
|
||||||
required: true,
|
hasAttachments: { type: Boolean, default: false },
|
||||||
},
|
sendWithSignature: { type: Boolean, default: false },
|
||||||
hasErrors: {
|
messageSignature: { type: String, default: '' },
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hasAttachments: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@ -25,14 +25,58 @@ const modelValue = defineModel({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const state = ref({
|
||||||
|
hasSlashCommand: false,
|
||||||
|
showMentions: false,
|
||||||
|
mentionSearchKey: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const plainTextSignature = computed(() =>
|
||||||
|
extractTextFromMarkdown(props.messageSignature)
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
modelValue,
|
||||||
|
newValue => {
|
||||||
|
if (props.isEmailOrWebWidgetInbox) return;
|
||||||
|
|
||||||
|
const bodyWithoutSignature = newValue
|
||||||
|
? removeSignature(newValue, plainTextSignature.value)
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// Check if message starts with slash
|
||||||
|
const startsWithSlash = bodyWithoutSignature.startsWith('/');
|
||||||
|
|
||||||
|
// Update slash command and mentions state
|
||||||
|
state.value = {
|
||||||
|
...state.value,
|
||||||
|
hasSlashCommand: startsWithSlash,
|
||||||
|
showMentions: startsWithSlash,
|
||||||
|
mentionSearchKey: startsWithSlash ? bodyWithoutSignature.slice(1) : '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const hideMention = () => {
|
||||||
|
state.value.showMentions = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceText = async message => {
|
||||||
|
// Only append signature on replace if sendWithSignature is true
|
||||||
|
const finalMessage = props.sendWithSignature
|
||||||
|
? appendSignature(message, plainTextSignature.value)
|
||||||
|
: message;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
modelValue.value = finalMessage;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex-1 h-full" :class="[!hasAttachments && 'min-h-[200px]']">
|
||||||
v-if="isEmailOrWebWidgetInbox"
|
<template v-if="isEmailOrWebWidgetInbox">
|
||||||
class="flex-1 h-full"
|
|
||||||
:class="!hasAttachments && 'min-h-[200px]'"
|
|
||||||
>
|
|
||||||
<Editor
|
<Editor
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
@ -44,22 +88,35 @@ const modelValue = defineModel({
|
|||||||
? '[&_.empty-node]:before:!text-n-ruby-9 [&_.empty-node]:dark:before:!text-n-ruby-9'
|
? '[&_.empty-node]:before:!text-n-ruby-9 [&_.empty-node]:dark:before:!text-n-ruby-9'
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
|
enable-variables
|
||||||
:show-character-count="false"
|
:show-character-count="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
<div v-else class="flex-1 h-full" :class="!hasAttachments && 'min-h-[200px]'">
|
<template v-else>
|
||||||
<TextArea
|
<TextArea
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
t('COMPOSE_NEW_CONVERSATION.FORM.MESSAGE_EDITOR.PLACEHOLDER')
|
t('COMPOSE_NEW_CONVERSATION.FORM.MESSAGE_EDITOR.PLACEHOLDER')
|
||||||
"
|
"
|
||||||
class="!px-0 [&>div]:!px-4 [&>div]:!border-transparent [&>div]:!bg-transparent"
|
class="!px-0 [&>div]:!px-4 [&>div]:!border-transparent [&>div]:!bg-transparent"
|
||||||
auto-height
|
|
||||||
:custom-text-area-class="
|
:custom-text-area-class="
|
||||||
hasErrors
|
hasErrors
|
||||||
? 'placeholder:!text-n-ruby-9 dark:placeholder:!text-n-ruby-9'
|
? 'placeholder:!text-n-ruby-9 dark:placeholder:!text-n-ruby-9'
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
|
auto-height
|
||||||
|
allow-signature
|
||||||
|
:signature="messageSignature"
|
||||||
|
:send-with-signature="sendWithSignature"
|
||||||
|
>
|
||||||
|
<CannedResponse
|
||||||
|
v-if="state.showMentions && state.hasSlashCommand"
|
||||||
|
v-on-clickaway="hideMention"
|
||||||
|
class="normal-editor__canned-box"
|
||||||
|
:search-key="state.mentionSearchKey"
|
||||||
|
@replace="replaceText"
|
||||||
/>
|
/>
|
||||||
|
</TextArea>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,72 +1,35 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, onMounted, nextTick, watch } from 'vue';
|
import { computed, ref, onMounted, nextTick, watch } from 'vue';
|
||||||
|
import {
|
||||||
|
appendSignature,
|
||||||
|
removeSignature,
|
||||||
|
extractTextFromMarkdown,
|
||||||
|
} from 'dashboard/helper/editorHelper';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: { type: String, default: '' },
|
||||||
type: String,
|
label: { type: String, default: '' },
|
||||||
default: '',
|
placeholder: { type: String, default: '' },
|
||||||
},
|
maxLength: { type: Number, default: 200 },
|
||||||
label: {
|
id: { type: String, default: '' },
|
||||||
type: String,
|
disabled: { type: Boolean, default: false },
|
||||||
default: '',
|
customTextAreaClass: { type: String, default: '' },
|
||||||
},
|
customTextAreaWrapperClass: { type: String, default: '' },
|
||||||
placeholder: {
|
showCharacterCount: { type: Boolean, default: false },
|
||||||
type: String,
|
autoHeight: { type: Boolean, default: false },
|
||||||
default: '',
|
resize: { type: Boolean, default: false },
|
||||||
},
|
minHeight: { type: String, default: '4rem' },
|
||||||
maxLength: {
|
maxHeight: { type: String, default: '12rem' },
|
||||||
type: Number,
|
autofocus: { type: Boolean, default: false },
|
||||||
default: 200,
|
message: { type: String, default: '' },
|
||||||
},
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
customTextAreaClass: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
customTextAreaWrapperClass: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
showCharacterCount: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
autoHeight: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
resize: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
minHeight: {
|
|
||||||
type: String,
|
|
||||||
default: '4rem',
|
|
||||||
},
|
|
||||||
maxHeight: {
|
|
||||||
type: String,
|
|
||||||
default: '12rem',
|
|
||||||
},
|
|
||||||
autofocus: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
messageType: {
|
messageType: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'info',
|
default: 'info',
|
||||||
validator: value => ['info', 'error', 'success'].includes(value),
|
validator: value => ['info', 'error', 'success'].includes(value),
|
||||||
},
|
},
|
||||||
|
signature: { type: String, default: '' },
|
||||||
|
sendWithSignature: { type: Boolean, default: false }, // add this as a prop, so that we won't have to add useUISettings
|
||||||
|
allowSignature: { type: Boolean, default: false }, // allowSignature is a kill switch, ensuring no signature methods are triggered except when this flag is true
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
@ -75,6 +38,9 @@ const textareaRef = ref(null);
|
|||||||
const isFocused = ref(false);
|
const isFocused = ref(false);
|
||||||
|
|
||||||
const characterCount = computed(() => props.modelValue.length);
|
const characterCount = computed(() => props.modelValue.length);
|
||||||
|
const cleanedSignature = computed(() =>
|
||||||
|
extractTextFromMarkdown(props.signature)
|
||||||
|
);
|
||||||
|
|
||||||
const messageClass = computed(() => {
|
const messageClass = computed(() => {
|
||||||
switch (props.messageType) {
|
switch (props.messageType) {
|
||||||
@ -97,6 +63,32 @@ const adjustHeight = () => {
|
|||||||
textareaRef.value.style.height = `${textareaRef.value.scrollHeight}px`;
|
textareaRef.value.style.height = `${textareaRef.value.scrollHeight}px`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setCursor = () => {
|
||||||
|
if (!textareaRef.value) return;
|
||||||
|
|
||||||
|
const bodyWithoutSignature = removeSignature(
|
||||||
|
props.modelValue,
|
||||||
|
cleanedSignature.value
|
||||||
|
);
|
||||||
|
const bodyEndsAt = bodyWithoutSignature.trimEnd().length;
|
||||||
|
|
||||||
|
textareaRef.value.focus();
|
||||||
|
textareaRef.value.setSelectionRange(bodyEndsAt, bodyEndsAt);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSignatureInEditor = signatureEnabled => {
|
||||||
|
if (!props.allowSignature) return;
|
||||||
|
const valueWithSignature = signatureEnabled
|
||||||
|
? appendSignature(props.modelValue, cleanedSignature.value)
|
||||||
|
: removeSignature(props.modelValue, cleanedSignature.value);
|
||||||
|
emit('update:modelValue', valueWithSignature);
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
adjustHeight();
|
||||||
|
setCursor();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleInput = event => {
|
const handleInput = event => {
|
||||||
emit('update:modelValue', event.target.value);
|
emit('update:modelValue', event.target.value);
|
||||||
if (props.autoHeight) {
|
if (props.autoHeight) {
|
||||||
@ -126,13 +118,20 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.sendWithSignature,
|
||||||
|
newValue => {
|
||||||
|
if (props.allowSignature) toggleSignatureInEditor(newValue);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.autoHeight) {
|
if (props.autoHeight) {
|
||||||
nextTick(adjustHeight);
|
nextTick(adjustHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.autofocus) {
|
if (props.autofocus) {
|
||||||
textareaRef.value.focus();
|
textareaRef.value?.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -161,6 +160,7 @@ onMounted(() => {
|
|||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
<slot /><!-- Slot for adding popover -->
|
||||||
<textarea
|
<textarea
|
||||||
:id="id"
|
:id="id"
|
||||||
ref="textareaRef"
|
ref="textareaRef"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user