fix: bubble UI issues (#10608)
This PR has fixes for the following issues - Inconsistent spacing between meta and text in text bubble - Activity bubble overflows for longer text (for now I have truncated it, I'll work with @absurdiya on a better solution) - Ugly lookinh gradient for expand button on email bubble - Email bubble overflow issues and text rendering issues - Alignment for error message - Minute-wise grouping not working - Link color should not be blue - Use `gray-3` for bubble background instead of `gray-4`
This commit is contained in:
@@ -218,9 +218,11 @@ const gridTemplate = computed(() => {
|
||||
const map = {
|
||||
[ORIENTATION.LEFT]: `
|
||||
"avatar bubble"
|
||||
"spacer meta"
|
||||
`,
|
||||
[ORIENTATION.RIGHT]: `
|
||||
"bubble"
|
||||
"meta"
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -399,6 +401,7 @@ provideMessageContext({
|
||||
class="[grid-area:bubble] flex"
|
||||
:class="{
|
||||
'pl-9 justify-end': orientation === ORIENTATION.RIGHT,
|
||||
'min-w-0': variant === MESSAGE_VARIANTS.EMAIL,
|
||||
}"
|
||||
@contextmenu="openContextMenu($event)"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { defineProps, computed } from 'vue';
|
||||
import Message from './Message.vue';
|
||||
import { MESSAGE_TYPES } from './constants.js';
|
||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
|
||||
/**
|
||||
@@ -11,7 +12,7 @@ import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
* @property {Number} currentUserId - ID of the current user
|
||||
* @property {Boolean} isAnEmailChannel - Whether this is an email channel
|
||||
* @property {Object} inboxSupportsReplyTo - Inbox reply support configuration
|
||||
* @property {Array} messages - Array of all messages
|
||||
* @property {Array} messages - Array of all messages [These are not in camelcase]
|
||||
*/
|
||||
const props = defineProps({
|
||||
readMessages: {
|
||||
@@ -51,20 +52,29 @@ const read = computed(() => {
|
||||
/**
|
||||
* Determines if a message should be grouped with the next message
|
||||
* @param {Number} index - Index of the current message
|
||||
* @param {Array} messages - Array of messages to check
|
||||
* @param {Array} searchList - Array of messages to check
|
||||
* @returns {Boolean} - Whether the message should be grouped with next
|
||||
*/
|
||||
const shouldGroupWithNext = (index, messages) => {
|
||||
if (index === messages.length - 1) return false;
|
||||
const shouldGroupWithNext = (index, searchList) => {
|
||||
if (index === searchList.length - 1) return false;
|
||||
|
||||
const current = messages[index];
|
||||
const next = messages[index + 1];
|
||||
const current = searchList[index];
|
||||
const next = searchList[index + 1];
|
||||
|
||||
if (next.status === 'failed') return false;
|
||||
|
||||
const nextSenderId = next.senderId ?? next.sender?.id;
|
||||
const currentSenderId = current.senderId ?? current.sender?.id;
|
||||
if (currentSenderId !== nextSenderId) return false;
|
||||
const hasSameSender = nextSenderId === currentSenderId;
|
||||
|
||||
const nextMessageType = next.messageType;
|
||||
const currentMessageType = current.messageType;
|
||||
|
||||
const areBothTemplates =
|
||||
nextMessageType === MESSAGE_TYPES.TEMPLATE &&
|
||||
currentMessageType === MESSAGE_TYPES.TEMPLATE;
|
||||
|
||||
if (!hasSameSender || !areBothTemplates) return false;
|
||||
|
||||
// Check if messages are in the same minute by rounding down to nearest minute
|
||||
return Math.floor(next.createdAt / 60) === Math.floor(current.createdAt / 60);
|
||||
@@ -101,7 +111,7 @@ const getInReplyToMessage = parentMessage => {
|
||||
v-bind="message"
|
||||
:is-email-inbox="isAnEmailChannel"
|
||||
:in-reply-to="getInReplyToMessage(message)"
|
||||
:group-with-next="shouldGroupWithNext(index, readMessages)"
|
||||
:group-with-next="shouldGroupWithNext(index, read)"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:current-user-id="currentUserId"
|
||||
data-clarity-mask="True"
|
||||
@@ -112,7 +122,7 @@ const getInReplyToMessage = parentMessage => {
|
||||
<Message
|
||||
v-bind="message"
|
||||
:in-reply-to="getInReplyToMessage(message)"
|
||||
:group-with-next="shouldGroupWithNext(index, unReadMessages)"
|
||||
:group-with-next="shouldGroupWithNext(index, unread)"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:current-user-id="currentUserId"
|
||||
:is-email-inbox="isAnEmailChannel"
|
||||
|
||||
@@ -110,7 +110,7 @@ const statusToShow = computed(() => {
|
||||
<template>
|
||||
<div class="text-xs flex items-center gap-1.5">
|
||||
<div class="inline">
|
||||
<span class="inline">{{ readableTime }}</span>
|
||||
<time class="inline">{{ readableTime }}</time>
|
||||
</div>
|
||||
<Icon v-if="isPrivate" icon="i-lucide-lock-keyhole" class="size-3" />
|
||||
<MessageStatus v-if="showStatusIndicator" :status="statusToShow" />
|
||||
|
||||
@@ -16,10 +16,10 @@ const readableTime = computed(() =>
|
||||
class="px-2 py-0.5 !rounded-full flex items-center gap-2"
|
||||
data-bubble-name="activity"
|
||||
>
|
||||
<span v-dompurify-html="content" />
|
||||
<span v-dompurify-html="content" :title="content" class="truncate" />
|
||||
<div v-if="readableTime" class="w-px h-3 rounded-full bg-n-slate-7" />
|
||||
<span class="text-n-slate-10">
|
||||
<time class="text-n-slate-10 truncate flex-shrink" :title="readableTime">
|
||||
{{ readableTime }}
|
||||
</span>
|
||||
</time>
|
||||
</BaseBubble>
|
||||
</template>
|
||||
|
||||
@@ -18,12 +18,12 @@ const varaintBaseMap = {
|
||||
[MESSAGE_VARIANTS.AGENT]: 'bg-n-solid-blue text-n-slate-12',
|
||||
[MESSAGE_VARIANTS.PRIVATE]:
|
||||
'bg-n-solid-amber text-n-amber-12 [&_.prosemirror-mention-node]:font-semibold',
|
||||
[MESSAGE_VARIANTS.USER]: 'bg-n-gray-4 text-n-slate-12',
|
||||
[MESSAGE_VARIANTS.USER]: 'bg-n-gray-3 text-n-slate-12',
|
||||
[MESSAGE_VARIANTS.ACTIVITY]: 'bg-n-alpha-1 text-n-slate-11 text-sm',
|
||||
[MESSAGE_VARIANTS.BOT]: 'bg-n-solid-iris text-n-slate-12',
|
||||
[MESSAGE_VARIANTS.TEMPLATE]: 'bg-n-solid-iris text-n-slate-12',
|
||||
[MESSAGE_VARIANTS.ERROR]: 'bg-n-ruby-4 text-n-ruby-12',
|
||||
[MESSAGE_VARIANTS.EMAIL]: 'bg-n-alpha-2 w-full',
|
||||
[MESSAGE_VARIANTS.EMAIL]: 'bg-n-gray-3 w-full',
|
||||
[MESSAGE_VARIANTS.UNSUPPORTED]:
|
||||
'bg-n-solid-amber/70 border border-dashed border-n-amber-12 text-n-amber-12',
|
||||
};
|
||||
@@ -81,7 +81,7 @@ const previewMessage = computed(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="text-sm min-w-32 break-words"
|
||||
class="text-sm"
|
||||
:class="[
|
||||
messageClass,
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ const showMeta = computed(() => {
|
||||
<template>
|
||||
<section
|
||||
v-show="showMeta"
|
||||
class="p-4 space-y-1 pr-9 border-b border-n-strong"
|
||||
class="space-y-1 pr-9 border-b border-n-strong text-sm"
|
||||
:class="hasError ? 'text-n-ruby-11' : 'text-n-slate-11'"
|
||||
>
|
||||
<template v-if="showMeta">
|
||||
|
||||
@@ -48,59 +48,62 @@ const textToShow = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble class="w-full overflow-hidden" data-bubble-name="email">
|
||||
<EmailMeta />
|
||||
<section
|
||||
ref="contentContainer"
|
||||
class="p-4"
|
||||
:class="{
|
||||
'max-h-[400px] overflow-hidden relative': !isExpanded && isExpandable,
|
||||
}"
|
||||
>
|
||||
<BaseBubble class="w-full" data-bubble-name="email">
|
||||
<EmailMeta class="p-3" />
|
||||
<section ref="contentContainer" class="p-3">
|
||||
<div
|
||||
v-if="isExpandable && !isExpanded"
|
||||
class="absolute left-0 right-0 bottom-0 h-40 p-8 flex items-end bg-gradient-to-t dark:from-[#24252b] from-[#F5F5F6] dark:via-[rgba(36,37,43,0.5)] via-[rgba(245,245,246,0.50)] dark:to-transparent to-[rgba(245,245,246,0.00)]"
|
||||
:class="{
|
||||
'max-h-[400px] overflow-hidden relative': !isExpanded && isExpandable,
|
||||
'overflow-y-scroll relative': isExpanded,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
class="text-n-slate-12 py-2 px-8 mx-auto text-center flex items-center gap-2"
|
||||
@click="isExpanded = true"
|
||||
<div
|
||||
v-if="isExpandable && !isExpanded"
|
||||
class="absolute left-0 right-0 bottom-0 h-40 px-8 flex items-end bg-gradient-to-t from-n-gray-3 via-n-gray-3 via-20% to-transparent"
|
||||
>
|
||||
<Icon icon="i-lucide-maximize-2" />
|
||||
{{ $t('EMAIL_HEADER.EXPAND') }}
|
||||
<button
|
||||
class="text-n-slate-12 py-2 px-8 mx-auto text-center flex items-center gap-2"
|
||||
@click="isExpanded = true"
|
||||
>
|
||||
<Icon icon="i-lucide-maximize-2" />
|
||||
{{ $t('EMAIL_HEADER.EXPAND') }}
|
||||
</button>
|
||||
</div>
|
||||
<FormattedContent v-if="isOutgoing && content" :content="content" />
|
||||
<template v-else>
|
||||
<Letter
|
||||
v-if="showQuotedMessage"
|
||||
class-name="prose prose-email !max-w-none"
|
||||
:html="fullHTML"
|
||||
:text="textToShow"
|
||||
/>
|
||||
<Letter
|
||||
v-else
|
||||
class-name="prose prose-email !max-w-none"
|
||||
:html="unquotedHTML"
|
||||
:text="textToShow"
|
||||
/>
|
||||
</template>
|
||||
<button
|
||||
v-if="hasQuotedMessage"
|
||||
class="text-n-slate-11 px-1 leading-none text-sm bg-n-alpha-black2 text-center flex items-center gap-1 mt-2"
|
||||
@click="showQuotedMessage = !showQuotedMessage"
|
||||
>
|
||||
<template v-if="showQuotedMessage">
|
||||
{{ $t('CHAT_LIST.HIDE_QUOTED_TEXT') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('CHAT_LIST.SHOW_QUOTED_TEXT') }}
|
||||
</template>
|
||||
<Icon
|
||||
:icon="
|
||||
showQuotedMessage
|
||||
? 'i-lucide-chevron-up'
|
||||
: 'i-lucide-chevron-down'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<FormattedContent v-if="isOutgoing && content" :content="content" />
|
||||
<template v-else>
|
||||
<Letter
|
||||
v-if="showQuotedMessage"
|
||||
class-name="prose prose-email !max-w-none"
|
||||
:html="fullHTML"
|
||||
:text="textToShow"
|
||||
/>
|
||||
<Letter
|
||||
v-else
|
||||
class-name="prose prose-email !max-w-none"
|
||||
:html="unquotedHTML"
|
||||
:text="textToShow"
|
||||
/>
|
||||
</template>
|
||||
<button
|
||||
v-if="hasQuotedMessage"
|
||||
class="text-n-slate-11 px-1 leading-none text-sm bg-n-alpha-black2 text-center flex items-center gap-1 mt-2"
|
||||
@click="showQuotedMessage = !showQuotedMessage"
|
||||
>
|
||||
<template v-if="showQuotedMessage">
|
||||
{{ $t('CHAT_LIST.HIDE_QUOTED_TEXT') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('CHAT_LIST.SHOW_QUOTED_TEXT') }}
|
||||
</template>
|
||||
<Icon
|
||||
:icon="
|
||||
showQuotedMessage ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
</section>
|
||||
<section v-if="attachments.length" class="px-4 pb-4 space-y-2">
|
||||
<AttachmentChips :attachments="attachments" class="gap-1" />
|
||||
|
||||
@@ -26,6 +26,6 @@ const formattedContent = computed(() => {
|
||||
<template>
|
||||
<span
|
||||
v-dompurify-html="formattedContent"
|
||||
class="[&>p:last-child]:mb-0 [&>ul]:list-inside"
|
||||
class="[&_.link]:text-n-slate-11 [&_.link]:underline [&>p:last-child]:mb-0 [&>ul]:list-inside"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -19,20 +19,22 @@ const isEmpty = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble class="flex flex-col gap-3 px-4 py-3" data-bubble-name="text">
|
||||
<span v-if="isEmpty" class="text-n-slate-11">
|
||||
{{ $t('CONVERSATION.NO_CONTENT') }}
|
||||
</span>
|
||||
<FormattedContent v-if="content" :content="content" />
|
||||
<AttachmentChips :attachments="attachments" class="gap-2" />
|
||||
<template v-if="isTemplate">
|
||||
<div
|
||||
v-if="contentAttributes.submittedEmail"
|
||||
class="px-2 py-1 rounded-lg bg-n-alpha-3"
|
||||
>
|
||||
{{ contentAttributes.submittedEmail }}
|
||||
</div>
|
||||
</template>
|
||||
<BaseBubble class="px-4 py-3" data-bubble-name="text">
|
||||
<div class="gap-3 flex flex-col">
|
||||
<span v-if="isEmpty" class="text-n-slate-11">
|
||||
{{ $t('CONVERSATION.NO_CONTENT') }}
|
||||
</span>
|
||||
<FormattedContent v-if="content" :content="content" />
|
||||
<AttachmentChips :attachments="attachments" class="gap-2" />
|
||||
<template v-if="isTemplate">
|
||||
<div
|
||||
v-if="contentAttributes.submittedEmail"
|
||||
class="px-2 py-1 rounded-lg bg-n-alpha-3"
|
||||
>
|
||||
{{ contentAttributes.submittedEmail }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</BaseBubble>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ const tailwindConfig = {
|
||||
typography: {
|
||||
email: {
|
||||
css: {
|
||||
color: 'rgb(var(--slate-11))',
|
||||
color: 'rgb(var(--slate-12))',
|
||||
lineHeight: '1.6',
|
||||
fontSize: '14px',
|
||||
'*': {
|
||||
@@ -129,16 +129,16 @@ const tailwindConfig = {
|
||||
th: {
|
||||
padding: '0.75em',
|
||||
color: 'rgb(var(--slate-12))',
|
||||
borderBottom: `1px solid rgb(var(--border-strong))`,
|
||||
border: `none`,
|
||||
textAlign: 'left',
|
||||
fontWeight: '600',
|
||||
},
|
||||
tr: {
|
||||
borderBottom: `1px solid rgb(var(--border-strong))`,
|
||||
border: `none`,
|
||||
},
|
||||
td: {
|
||||
padding: '0.75em',
|
||||
borderBottom: `1px solid rgb(var(--border-strong))`,
|
||||
border: `none`,
|
||||
},
|
||||
img: {
|
||||
maxWidth: '100%',
|
||||
|
||||
Reference in New Issue
Block a user