chore: Improve compose new conversation form (#13176)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import WootEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: '' },
|
modelValue: { type: String, default: '' },
|
||||||
|
editorKey: { type: String, default: '' },
|
||||||
label: { type: String, default: '' },
|
label: { type: String, default: '' },
|
||||||
placeholder: { type: String, default: '' },
|
placeholder: { type: String, default: '' },
|
||||||
focusOnMount: { type: Boolean, default: false },
|
focusOnMount: { type: Boolean, default: false },
|
||||||
@@ -96,6 +97,7 @@ watch(
|
|||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<WootEditor
|
<WootEditor
|
||||||
|
:editor-id="editorKey"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:focus-on-mount="focusOnMount"
|
:focus-on-mount="focusOnMount"
|
||||||
@@ -152,6 +154,13 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ProseMirror-menubar {
|
||||||
|
width: fit-content !important;
|
||||||
|
position: relative !important;
|
||||||
|
top: unset !important;
|
||||||
|
@apply ltr:left-[-0.188rem] rtl:right-[-0.188rem] !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ const emit = defineEmits([
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const attachmentId = ref(0);
|
||||||
|
const generateUid = () => {
|
||||||
|
attachmentId.value += 1;
|
||||||
|
return `attachment-${attachmentId.value}`;
|
||||||
|
};
|
||||||
|
|
||||||
const uploadAttachment = ref(null);
|
const uploadAttachment = ref(null);
|
||||||
const isEmojiPickerOpen = ref(false);
|
const isEmojiPickerOpen = ref(false);
|
||||||
|
|
||||||
@@ -176,7 +182,8 @@ const onPaste = e => {
|
|||||||
.filter(file => file.size > 0)
|
.filter(file => file.size > 0)
|
||||||
.forEach(file => {
|
.forEach(file => {
|
||||||
const { name, type, size } = file;
|
const { name, type, size } = file;
|
||||||
onFileUpload({ file, name, type, size });
|
// Add unique ID for clipboard-pasted files
|
||||||
|
onFileUpload({ file, name, type, size, id: generateUid() });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
appendSignature,
|
appendSignature,
|
||||||
removeSignature,
|
removeSignature,
|
||||||
getEffectiveChannelType,
|
getEffectiveChannelType,
|
||||||
|
stripUnsupportedMarkdown,
|
||||||
} from 'dashboard/helper/editorHelper';
|
} from 'dashboard/helper/editorHelper';
|
||||||
import {
|
import {
|
||||||
buildContactableInboxesList,
|
buildContactableInboxesList,
|
||||||
@@ -47,6 +48,8 @@ const emit = defineEmits([
|
|||||||
'createConversation',
|
'createConversation',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const DEFAULT_FORMATTING = 'Context::Default';
|
||||||
|
|
||||||
const showContactsDropdown = ref(false);
|
const showContactsDropdown = ref(false);
|
||||||
const showInboxesDropdown = ref(false);
|
const showInboxesDropdown = ref(false);
|
||||||
const showCcEmailsDropdown = ref(false);
|
const showCcEmailsDropdown = ref(false);
|
||||||
@@ -198,10 +201,22 @@ const setSelectedContact = async ({ value, action, ...rest }) => {
|
|||||||
showInboxesDropdown.value = true;
|
showInboxesDropdown.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInboxAction = ({ value, action, ...rest }) => {
|
const stripMessageFormatting = channelType => {
|
||||||
|
if (!state.message || !channelType) return;
|
||||||
|
|
||||||
|
state.message = stripUnsupportedMarkdown(state.message, channelType, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInboxAction = ({ value, action, channelType, medium, ...rest }) => {
|
||||||
v$.value.$reset();
|
v$.value.$reset();
|
||||||
state.message = '';
|
|
||||||
emit('updateTargetInbox', { ...rest });
|
// Strip unsupported formatting when changing the target inbox
|
||||||
|
if (channelType) {
|
||||||
|
const newChannelType = getEffectiveChannelType(channelType, medium);
|
||||||
|
stripMessageFormatting(newChannelType);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('updateTargetInbox', { ...rest, channelType, medium });
|
||||||
showInboxesDropdown.value = false;
|
showInboxesDropdown.value = false;
|
||||||
state.attachedFiles = [];
|
state.attachedFiles = [];
|
||||||
};
|
};
|
||||||
@@ -221,7 +236,9 @@ const removeSignatureFromMessage = () => {
|
|||||||
const removeTargetInbox = value => {
|
const removeTargetInbox = value => {
|
||||||
v$.value.$reset();
|
v$.value.$reset();
|
||||||
removeSignatureFromMessage();
|
removeSignatureFromMessage();
|
||||||
state.message = '';
|
|
||||||
|
stripMessageFormatting(DEFAULT_FORMATTING);
|
||||||
|
|
||||||
emit('updateTargetInbox', value);
|
emit('updateTargetInbox', value);
|
||||||
state.attachedFiles = [];
|
state.attachedFiles = [];
|
||||||
};
|
};
|
||||||
@@ -324,67 +341,68 @@ const shouldShowMessageEditor = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="w-[42rem] divide-y divide-n-strong overflow-visible transition-all duration-300 ease-in-out top-full justify-between flex flex-col bg-n-alpha-3 border border-n-strong shadow-sm backdrop-blur-[100px] rounded-xl min-w-0"
|
class="w-[42rem] divide-y divide-n-strong overflow-visible transition-all duration-300 ease-in-out top-full flex flex-col bg-n-alpha-3 border border-n-strong shadow-sm backdrop-blur-[100px] rounded-xl min-w-0 max-h-[calc(100vh-8rem)]"
|
||||||
>
|
>
|
||||||
<ContactSelector
|
<div class="flex-1 overflow-y-auto divide-y divide-n-strong">
|
||||||
:contacts="contacts"
|
<ContactSelector
|
||||||
:selected-contact="selectedContact"
|
:contacts="contacts"
|
||||||
:show-contacts-dropdown="showContactsDropdown"
|
:selected-contact="selectedContact"
|
||||||
:is-loading="isLoading"
|
:show-contacts-dropdown="showContactsDropdown"
|
||||||
:is-creating-contact="isCreatingContact"
|
:is-loading="isLoading"
|
||||||
:contact-id="contactId"
|
:is-creating-contact="isCreatingContact"
|
||||||
:contactable-inboxes-list="contactableInboxesList"
|
:contact-id="contactId"
|
||||||
:show-inboxes-dropdown="showInboxesDropdown"
|
:contactable-inboxes-list="contactableInboxesList"
|
||||||
:has-errors="validationStates.isContactInvalid"
|
:show-inboxes-dropdown="showInboxesDropdown"
|
||||||
@search-contacts="handleContactSearch"
|
:has-errors="validationStates.isContactInvalid"
|
||||||
@set-selected-contact="setSelectedContact"
|
@search-contacts="handleContactSearch"
|
||||||
@clear-selected-contact="clearSelectedContact"
|
@set-selected-contact="setSelectedContact"
|
||||||
@update-dropdown="handleDropdownUpdate"
|
@clear-selected-contact="clearSelectedContact"
|
||||||
/>
|
@update-dropdown="handleDropdownUpdate"
|
||||||
<InboxEmptyState v-if="showNoInboxAlert" />
|
/>
|
||||||
<InboxSelector
|
<InboxEmptyState v-if="showNoInboxAlert" />
|
||||||
v-else
|
<InboxSelector
|
||||||
:target-inbox="targetInbox"
|
v-else
|
||||||
:selected-contact="selectedContact"
|
:target-inbox="targetInbox"
|
||||||
:show-inboxes-dropdown="showInboxesDropdown"
|
:selected-contact="selectedContact"
|
||||||
:contactable-inboxes-list="contactableInboxesList"
|
:show-inboxes-dropdown="showInboxesDropdown"
|
||||||
:has-errors="validationStates.isInboxInvalid"
|
:contactable-inboxes-list="contactableInboxesList"
|
||||||
@update-inbox="removeTargetInbox"
|
:has-errors="validationStates.isInboxInvalid"
|
||||||
@toggle-dropdown="showInboxesDropdown = $event"
|
@update-inbox="removeTargetInbox"
|
||||||
@handle-inbox-action="handleInboxAction"
|
@toggle-dropdown="showInboxesDropdown = $event"
|
||||||
/>
|
@handle-inbox-action="handleInboxAction"
|
||||||
|
/>
|
||||||
|
|
||||||
<EmailOptions
|
<EmailOptions
|
||||||
v-if="inboxTypes.isEmail"
|
v-if="inboxTypes.isEmail"
|
||||||
v-model:cc-emails="state.ccEmails"
|
v-model:cc-emails="state.ccEmails"
|
||||||
v-model:bcc-emails="state.bccEmails"
|
v-model:bcc-emails="state.bccEmails"
|
||||||
v-model:subject="state.subject"
|
v-model:subject="state.subject"
|
||||||
:contacts="contacts"
|
:contacts="contacts"
|
||||||
:show-cc-emails-dropdown="showCcEmailsDropdown"
|
:show-cc-emails-dropdown="showCcEmailsDropdown"
|
||||||
:show-bcc-emails-dropdown="showBccEmailsDropdown"
|
:show-bcc-emails-dropdown="showBccEmailsDropdown"
|
||||||
:is-loading="isLoading"
|
:is-loading="isLoading"
|
||||||
:has-errors="validationStates.isSubjectInvalid"
|
:has-errors="validationStates.isSubjectInvalid"
|
||||||
@search-cc-emails="searchCcEmails"
|
@search-cc-emails="searchCcEmails"
|
||||||
@search-bcc-emails="searchBccEmails"
|
@search-bcc-emails="searchBccEmails"
|
||||||
@update-dropdown="handleDropdownUpdate"
|
@update-dropdown="handleDropdownUpdate"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MessageEditor
|
<MessageEditor
|
||||||
v-if="shouldShowMessageEditor"
|
v-if="shouldShowMessageEditor"
|
||||||
v-model="state.message"
|
v-model="state.message"
|
||||||
:message-signature="messageSignature"
|
:message-signature="messageSignature"
|
||||||
:send-with-signature="sendWithSignature"
|
:send-with-signature="sendWithSignature"
|
||||||
:has-errors="validationStates.isMessageInvalid"
|
:has-errors="validationStates.isMessageInvalid"
|
||||||
:has-attachments="state.attachedFiles.length > 0"
|
:channel-type="inboxChannelType"
|
||||||
:channel-type="inboxChannelType"
|
:medium="targetInbox?.medium || ''"
|
||||||
:medium="targetInbox?.medium || ''"
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<AttachmentPreviews
|
<AttachmentPreviews
|
||||||
v-if="state.attachedFiles.length > 0"
|
v-if="state.attachedFiles.length > 0"
|
||||||
:attachments="state.attachedFiles"
|
:attachments="state.attachedFiles"
|
||||||
@update:attachments="state.attachedFiles = $event"
|
@update:attachments="state.attachedFiles = $event"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ActionButtons
|
<ActionButtons
|
||||||
:attached-files="state.attachedFiles"
|
:attached-files="state.attachedFiles"
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ const targetInboxLabel = computed(() => {
|
|||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
v-if="contactableInboxesList?.length > 0 && showInboxesDropdown"
|
v-if="contactableInboxesList?.length > 0 && showInboxesDropdown"
|
||||||
:menu-items="contactableInboxesList"
|
:menu-items="contactableInboxesList"
|
||||||
class="ltr:left-0 rtl:right-0 z-[100] top-8 overflow-y-auto max-h-60 w-fit max-w-sm dark:!outline-n-slate-5"
|
class="ltr:left-0 rtl:right-0 z-[100] top-8 overflow-y-auto max-h-56 w-fit max-w-sm dark:!outline-n-slate-5"
|
||||||
@action="emit('handleInboxAction', $event)"
|
@action="emit('handleInboxAction', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
hasErrors: { type: Boolean, default: false },
|
hasErrors: { type: Boolean, default: false },
|
||||||
hasAttachments: { type: Boolean, default: false },
|
|
||||||
sendWithSignature: { type: Boolean, default: false },
|
sendWithSignature: { type: Boolean, default: false },
|
||||||
messageSignature: { type: String, default: '' },
|
messageSignature: { type: String, default: '' },
|
||||||
channelType: { type: String, default: '' },
|
channelType: { type: String, default: '' },
|
||||||
@@ -24,14 +23,14 @@ const modelValue = defineModel({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-1 h-full" :class="[!hasAttachments && 'min-h-[200px]']">
|
<div class="flex-1 h-full">
|
||||||
<Editor
|
<Editor
|
||||||
:key="editorKey"
|
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
|
:editor-key="editorKey"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
t('COMPOSE_NEW_CONVERSATION.FORM.MESSAGE_EDITOR.PLACEHOLDER')
|
t('COMPOSE_NEW_CONVERSATION.FORM.MESSAGE_EDITOR.PLACEHOLDER')
|
||||||
"
|
"
|
||||||
class="[&>div]:!border-transparent [&>div]:px-4 [&>div]:py-4 [&>div]:!bg-transparent h-full [&_.ProseMirror-woot-style]:!max-h-[200px]"
|
class="[&>div]:!border-transparent [&>div]:px-4 [&>div]:py-4 [&>div]:!bg-transparent h-full [&_.ProseMirror-woot-style]:!max-h-[12.5rem] [&_.ProseMirror-woot-style]:!min-h-[10rem] [&_.ProseMirror-menubar]:!pt-0 [&_.mention--box]:-top-[7.5rem] [&_.mention--box]:bottom-[unset]"
|
||||||
:class="
|
:class="
|
||||||
hasErrors
|
hasErrors
|
||||||
? '[&_.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'
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ const handleBlur = e => emit('blur', e);
|
|||||||
v-if="showDropdownMenu"
|
v-if="showDropdownMenu"
|
||||||
:menu-items="filteredMenuItems"
|
:menu-items="filteredMenuItems"
|
||||||
:is-searching="isLoading"
|
:is-searching="isLoading"
|
||||||
class="ltr:left-0 rtl:right-0 z-[100] top-8 overflow-y-auto max-h-60 w-[inherit] max-w-md dark:!outline-n-slate-5"
|
class="ltr:left-0 rtl:right-0 z-[100] top-8 overflow-y-auto max-h-56 w-[inherit] max-w-md dark:!outline-n-slate-5"
|
||||||
@action="handleDropdownAction"
|
@action="handleDropdownAction"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,9 +38,14 @@ export function extractTextFromMarkdown(markdown) {
|
|||||||
*
|
*
|
||||||
* @param {string} markdown - markdown text to process
|
* @param {string} markdown - markdown text to process
|
||||||
* @param {string} channelType - The channel type to check supported formatting
|
* @param {string} channelType - The channel type to check supported formatting
|
||||||
|
* @param {boolean} cleanWhitespace - Whether to clean up extra whitespace and blank lines (default: true for signatures)
|
||||||
* @returns {string} - The markdown with unsupported formatting removed
|
* @returns {string} - The markdown with unsupported formatting removed
|
||||||
*/
|
*/
|
||||||
export function stripUnsupportedSignatureMarkdown(markdown, channelType) {
|
export function stripUnsupportedMarkdown(
|
||||||
|
markdown,
|
||||||
|
channelType,
|
||||||
|
cleanWhitespace = true
|
||||||
|
) {
|
||||||
if (!markdown) return '';
|
if (!markdown) return '';
|
||||||
|
|
||||||
const { marks = [], nodes = [] } = FORMATTING[channelType] || {};
|
const { marks = [], nodes = [] } = FORMATTING[channelType] || {};
|
||||||
@@ -55,6 +60,9 @@ export function stripUnsupportedSignatureMarkdown(markdown, channelType) {
|
|||||||
);
|
);
|
||||||
}, markdown);
|
}, markdown);
|
||||||
|
|
||||||
|
if (!cleanWhitespace) return result;
|
||||||
|
|
||||||
|
// Clean whitespace for signatures
|
||||||
return result
|
return result
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(line => line.trim())
|
.map(line => line.trim())
|
||||||
@@ -155,7 +163,7 @@ export function getEffectiveChannelType(channelType, medium) {
|
|||||||
export function appendSignature(body, signature, channelType) {
|
export function appendSignature(body, signature, channelType) {
|
||||||
// Strip only unsupported formatting based on channel capabilities
|
// Strip only unsupported formatting based on channel capabilities
|
||||||
const preparedSignature = channelType
|
const preparedSignature = channelType
|
||||||
? stripUnsupportedSignatureMarkdown(signature, channelType)
|
? stripUnsupportedMarkdown(signature, channelType)
|
||||||
: signature;
|
: signature;
|
||||||
const cleanedSignature = cleanSignature(preparedSignature);
|
const cleanedSignature = cleanSignature(preparedSignature);
|
||||||
// if signature is already present, return body
|
// if signature is already present, return body
|
||||||
@@ -178,7 +186,7 @@ export function appendSignature(body, signature, channelType) {
|
|||||||
export function removeSignature(body, signature, channelType) {
|
export function removeSignature(body, signature, channelType) {
|
||||||
// Build unique list of signature variants to try
|
// Build unique list of signature variants to try
|
||||||
const channelStripped = channelType
|
const channelStripped = channelType
|
||||||
? cleanSignature(stripUnsupportedSignatureMarkdown(signature, channelType))
|
? cleanSignature(stripUnsupportedMarkdown(signature, channelType))
|
||||||
: null;
|
: null;
|
||||||
const signaturesToTry = [
|
const signaturesToTry = [
|
||||||
cleanSignature(signature),
|
cleanSignature(signature),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
replaceSignature,
|
replaceSignature,
|
||||||
cleanSignature,
|
cleanSignature,
|
||||||
extractTextFromMarkdown,
|
extractTextFromMarkdown,
|
||||||
stripUnsupportedSignatureMarkdown,
|
stripUnsupportedMarkdown,
|
||||||
insertAtCursor,
|
insertAtCursor,
|
||||||
findNodeToInsertImage,
|
findNodeToInsertImage,
|
||||||
setURLWithQueryAndSize,
|
setURLWithQueryAndSize,
|
||||||
@@ -145,25 +145,19 @@ describe('appendSignature', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stripUnsupportedSignatureMarkdown', () => {
|
describe('stripUnsupportedMarkdown', () => {
|
||||||
const richSignature =
|
const richSignature =
|
||||||
'**Bold** _italic_ [link](http://example.com) ';
|
'**Bold** _italic_ [link](http://example.com) ';
|
||||||
|
|
||||||
it('keeps all formatting for Email channel (supports image, link, strong, em)', () => {
|
it('keeps all formatting for Email channel (supports image, link, strong, em)', () => {
|
||||||
const result = stripUnsupportedSignatureMarkdown(
|
const result = stripUnsupportedMarkdown(richSignature, 'Channel::Email');
|
||||||
richSignature,
|
|
||||||
'Channel::Email'
|
|
||||||
);
|
|
||||||
expect(result).toContain('**Bold**');
|
expect(result).toContain('**Bold**');
|
||||||
expect(result).toContain('_italic_');
|
expect(result).toContain('_italic_');
|
||||||
expect(result).toContain('[link](http://example.com)');
|
expect(result).toContain('[link](http://example.com)');
|
||||||
expect(result).toContain('');
|
expect(result).toContain('');
|
||||||
});
|
});
|
||||||
it('strips images but keeps bold/italic for Api channel', () => {
|
it('strips images but keeps bold/italic for Api channel', () => {
|
||||||
const result = stripUnsupportedSignatureMarkdown(
|
const result = stripUnsupportedMarkdown(richSignature, 'Channel::Api');
|
||||||
richSignature,
|
|
||||||
'Channel::Api'
|
|
||||||
);
|
|
||||||
expect(result).toContain('**Bold**');
|
expect(result).toContain('**Bold**');
|
||||||
expect(result).toContain('_italic_');
|
expect(result).toContain('_italic_');
|
||||||
expect(result).toContain('link'); // link text kept
|
expect(result).toContain('link'); // link text kept
|
||||||
@@ -171,20 +165,14 @@ describe('stripUnsupportedSignatureMarkdown', () => {
|
|||||||
expect(result).not.toContain('; // image removed
|
expect(result).not.toContain('; // image removed
|
||||||
});
|
});
|
||||||
it('strips images but keeps bold/italic/link for Telegram channel', () => {
|
it('strips images but keeps bold/italic/link for Telegram channel', () => {
|
||||||
const result = stripUnsupportedSignatureMarkdown(
|
const result = stripUnsupportedMarkdown(richSignature, 'Channel::Telegram');
|
||||||
richSignature,
|
|
||||||
'Channel::Telegram'
|
|
||||||
);
|
|
||||||
expect(result).toContain('**Bold**');
|
expect(result).toContain('**Bold**');
|
||||||
expect(result).toContain('_italic_');
|
expect(result).toContain('_italic_');
|
||||||
expect(result).toContain('[link](http://example.com)');
|
expect(result).toContain('[link](http://example.com)');
|
||||||
expect(result).not.toContain(';
|
expect(result).not.toContain(';
|
||||||
});
|
});
|
||||||
it('strips all formatting for SMS channel', () => {
|
it('strips all formatting for SMS channel', () => {
|
||||||
const result = stripUnsupportedSignatureMarkdown(
|
const result = stripUnsupportedMarkdown(richSignature, 'Channel::Sms');
|
||||||
richSignature,
|
|
||||||
'Channel::Sms'
|
|
||||||
);
|
|
||||||
expect(result).toContain('Bold');
|
expect(result).toContain('Bold');
|
||||||
expect(result).toContain('italic');
|
expect(result).toContain('italic');
|
||||||
expect(result).toContain('link');
|
expect(result).toContain('link');
|
||||||
@@ -194,8 +182,52 @@ describe('stripUnsupportedSignatureMarkdown', () => {
|
|||||||
expect(result).not.toContain(';
|
expect(result).not.toContain(';
|
||||||
});
|
});
|
||||||
it('returns empty string for empty input', () => {
|
it('returns empty string for empty input', () => {
|
||||||
expect(stripUnsupportedSignatureMarkdown('', 'Channel::Api')).toBe('');
|
expect(stripUnsupportedMarkdown('', 'Channel::Api')).toBe('');
|
||||||
expect(stripUnsupportedSignatureMarkdown(null, 'Channel::Api')).toBe('');
|
expect(stripUnsupportedMarkdown(null, 'Channel::Api')).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with cleanWhitespace parameter', () => {
|
||||||
|
const textWithWhitespace =
|
||||||
|
'**Bold** text\n\nWith multiple\n\nLine breaks\n\n And spaces ';
|
||||||
|
|
||||||
|
it('cleans whitespace when cleanWhitespace=true (default)', () => {
|
||||||
|
const result = stripUnsupportedMarkdown(
|
||||||
|
textWithWhitespace,
|
||||||
|
'Channel::Api',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(result).toBe(
|
||||||
|
'**Bold** text\nWith multiple\nLine breaks\nAnd spaces'
|
||||||
|
);
|
||||||
|
expect(result).not.toContain('\n\n');
|
||||||
|
expect(result).not.toContain(' ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves whitespace when cleanWhitespace=false', () => {
|
||||||
|
const result = stripUnsupportedMarkdown(
|
||||||
|
textWithWhitespace,
|
||||||
|
'Channel::Api',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(result).toContain('\n\n');
|
||||||
|
expect(result).toContain(' And spaces ');
|
||||||
|
expect(result).toBe(
|
||||||
|
'**Bold** text\n\nWith multiple\n\nLine breaks\n\n And spaces '
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips formatting but preserves whitespace for messages', () => {
|
||||||
|
const messageWithFormatting = '**Bold**\n\n`code`\n\nNormal text';
|
||||||
|
const result = stripUnsupportedMarkdown(
|
||||||
|
messageWithFormatting,
|
||||||
|
'Channel::Sms',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(result).toBe('Bold\n\ncode\n\nNormal text');
|
||||||
|
expect(result).toContain('\n\n');
|
||||||
|
expect(result).not.toContain('**');
|
||||||
|
expect(result).not.toContain('`');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ onUnmounted(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<section class="flex flex-col flex-grow w-full h-full overflow-hidden">
|
<section class="flex flex-col flex-grow w-full h-full overflow-hidden">
|
||||||
<div class="w-full max-w-5xl mx-auto z-[60]">
|
<div class="w-full max-w-5xl mx-auto z-30">
|
||||||
<div class="flex flex-col w-full px-4">
|
<div class="flex flex-col w-full px-4">
|
||||||
<SearchHeader
|
<SearchHeader
|
||||||
v-model:filters="filters"
|
v-model:filters="filters"
|
||||||
|
|||||||
Reference in New Issue
Block a user