mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 10:12:34 +00:00
feat: integrate new bubbles (#10550)
To test this, set the `useNextBubble` value to `true` in the
localstorage. Here's a quick command to run in the console
```js
localStorage.setItem('useNextBubble', true)
```
```js
localStorage.setItem('useNextBubble', false)
```
---------
Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
@@ -124,6 +124,19 @@
|
||||
--teal-11: 0 133 115;
|
||||
--teal-12: 13 61 56;
|
||||
|
||||
--gray-1: 252 252 252;
|
||||
--gray-2: 249 249 249;
|
||||
--gray-3: 240 240 240;
|
||||
--gray-4: 232 232 232;
|
||||
--gray-5: 224 224 224;
|
||||
--gray-6: 217 217 217;
|
||||
--gray-7: 206 206 206;
|
||||
--gray-8: 187 187 187;
|
||||
--gray-9: 141 141 141;
|
||||
--gray-10: 131 131 131;
|
||||
--gray-11: 100 100 100;
|
||||
--gray-12: 32 32 32;
|
||||
|
||||
--background-color: 253 253 253;
|
||||
--text-blue: 8 109 224;
|
||||
--border-container: 236 236 236;
|
||||
@@ -213,6 +226,19 @@
|
||||
--teal-11: 11 216 182;
|
||||
--teal-12: 173 240 221;
|
||||
|
||||
--gray-1: 17 17 17;
|
||||
--gray-2: 25 25 25;
|
||||
--gray-3: 34 34 34;
|
||||
--gray-4: 42 42 42;
|
||||
--gray-5: 49 49 49;
|
||||
--gray-6: 58 58 58;
|
||||
--gray-7: 72 72 72;
|
||||
--gray-8: 96 96 96;
|
||||
--gray-9: 110 110 110;
|
||||
--gray-10: 123 123 123;
|
||||
--gray-11: 180 180 180;
|
||||
--gray-12: 238 238 238;
|
||||
|
||||
--background-color: 18 18 19;
|
||||
--border-strong: 52 52 52;
|
||||
--border-weak: 38 38 42;
|
||||
@@ -232,7 +258,7 @@
|
||||
--black-alpha-2: 0, 0, 0, 0.2;
|
||||
--border-blue: 39, 129, 246, 0.5;
|
||||
--border-container: 236, 236, 236, 0;
|
||||
--white-alpha: 255, 255, 255, 0.8;
|
||||
--white-alpha: 255, 255, 255, 0.1;
|
||||
}
|
||||
/* NEXT COLORS END */
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<script setup>
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { computed, ref, toRefs } from 'vue';
|
||||
import { provideMessageContext } from './provider.js';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { LocalStorage } from 'shared/helpers/localStorage';
|
||||
import { ACCOUNT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import {
|
||||
MESSAGE_TYPES,
|
||||
ATTACHMENT_TYPES,
|
||||
@@ -8,6 +14,7 @@ import {
|
||||
SENDER_TYPES,
|
||||
ORIENTATION,
|
||||
MESSAGE_STATUS,
|
||||
CONTENT_TYPES,
|
||||
} from './constants';
|
||||
|
||||
import Avatar from 'next/avatar/Avatar.vue';
|
||||
@@ -19,17 +26,14 @@ import FileBubble from './bubbles/File.vue';
|
||||
import AudioBubble from './bubbles/Audio.vue';
|
||||
import VideoBubble from './bubbles/Video.vue';
|
||||
import InstagramStoryBubble from './bubbles/InstagramStory.vue';
|
||||
import AttachmentsBubble from './bubbles/Attachments.vue';
|
||||
import EmailBubble from './bubbles/Email/Index.vue';
|
||||
import UnsupportedBubble from './bubbles/Unsupported.vue';
|
||||
import ContactBubble from './bubbles/Contact.vue';
|
||||
import DyteBubble from './bubbles/Dyte.vue';
|
||||
const LocationBubble = defineAsyncComponent(
|
||||
() => import('./bubbles/Location.vue')
|
||||
);
|
||||
import LocationBubble from './bubbles/Location.vue';
|
||||
|
||||
import MessageError from './MessageError.vue';
|
||||
import MessageMeta from './MessageMeta.vue';
|
||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
@@ -65,7 +69,7 @@ import MessageMeta from './MessageMeta.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {('sent'|'delivered'|'read'|'failed')} status - The delivery status of the message
|
||||
* @property {('sent'|'delivered'|'read'|'failed'|'progress')} status - The delivery status of the message
|
||||
* @property {ContentAttributes} [contentAttributes={}] - Additional attributes of the message content
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
* @property {Sender|null} [sender=null] - The sender information
|
||||
@@ -78,6 +82,11 @@ import MessageMeta from './MessageMeta.vue';
|
||||
* @property {string|null} [error=null] - Error message if the message failed to send
|
||||
* @property {string|null} [senderType=null] - The type of the sender
|
||||
* @property {string} content - The message content
|
||||
* @property {boolean} [groupWithNext=false] - Whether the message should be grouped with the next message
|
||||
* @property {Object|null} [inReplyTo=null] - The message to which this message is a reply
|
||||
* @property {boolean} [isEmailInbox=false] - Whether the message is from an email inbox
|
||||
* @property {number} conversationId - The ID of the conversation to which the message belongs
|
||||
* @property {number} inboxId - The ID of the inbox to which the message belongs
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line vue/define-macros-order
|
||||
@@ -93,70 +102,51 @@ const props = defineProps({
|
||||
required: true,
|
||||
validator: value => Object.values(MESSAGE_STATUS).includes(value),
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
private: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
sender: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
senderId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
senderType: {
|
||||
attachments: { type: Array, default: () => [] },
|
||||
content: { type: String, default: null },
|
||||
contentAttributes: { type: Object, default: () => ({}) },
|
||||
contentType: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contentAttributes: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
currentUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
groupWithNext: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
inReplyTo: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
isEmailInbox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
default: 'text',
|
||||
validator: value => Object.values(CONTENT_TYPES).includes(value),
|
||||
},
|
||||
conversationId: { type: Number, required: true },
|
||||
createdAt: { type: Number, required: true }, // eslint-disable-line vue/no-unused-properties
|
||||
currentUserId: { type: Number, required: true },
|
||||
groupWithNext: { type: Boolean, default: false },
|
||||
inboxId: { type: Number, required: true }, // eslint-disable-line vue/no-unused-properties
|
||||
inboxSupportsReplyTo: { type: Object, default: () => ({}) },
|
||||
inReplyTo: { type: Object, default: null }, // eslint-disable-line vue/no-unused-properties
|
||||
isEmailInbox: { type: Boolean, default: false },
|
||||
private: { type: Boolean, default: false },
|
||||
sender: { type: Object, default: null },
|
||||
senderId: { type: Number, default: null },
|
||||
senderType: { type: String, default: null },
|
||||
sourceId: { type: String, default: '' }, // eslint-disable-line vue/no-unused-properties
|
||||
});
|
||||
|
||||
const contextMenuPosition = ref({});
|
||||
const showContextMenu = ref(false);
|
||||
/**
|
||||
* Computes the message variant based on props
|
||||
* @type {import('vue').ComputedRef<'user'|'agent'|'activity'|'private'|'bot'|'template'>}
|
||||
*/
|
||||
const variant = computed(() => {
|
||||
if (props.private) return MESSAGE_VARIANTS.PRIVATE;
|
||||
|
||||
if (props.isEmailInbox) {
|
||||
const emailInboxTypes = [MESSAGE_TYPES.INCOMING, MESSAGE_TYPES.OUTGOING];
|
||||
if (emailInboxTypes.includes(props.messageType)) {
|
||||
return MESSAGE_VARIANTS.EMAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.contentType === CONTENT_TYPES.INCOMING_EMAIL) {
|
||||
return MESSAGE_VARIANTS.EMAIL;
|
||||
}
|
||||
|
||||
if (props.status === MESSAGE_STATUS.FAILED) return MESSAGE_VARIANTS.ERROR;
|
||||
if (props.contentAttributes.isUnsupported)
|
||||
if (props.contentAttributes?.isUnsupported)
|
||||
return MESSAGE_VARIANTS.UNSUPPORTED;
|
||||
|
||||
const variants = {
|
||||
@@ -170,10 +160,20 @@ const variant = computed(() => {
|
||||
});
|
||||
|
||||
const isMyMessage = computed(() => {
|
||||
// if an outgoing message is still processing, then it's definitely a
|
||||
// message sent by the current user
|
||||
if (
|
||||
props.status === MESSAGE_STATUS.PROGRESS &&
|
||||
props.messageType === MESSAGE_TYPES.OUTGOING
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const senderId = props.senderId ?? props.sender?.id;
|
||||
const senderType = props.senderType ?? props.sender?.type;
|
||||
|
||||
if (!senderType || !senderId) return false;
|
||||
if (!senderType || !senderId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
senderType.toLowerCase() === SENDER_TYPES.USER.toLowerCase() &&
|
||||
@@ -218,11 +218,9 @@ const gridTemplate = computed(() => {
|
||||
const map = {
|
||||
[ORIENTATION.LEFT]: `
|
||||
"avatar bubble"
|
||||
"spacer meta"
|
||||
`,
|
||||
[ORIENTATION.RIGHT]: `
|
||||
"bubble"
|
||||
"meta"
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -248,7 +246,11 @@ const componentToRender = computed(() => {
|
||||
if (emailInboxTypes.includes(props.messageType)) return EmailBubble;
|
||||
}
|
||||
|
||||
if (props.contentAttributes.isUnsupported) {
|
||||
if (props.contentType === CONTENT_TYPES.INCOMING_EMAIL) {
|
||||
return EmailBubble;
|
||||
}
|
||||
|
||||
if (props.contentAttributes?.isUnsupported) {
|
||||
return UnsupportedBubble;
|
||||
}
|
||||
|
||||
@@ -260,7 +262,7 @@ const componentToRender = computed(() => {
|
||||
return InstagramStoryBubble;
|
||||
}
|
||||
|
||||
if (props.attachments.length === 1) {
|
||||
if (Array.isArray(props.attachments) && props.attachments.length === 1) {
|
||||
const fileType = props.attachments[0].fileType;
|
||||
|
||||
if (!props.content) {
|
||||
@@ -275,23 +277,93 @@ const componentToRender = computed(() => {
|
||||
if (fileType === ATTACHMENT_TYPES.CONTACT) return ContactBubble;
|
||||
}
|
||||
|
||||
if (props.attachments.length > 1 && !props.content) {
|
||||
return AttachmentsBubble;
|
||||
}
|
||||
|
||||
return TextBubble;
|
||||
});
|
||||
|
||||
const shouldShowContextMenu = computed(() => {
|
||||
return !(
|
||||
props.status === MESSAGE_STATUS.FAILED ||
|
||||
props.status === MESSAGE_STATUS.PROGRESS ||
|
||||
props.contentAttributes?.isUnsupported
|
||||
);
|
||||
});
|
||||
|
||||
const isBubble = computed(() => {
|
||||
return props.messageType !== MESSAGE_TYPES.ACTIVITY;
|
||||
});
|
||||
|
||||
const isMessageDeleted = computed(() => {
|
||||
return props.contentAttributes?.deleted;
|
||||
});
|
||||
|
||||
const payloadForContextMenu = computed(() => {
|
||||
return {
|
||||
id: props.id,
|
||||
content_attributes: props.contentAttributes,
|
||||
content: props.content,
|
||||
conversation_id: props.conversationId,
|
||||
};
|
||||
});
|
||||
|
||||
const contextMenuEnabledOptions = computed(() => {
|
||||
const hasText = !!props.content;
|
||||
const hasAttachments = !!(props.attachments && props.attachments.length > 0);
|
||||
|
||||
const isOutgoing = props.messageType === MESSAGE_TYPES.OUTGOING;
|
||||
|
||||
return {
|
||||
copy: hasText,
|
||||
delete: hasText || hasAttachments,
|
||||
cannedResponse: isOutgoing && hasText,
|
||||
replyTo: !props.private && props.inboxSupportsReplyTo.outgoing,
|
||||
};
|
||||
});
|
||||
|
||||
function openContextMenu(e) {
|
||||
const shouldSkipContextMenu =
|
||||
e.target?.classList.contains('skip-context-menu') ||
|
||||
e.target?.tagName.toLowerCase() === 'a';
|
||||
if (shouldSkipContextMenu || getSelection().toString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
if (e.type === 'contextmenu') {
|
||||
useTrack(ACCOUNT_EVENTS.OPEN_MESSAGE_CONTEXT_MENU);
|
||||
}
|
||||
contextMenuPosition.value = {
|
||||
x: e.pageX || e.clientX,
|
||||
y: e.pageY || e.clientY,
|
||||
};
|
||||
showContextMenu.value = true;
|
||||
}
|
||||
|
||||
function closeContextMenu() {
|
||||
showContextMenu.value = false;
|
||||
contextMenuPosition.value = { x: null, y: null };
|
||||
}
|
||||
|
||||
function handleReplyTo() {
|
||||
const replyStorageKey = LOCAL_STORAGE_KEYS.MESSAGE_REPLY_TO;
|
||||
const { conversationId, id: replyTo } = props;
|
||||
|
||||
LocalStorage.updateJsonStore(replyStorageKey, conversationId, replyTo);
|
||||
emitter.emit(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, props);
|
||||
}
|
||||
|
||||
provideMessageContext({
|
||||
...toRefs(props),
|
||||
isPrivate: computed(() => props.private),
|
||||
variant,
|
||||
inReplyTo: props.inReplyTo,
|
||||
orientation,
|
||||
isMyMessage,
|
||||
shouldGroupWithNext,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:id="`message${props.id}`"
|
||||
class="flex w-full"
|
||||
:data-message-id="props.id"
|
||||
:class="[flexOrientationClass, shouldGroupWithNext ? 'mb-2' : 'mb-4']"
|
||||
@@ -324,12 +396,13 @@ provideMessageContext({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="[grid-area:bubble]"
|
||||
class="[grid-area:bubble] flex"
|
||||
:class="{
|
||||
'pl-9': ORIENTATION.RIGHT === orientation,
|
||||
'pl-9 justify-end': orientation === ORIENTATION.RIGHT,
|
||||
}"
|
||||
@contextmenu="openContextMenu($event)"
|
||||
>
|
||||
<Component :is="componentToRender" v-bind="props" />
|
||||
<Component :is="componentToRender" />
|
||||
</div>
|
||||
<MessageError
|
||||
v-if="contentAttributes.externalError"
|
||||
@@ -337,15 +410,18 @@ provideMessageContext({
|
||||
:class="flexOrientationClass"
|
||||
:error="contentAttributes.externalError"
|
||||
/>
|
||||
<MessageMeta
|
||||
v-else-if="!shouldGroupWithNext"
|
||||
class="[grid-area:meta]"
|
||||
:class="flexOrientationClass"
|
||||
:sender="props.sender"
|
||||
:status="props.status"
|
||||
:private="props.private"
|
||||
:is-my-message="isMyMessage"
|
||||
:created-at="props.createdAt"
|
||||
</div>
|
||||
<div v-if="shouldShowContextMenu" class="context-menu-wrap">
|
||||
<ContextMenu
|
||||
v-if="isBubble && !isMessageDeleted"
|
||||
:context-menu-position="contextMenuPosition"
|
||||
:is-open="showContextMenu"
|
||||
:enabled-options="contextMenuEnabledOptions"
|
||||
:message="payloadForContextMenu"
|
||||
hide-button
|
||||
@open="openContextMenu"
|
||||
@close="closeContextMenu"
|
||||
@reply-to="handleReplyTo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
124
app/javascript/dashboard/components-next/message/MessageList.vue
Normal file
124
app/javascript/dashboard/components-next/message/MessageList.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<script setup>
|
||||
import { defineProps, computed } from 'vue';
|
||||
import Message from './Message.vue';
|
||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
|
||||
/**
|
||||
* Props definition for the component
|
||||
* @typedef {Object} Props
|
||||
* @property {Array} readMessages - Array of read messages
|
||||
* @property {Array} unReadMessages - Array of unread messages
|
||||
* @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
|
||||
*/
|
||||
const props = defineProps({
|
||||
readMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
unReadMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
currentUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isAnEmailChannel: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
inboxSupportsReplyTo: {
|
||||
type: Object,
|
||||
default: () => ({ incoming: false, outgoing: false }),
|
||||
},
|
||||
messages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const unread = computed(() => {
|
||||
return useCamelCase(props.unReadMessages, { deep: true });
|
||||
});
|
||||
|
||||
const read = computed(() => {
|
||||
return useCamelCase(props.readMessages, { deep: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returns {Boolean} - Whether the message should be grouped with next
|
||||
*/
|
||||
const shouldGroupWithNext = (index, messages) => {
|
||||
if (index === messages.length - 1) return false;
|
||||
|
||||
const current = messages[index];
|
||||
const next = messages[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;
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the message that was replied to
|
||||
* @param {Object} parentMessage - The message containing the reply reference
|
||||
* @returns {Object|null} - The message being replied to, or null if not found
|
||||
*/
|
||||
const getInReplyToMessage = parentMessage => {
|
||||
if (!parentMessage) return null;
|
||||
|
||||
const inReplyToMessageId =
|
||||
parentMessage.contentAttributes?.inReplyTo ??
|
||||
parentMessage.content_attributes?.in_reply_to;
|
||||
|
||||
if (!inReplyToMessageId) return null;
|
||||
|
||||
// Find in-reply-to message in the messages prop
|
||||
const replyMessage = props.messages?.find(
|
||||
message => message.id === inReplyToMessageId
|
||||
);
|
||||
|
||||
return replyMessage ? useCamelCase(replyMessage) : null;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="px-4 bg-n-background">
|
||||
<slot name="beforeAll" />
|
||||
<template v-for="(message, index) in read" :key="message.id">
|
||||
<Message
|
||||
v-bind="message"
|
||||
:is-email-inbox="isAnEmailChannel"
|
||||
:in-reply-to="getInReplyToMessage(message)"
|
||||
:group-with-next="shouldGroupWithNext(index, readMessages)"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:current-user-id="currentUserId"
|
||||
data-clarity-mask="True"
|
||||
/>
|
||||
</template>
|
||||
<slot name="beforeUnread" />
|
||||
<template v-for="(message, index) in unread" :key="message.id">
|
||||
<Message
|
||||
v-bind="message"
|
||||
:in-reply-to="getInReplyToMessage(message)"
|
||||
:group-with-next="shouldGroupWithNext(index, unReadMessages)"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:current-user-id="currentUserId"
|
||||
:is-email-inbox="isAnEmailChannel"
|
||||
data-clarity-mask="True"
|
||||
/>
|
||||
</template>
|
||||
<slot name="after" />
|
||||
</ul>
|
||||
</template>
|
||||
@@ -4,74 +4,116 @@ import { messageTimestamp } from 'shared/helpers/timeHelper';
|
||||
|
||||
import MessageStatus from './MessageStatus.vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import { useInbox } from 'dashboard/composables/useInbox';
|
||||
import { useMessageContext } from './provider.js';
|
||||
|
||||
import { MESSAGE_STATUS } from './constants';
|
||||
import { MESSAGE_STATUS, MESSAGE_TYPES } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Sender
|
||||
* @property {Object} additional_attributes - Additional attributes of the sender
|
||||
* @property {Object} custom_attributes - Custom attributes of the sender
|
||||
* @property {string} email - Email of the sender
|
||||
* @property {number} id - ID of the sender
|
||||
* @property {string|null} identifier - Identifier of the sender
|
||||
* @property {string} name - Name of the sender
|
||||
* @property {string|null} phone_number - Phone number of the sender
|
||||
* @property {string} thumbnail - Thumbnail URL of the sender
|
||||
* @property {string} type - Type of sender
|
||||
*/
|
||||
const {
|
||||
isAFacebookInbox,
|
||||
isALineChannel,
|
||||
isAPIInbox,
|
||||
isASmsInbox,
|
||||
isATelegramChannel,
|
||||
isATwilioChannel,
|
||||
isAWebWidgetInbox,
|
||||
isAWhatsAppChannel,
|
||||
isAnEmailChannel,
|
||||
} = useInbox();
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {('sent'|'delivered'|'read'|'failed')} status - The delivery status of the message
|
||||
* @property {boolean} [private=false] - Whether the message is private
|
||||
* @property {isMyMessage} [private=false] - Whether the message is sent by the current user or not
|
||||
* @property {number} createdAt - Timestamp when the message was created
|
||||
* @property {Sender|null} [sender=null] - The sender information
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
sender: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => Object.values(MESSAGE_STATUS).includes(value),
|
||||
},
|
||||
private: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isMyMessage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
createdAt: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const { status, isPrivate, createdAt, sourceId, messageType } =
|
||||
useMessageContext();
|
||||
|
||||
const readableTime = computed(() =>
|
||||
messageTimestamp(props.createdAt, 'LLL d, h:mm a')
|
||||
messageTimestamp(createdAt.value, 'LLL d, h:mm a')
|
||||
);
|
||||
|
||||
const showSender = computed(() => !props.isMyMessage && props.sender);
|
||||
const showStatusIndicator = computed(() => {
|
||||
if (isPrivate.value) return false;
|
||||
if (messageType.value === MESSAGE_TYPES.OUTGOING) return true;
|
||||
if (messageType.value === MESSAGE_TYPES.TEMPLATE) return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const isSent = computed(() => {
|
||||
if (!showStatusIndicator.value) return false;
|
||||
|
||||
// Messages will be marked as sent for the Email channel if they have a source ID.
|
||||
if (isAnEmailChannel.value) return !!sourceId.value;
|
||||
|
||||
if (
|
||||
isAWhatsAppChannel.value ||
|
||||
isATwilioChannel.value ||
|
||||
isAFacebookInbox.value ||
|
||||
isASmsInbox.value ||
|
||||
isATelegramChannel.value
|
||||
) {
|
||||
return sourceId.value && status.value === MESSAGE_STATUS.SENT;
|
||||
}
|
||||
|
||||
// All messages will be mark as sent for the Line channel, as there is no source ID.
|
||||
if (isALineChannel.value) return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const isDelivered = computed(() => {
|
||||
if (!showStatusIndicator.value) return false;
|
||||
|
||||
if (
|
||||
isAWhatsAppChannel.value ||
|
||||
isATwilioChannel.value ||
|
||||
isASmsInbox.value ||
|
||||
isAFacebookInbox.value
|
||||
) {
|
||||
return sourceId.value && status.value === MESSAGE_STATUS.DELIVERED;
|
||||
}
|
||||
// All messages marked as delivered for the web widget inbox and API inbox once they are sent.
|
||||
if (isAWebWidgetInbox.value || isAPIInbox.value) {
|
||||
return status.value === MESSAGE_STATUS.SENT;
|
||||
}
|
||||
if (isALineChannel.value) {
|
||||
return status.value === MESSAGE_STATUS.DELIVERED;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const isRead = computed(() => {
|
||||
if (!showStatusIndicator.value) return false;
|
||||
|
||||
if (
|
||||
isAWhatsAppChannel.value ||
|
||||
isATwilioChannel.value ||
|
||||
isAFacebookInbox.value
|
||||
) {
|
||||
return sourceId.value && status.value === MESSAGE_STATUS.READ;
|
||||
}
|
||||
|
||||
if (isAWebWidgetInbox.value || isAPIInbox.value) {
|
||||
return status.value === MESSAGE_STATUS.READ;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const statusToShow = computed(() => {
|
||||
if (isRead.value) return MESSAGE_STATUS.READ;
|
||||
if (isDelivered.value) return MESSAGE_STATUS.DELIVERED;
|
||||
if (isSent.value) return MESSAGE_STATUS.SENT;
|
||||
|
||||
return MESSAGE_STATUS.PROGRESS;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-xs text-n-slate-11 flex items-center gap-1.5">
|
||||
<div class="text-xs flex items-center gap-1.5">
|
||||
<div class="inline">
|
||||
<span v-if="showSender" class="inline capitalize">{{ sender.name }}</span>
|
||||
<span v-if="showSender && readableTime" class="inline"> • </span>
|
||||
<span class="inline">{{ readableTime }}</span>
|
||||
</div>
|
||||
<Icon
|
||||
v-if="props.private"
|
||||
icon="i-lucide-lock-keyhole"
|
||||
class="text-n-slate-10 size-3"
|
||||
/>
|
||||
<MessageStatus v-if="props.isMyMessage" :status />
|
||||
<Icon v-if="isPrivate" icon="i-lucide-lock-keyhole" class="size-3" />
|
||||
<MessageStatus v-if="showStatusIndicator" :status="statusToShow" />
|
||||
</div>
|
||||
</template>
|
||||
`
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { messageTimestamp } from 'shared/helpers/timeHelper';
|
||||
import BaseBubble from './Base.vue';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
|
||||
defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const { content, createdAt } = useMessageContext();
|
||||
|
||||
const readableTime = computed(() =>
|
||||
messageTimestamp(createdAt.value, 'LLL d, h:mm a')
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble class="px-2 py-0.5" data-bubble-name="activity">
|
||||
<BaseBubble
|
||||
class="px-2 py-0.5 !rounded-full flex items-center gap-2"
|
||||
data-bubble-name="activity"
|
||||
>
|
||||
<span v-dompurify-html="content" />
|
||||
<div v-if="readableTime" class="w-px h-3 rounded-full bg-n-slate-7" />
|
||||
<span class="text-n-slate-10">
|
||||
{{ readableTime }}
|
||||
</span>
|
||||
</BaseBubble>
|
||||
</template>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<script setup>
|
||||
import BaseBubble from 'next/message/bubbles/Base.vue';
|
||||
import AttachmentChips from 'next/message/chips/AttachmentChips.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble class="grid gap-2 bg-transparent" data-bubble-name="attachments">
|
||||
<AttachmentChips :attachments="attachments" class="gap-1" />
|
||||
</BaseBubble>
|
||||
</template>
|
||||
@@ -2,43 +2,17 @@
|
||||
import { computed } from 'vue';
|
||||
import BaseBubble from './Base.vue';
|
||||
import AudioChip from 'next/message/chips/Audio.vue';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const { attachments } = useMessageContext();
|
||||
|
||||
const attachment = computed(() => {
|
||||
return props.attachments[0];
|
||||
return attachments.value[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble class="bg-transparent" data-bubble-name="audio">
|
||||
<AudioChip
|
||||
:attachment="attachment"
|
||||
class="p-2 text-n-slate-12 bg-n-alpha-3"
|
||||
/>
|
||||
<AudioChip :attachment="attachment" class="p-2 text-n-slate-12" />
|
||||
</BaseBubble>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import MessageMeta from '../MessageMeta.vue';
|
||||
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -8,14 +10,15 @@ import { useI18n } from 'vue-i18n';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { MESSAGE_VARIANTS, ORIENTATION } from '../constants';
|
||||
|
||||
const { variant, orientation, inReplyTo } = useMessageContext();
|
||||
const { variant, orientation, inReplyTo, shouldGroupWithNext } =
|
||||
useMessageContext();
|
||||
const { t } = useI18n();
|
||||
|
||||
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-slate-4 text-n-slate-12',
|
||||
[MESSAGE_VARIANTS.USER]: 'bg-n-gray-4 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',
|
||||
@@ -31,6 +34,16 @@ const orientationMap = {
|
||||
[ORIENTATION.CENTER]: 'rounded-md',
|
||||
};
|
||||
|
||||
const flexOrientationClass = computed(() => {
|
||||
const map = {
|
||||
[ORIENTATION.LEFT]: 'justify-start',
|
||||
[ORIENTATION.RIGHT]: 'justify-end',
|
||||
[ORIENTATION.CENTER]: 'justify-center',
|
||||
};
|
||||
|
||||
return map[orientation.value];
|
||||
});
|
||||
|
||||
const messageClass = computed(() => {
|
||||
const classToApply = [varaintBaseMap[variant.value]];
|
||||
|
||||
@@ -72,7 +85,7 @@ const previewMessage = computed(() => {
|
||||
:class="[
|
||||
messageClass,
|
||||
{
|
||||
'max-w-md': variant !== MESSAGE_VARIANTS.EMAIL,
|
||||
'max-w-lg': variant !== MESSAGE_VARIANTS.EMAIL,
|
||||
},
|
||||
]"
|
||||
>
|
||||
@@ -86,5 +99,16 @@ const previewMessage = computed(() => {
|
||||
</span>
|
||||
</div>
|
||||
<slot />
|
||||
<MessageMeta
|
||||
v-if="!shouldGroupWithNext && variant !== MESSAGE_VARIANTS.ACTIVITY"
|
||||
:class="[
|
||||
flexOrientationClass,
|
||||
variant === MESSAGE_VARIANTS.EMAIL ? 'px-3 pb-3' : '',
|
||||
variant === MESSAGE_VARIANTS.PRIVATE
|
||||
? 'text-n-amber-12/50'
|
||||
: 'text-n-slate-11',
|
||||
]"
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,11 +3,11 @@ import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import BaseBubble from './Base.vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
icon: { type: [String, Object], required: true },
|
||||
iconBgColor: { type: String, default: 'bg-n-alpha-3' },
|
||||
sender: { type: Object, default: () => ({}) },
|
||||
senderTranslationKey: { type: String, required: true },
|
||||
content: { type: String, required: true },
|
||||
action: {
|
||||
@@ -19,60 +19,62 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const { sender } = useMessageContext();
|
||||
const { t } = useI18n();
|
||||
|
||||
const senderName = computed(() => {
|
||||
return props.sender.name;
|
||||
return sender?.value.name;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble
|
||||
class="overflow-hidden grid gap-4 min-w-64 p-0"
|
||||
class="overflow-hidden p-3 !bg-n-solid-2 shadow-[0px_0px_12px_0px_rgba(0,0,0,0.05)]"
|
||||
data-bubble-name="attachment"
|
||||
>
|
||||
<slot name="before" />
|
||||
<div class="grid gap-3 px-3 pt-3 z-20">
|
||||
<div
|
||||
class="size-8 rounded-lg grid place-content-center"
|
||||
:class="iconBgColor"
|
||||
>
|
||||
<slot name="icon">
|
||||
<Icon :icon="icon" class="text-white size-4" />
|
||||
</slot>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div v-if="senderName" class="text-n-slate-12 text-sm truncate">
|
||||
{{
|
||||
t(senderTranslationKey, {
|
||||
sender: senderName,
|
||||
})
|
||||
}}
|
||||
<div class="grid gap-4 min-w-64">
|
||||
<div class="grid gap-3 z-20">
|
||||
<div
|
||||
class="size-8 rounded-lg grid place-content-center"
|
||||
:class="iconBgColor"
|
||||
>
|
||||
<slot name="icon">
|
||||
<Icon :icon="icon" class="text-white size-4" />
|
||||
</slot>
|
||||
</div>
|
||||
<slot>
|
||||
<div v-if="content" class="truncate text-sm text-n-slate-11">
|
||||
{{ content }}
|
||||
<div class="space-y-1">
|
||||
<div v-if="senderName" class="text-n-slate-12 text-sm truncate">
|
||||
{{
|
||||
t(senderTranslationKey, {
|
||||
sender: senderName,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</slot>
|
||||
<slot>
|
||||
<div v-if="content" class="truncate text-sm text-n-slate-11">
|
||||
{{ content }}
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="action">
|
||||
<a
|
||||
v-if="action.href"
|
||||
:href="action.href"
|
||||
rel="noreferrer noopener nofollow"
|
||||
target="_blank"
|
||||
class="w-full block bg-n-solid-3 px-4 py-2 rounded-lg text-sm text-center border border-n-container"
|
||||
>
|
||||
{{ action.label }}
|
||||
</a>
|
||||
<button
|
||||
v-else
|
||||
class="w-full bg-n-solid-3 px-4 py-2 rounded-lg text-sm text-center border border-n-container"
|
||||
@click="action.onClick"
|
||||
>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="action" class="px-3 pb-3">
|
||||
<a
|
||||
v-if="action.href"
|
||||
:href="action.href"
|
||||
rel="noreferrer noopener nofollow"
|
||||
target="_blank"
|
||||
class="w-full block bg-n-solid-3 px-4 py-2 rounded-lg text-sm text-center"
|
||||
>
|
||||
{{ action.label }}
|
||||
</a>
|
||||
<button
|
||||
v-else
|
||||
class="w-full bg-n-solid-3 px-4 py-2 rounded-lg text-sm"
|
||||
@click="action.onClick"
|
||||
>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</div>
|
||||
</BaseBubble>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed } from 'vue';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import BaseAttachmentBubble from './BaseAttachment.vue';
|
||||
|
||||
import {
|
||||
@@ -10,45 +11,13 @@ import {
|
||||
ExceptionWithMessage,
|
||||
} from 'shared/helpers/CustomErrors';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
sender: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const { content, attachments } = useMessageContext();
|
||||
|
||||
const $store = useStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const attachment = computed(() => {
|
||||
return props.attachments[0];
|
||||
return attachments.value[0];
|
||||
});
|
||||
|
||||
const phoneNumber = computed(() => {
|
||||
@@ -64,7 +33,7 @@ const rawPhoneNumber = computed(() => {
|
||||
});
|
||||
|
||||
const name = computed(() => {
|
||||
return props.content;
|
||||
return content.value;
|
||||
});
|
||||
|
||||
function getContactObject() {
|
||||
@@ -129,7 +98,6 @@ const action = computed(() => ({
|
||||
<BaseAttachmentBubble
|
||||
icon="i-teenyicons-user-circle-solid"
|
||||
icon-bg-color="bg-[#D6409F]"
|
||||
:sender="sender"
|
||||
sender-translation-key="CONVERSATION.SHARED_ATTACHMENT.CONTACT"
|
||||
:content="phoneNumber"
|
||||
:action="formattedPhoneNumber ? action : null"
|
||||
|
||||
@@ -5,23 +5,16 @@ import { buildDyteURL } from 'shared/helpers/IntegrationHelper';
|
||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import BaseAttachmentBubble from './BaseAttachment.vue';
|
||||
|
||||
const props = defineProps({
|
||||
contentAttributes: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sender: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const { contentAttributes } = useMessageContext();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const meetingData = computed(() => {
|
||||
return useCamelCase(props.contentAttributes.data);
|
||||
return useCamelCase(contentAttributes.value.data);
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
@@ -57,7 +50,6 @@ const action = computed(() => ({
|
||||
<BaseAttachmentBubble
|
||||
icon="i-ph-video-camera-fill"
|
||||
icon-bg-color="bg-[#2781F6]"
|
||||
:sender="sender"
|
||||
sender-translation-key="CONVERSATION.SHARED_ATTACHMENT.MEETING"
|
||||
:action="action"
|
||||
>
|
||||
|
||||
@@ -1,57 +1,44 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { MESSAGE_STATUS } from '../../constants';
|
||||
import { useMessageContext } from '../../provider.js';
|
||||
|
||||
const props = defineProps({
|
||||
contentAttributes: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => Object.values(MESSAGE_STATUS).includes(value),
|
||||
},
|
||||
sender: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const { contentAttributes, status, sender } = useMessageContext();
|
||||
|
||||
const hasError = computed(() => {
|
||||
return props.status === MESSAGE_STATUS.FAILED;
|
||||
return status.value === MESSAGE_STATUS.FAILED;
|
||||
});
|
||||
|
||||
const fromEmail = computed(() => {
|
||||
return props.contentAttributes?.email?.from ?? [];
|
||||
return contentAttributes.value?.email?.from ?? [];
|
||||
});
|
||||
|
||||
const toEmail = computed(() => {
|
||||
return props.contentAttributes?.email?.to ?? [];
|
||||
return contentAttributes.value?.email?.to ?? [];
|
||||
});
|
||||
|
||||
const ccEmail = computed(() => {
|
||||
return (
|
||||
props.contentAttributes?.ccEmails ??
|
||||
props.contentAttributes?.email?.cc ??
|
||||
contentAttributes.value?.ccEmails ??
|
||||
contentAttributes.value?.email?.cc ??
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
const senderName = computed(() => {
|
||||
return props.sender.name ?? '';
|
||||
return sender.value.name ?? '';
|
||||
});
|
||||
|
||||
const bccEmail = computed(() => {
|
||||
return (
|
||||
props.contentAttributes?.bccEmails ??
|
||||
props.contentAttributes?.email?.bcc ??
|
||||
contentAttributes.value?.bccEmails ??
|
||||
contentAttributes.value?.email?.bcc ??
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
const subject = computed(() => {
|
||||
return props.contentAttributes?.email?.subject ?? '';
|
||||
return contentAttributes.value?.email?.subject ?? '';
|
||||
});
|
||||
|
||||
const showMeta = computed(() => {
|
||||
|
||||
@@ -7,37 +7,13 @@ import { EmailQuoteExtractor } from './removeReply.js';
|
||||
import BaseBubble from 'next/message/bubbles/Base.vue';
|
||||
import FormattedContent from 'next/message/bubbles/Text/FormattedContent.vue';
|
||||
import AttachmentChips from 'next/message/chips/AttachmentChips.vue';
|
||||
|
||||
import EmailMeta from './EmailMeta.vue';
|
||||
import { MESSAGE_STATUS, MESSAGE_TYPES } from '../../constants';
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contentAttributes: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => Object.values(MESSAGE_STATUS).includes(value),
|
||||
},
|
||||
sender: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
messageType: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
import { useMessageContext } from '../../provider.js';
|
||||
import { MESSAGE_TYPES } from 'next/message/constants.js';
|
||||
|
||||
const { content, contentAttributes, attachments, messageType } =
|
||||
useMessageContext();
|
||||
|
||||
const isExpandable = ref(false);
|
||||
const isExpanded = ref(false);
|
||||
@@ -49,11 +25,11 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const isOutgoing = computed(() => {
|
||||
return props.messageType === MESSAGE_TYPES.OUTGOING;
|
||||
return messageType.value === MESSAGE_TYPES.OUTGOING;
|
||||
});
|
||||
|
||||
const fullHTML = computed(() => {
|
||||
return props.contentAttributes?.email?.htmlContent?.full ?? props.content;
|
||||
return contentAttributes?.value?.email?.htmlContent?.full ?? content.value;
|
||||
});
|
||||
|
||||
const unquotedHTML = computed(() => {
|
||||
@@ -66,14 +42,14 @@ const hasQuotedMessage = computed(() => {
|
||||
|
||||
const textToShow = computed(() => {
|
||||
const text =
|
||||
props.contentAttributes?.email?.textContent?.full ?? props.content;
|
||||
return text.replace(/\n/g, '<br>');
|
||||
contentAttributes?.value?.email?.textContent?.full ?? content.value;
|
||||
return text?.replace(/\n/g, '<br>');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble class="w-full overflow-hidden" data-bubble-name="email">
|
||||
<EmailMeta :status :sender :content-attributes />
|
||||
<EmailMeta />
|
||||
<section
|
||||
ref="contentContainer"
|
||||
class="p-4"
|
||||
|
||||
@@ -2,43 +2,16 @@
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import BaseAttachmentBubble from './BaseAttachment.vue';
|
||||
import FileIcon from 'next/icon/FileIcon.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
sender: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const { attachments } = useMessageContext();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const url = computed(() => {
|
||||
return props.attachments[0].dataUrl;
|
||||
return attachments.value[0].dataUrl;
|
||||
});
|
||||
|
||||
const fileName = computed(() => {
|
||||
@@ -58,7 +31,6 @@ const fileType = computed(() => {
|
||||
<BaseAttachmentBubble
|
||||
icon="i-teenyicons-user-circle-solid"
|
||||
icon-bg-color="bg-n-alpha-3 dark:bg-n-alpha-white"
|
||||
:sender="sender"
|
||||
sender-translation-key="CONVERSATION.SHARED_ATTACHMENT.FILE"
|
||||
:content="decodeURI(fileName)"
|
||||
:action="{
|
||||
|
||||
@@ -4,44 +4,18 @@ import BaseBubble from './Base.vue';
|
||||
import Button from 'next/button/Button.vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||
import { useMessageContext } from 'next/message/provider.js';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import GalleryView from 'dashboard/components/widgets/conversation/components/GalleryView.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['error']);
|
||||
const { filteredCurrentChatAttachments, attachments } = useMessageContext();
|
||||
|
||||
const attachment = computed(() => {
|
||||
return props.attachments[0];
|
||||
return attachments.value[0];
|
||||
});
|
||||
|
||||
const hasError = ref(false);
|
||||
const showGallery = ref(false);
|
||||
const { filteredCurrentChatAttachments } = useMessageContext();
|
||||
|
||||
const handleError = () => {
|
||||
hasError.value = true;
|
||||
@@ -64,20 +38,17 @@ const downloadAttachment = async () => {
|
||||
|
||||
<template>
|
||||
<BaseBubble
|
||||
class="overflow-hidden relative group border-[4px] border-n-weak"
|
||||
class="overflow-hidden p-3"
|
||||
data-bubble-name="image"
|
||||
@click="showGallery = true"
|
||||
>
|
||||
<div
|
||||
v-if="hasError"
|
||||
class="flex items-center gap-1 px-5 py-4 text-center rounded-lg bg-n-alpha-1"
|
||||
>
|
||||
<div v-if="hasError" class="flex items-center gap-1 text-center rounded-lg">
|
||||
<Icon icon="i-lucide-circle-off" class="text-n-slate-11" />
|
||||
<p class="mb-0 text-n-slate-11">
|
||||
{{ $t('COMPONENTS.MEDIA.IMAGE_UNAVAILABLE') }}
|
||||
</p>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-else class="relative group rounded-lg overflow-hidden">
|
||||
<img
|
||||
:src="attachment.dataUrl"
|
||||
:width="attachment.width"
|
||||
@@ -98,7 +69,7 @@ const downloadAttachment = async () => {
|
||||
@click="downloadAttachment"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</BaseBubble>
|
||||
<GalleryView
|
||||
v-if="showGallery"
|
||||
|
||||
@@ -7,46 +7,22 @@ import BaseBubble from 'next/message/bubbles/Base.vue';
|
||||
import MessageFormatter from 'shared/helpers/MessageFormatter.js';
|
||||
import { MESSAGE_VARIANTS } from '../constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['error']);
|
||||
const { variant, content, attachments } = useMessageContext();
|
||||
|
||||
const attachment = computed(() => {
|
||||
return props.attachments[0];
|
||||
return attachments.value[0];
|
||||
});
|
||||
|
||||
const { variant } = useMessageContext();
|
||||
const hasImgStoryError = ref(false);
|
||||
const hasVideoStoryError = ref(false);
|
||||
|
||||
const formattedContent = computed(() => {
|
||||
if (variant.value === MESSAGE_VARIANTS.ACTIVITY) {
|
||||
return props.content;
|
||||
return content.value;
|
||||
}
|
||||
|
||||
return new MessageFormatter(props.content).formattedMessage;
|
||||
return new MessageFormatter(content.value).formattedMessage;
|
||||
});
|
||||
|
||||
const onImageLoadError = () => {
|
||||
|
||||
@@ -1,43 +1,14 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, nextTick, useTemplateRef } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import BaseAttachmentBubble from './BaseAttachment.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
sender: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
import { useMessageContext } from '../provider.js';
|
||||
|
||||
const { attachments } = useMessageContext();
|
||||
const { t } = useI18n();
|
||||
|
||||
const attachment = computed(() => {
|
||||
return props.attachments[0];
|
||||
return attachments.value[0];
|
||||
});
|
||||
|
||||
const lat = computed(() => {
|
||||
@@ -48,61 +19,23 @@ const long = computed(() => {
|
||||
});
|
||||
|
||||
const title = computed(() => {
|
||||
return attachment.value.fallbackTitle;
|
||||
return attachment.value.fallbackTitle ?? attachment.value.fallback_title;
|
||||
});
|
||||
|
||||
const mapUrl = computed(
|
||||
() => `https://maps.google.com/?q=${lat.value},${long.value}`
|
||||
);
|
||||
|
||||
const mapContainer = useTemplateRef('mapContainer');
|
||||
|
||||
const setupMap = () => {
|
||||
const map = new maplibregl.Map({
|
||||
style: 'https://tiles.openfreemap.org/styles/positron',
|
||||
center: [long.value, lat.value],
|
||||
zoom: 9.5,
|
||||
container: mapContainer.value,
|
||||
attributionControl: false,
|
||||
dragPan: false,
|
||||
dragRotate: false,
|
||||
scrollZoom: false,
|
||||
touchZoom: false,
|
||||
touchRotate: false,
|
||||
keyboard: false,
|
||||
doubleClickZoom: false,
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
setupMap();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseAttachmentBubble
|
||||
icon="i-ph-navigation-arrow-fill"
|
||||
icon-bg-color="bg-[#0D9B8A]"
|
||||
:sender="sender"
|
||||
sender-translation-key="CONVERSATION.SHARED_ATTACHMENT.LOCATION"
|
||||
:content="title"
|
||||
:action="{
|
||||
label: t('COMPONENTS.LOCATION_BUBBLE.SEE_ON_MAP'),
|
||||
href: mapUrl,
|
||||
}"
|
||||
>
|
||||
<template #before>
|
||||
<div
|
||||
ref="mapContainer"
|
||||
class="z-10 w-full max-w-md -mb-12 min-w-64 h-28"
|
||||
/>
|
||||
</template>
|
||||
</BaseAttachmentBubble>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
</style>
|
||||
|
||||
@@ -4,47 +4,25 @@ import BaseBubble from 'next/message/bubbles/Base.vue';
|
||||
import FormattedContent from './FormattedContent.vue';
|
||||
import AttachmentChips from 'next/message/chips/AttachmentChips.vue';
|
||||
import { MESSAGE_TYPES } from '../../constants';
|
||||
import { useMessageContext } from '../../provider.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
contentAttributes: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
messageType: {
|
||||
type: Number,
|
||||
required: true,
|
||||
validator: value => Object.values(MESSAGE_TYPES).includes(value),
|
||||
},
|
||||
});
|
||||
const { content, attachments, contentAttributes, messageType } =
|
||||
useMessageContext();
|
||||
|
||||
const isTemplate = computed(() => {
|
||||
return props.messageType === MESSAGE_TYPES.TEMPLATE;
|
||||
return messageType.value === MESSAGE_TYPES.TEMPLATE;
|
||||
});
|
||||
|
||||
const isEmpty = computed(() => {
|
||||
return !content.value && !attachments.value.length;
|
||||
});
|
||||
</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">
|
||||
|
||||
@@ -3,39 +3,14 @@ import { ref, computed } from 'vue';
|
||||
import BaseBubble from './Base.vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||
import { useMessageContext } from 'next/message/provider.js';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import GalleryView from 'dashboard/components/widgets/conversation/components/GalleryView.vue';
|
||||
import { ATTACHMENT_TYPES } from '../constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['error']);
|
||||
const hasError = ref(false);
|
||||
const showGallery = ref(false);
|
||||
const { filteredCurrentChatAttachments } = useMessageContext();
|
||||
const { filteredCurrentChatAttachments, attachments } = useMessageContext();
|
||||
|
||||
const handleError = () => {
|
||||
hasError.value = true;
|
||||
@@ -43,7 +18,7 @@ const handleError = () => {
|
||||
};
|
||||
|
||||
const attachment = computed(() => {
|
||||
return props.attachments[0];
|
||||
return attachments.value[0];
|
||||
});
|
||||
|
||||
const isReel = computed(() => {
|
||||
@@ -53,25 +28,28 @@ const isReel = computed(() => {
|
||||
|
||||
<template>
|
||||
<BaseBubble
|
||||
class="overflow-hidden relative group border-[4px] border-n-weak"
|
||||
class="overflow-hidden p-3"
|
||||
data-bubble-name="video"
|
||||
@click="showGallery = true"
|
||||
>
|
||||
<div
|
||||
v-if="isReel"
|
||||
class="absolute p-2 flex items-start justify-end size-12 bg-gradient-to-bl from-n-alpha-black1 to-transparent right-0"
|
||||
>
|
||||
<Icon icon="i-lucide-instagram" class="text-white" />
|
||||
<div class="relative group rounded-lg overflow-hidden">
|
||||
<div
|
||||
v-if="isReel"
|
||||
class="absolute p-2 flex items-start justify-end right-0"
|
||||
>
|
||||
<Icon icon="i-lucide-instagram" class="text-white shadow-lg" />
|
||||
</div>
|
||||
<video
|
||||
controls
|
||||
class="rounded-lg"
|
||||
:src="attachment.dataUrl"
|
||||
:class="{
|
||||
'max-w-48': isReel,
|
||||
'max-w-full': !isReel,
|
||||
}"
|
||||
@error="handleError"
|
||||
/>
|
||||
</div>
|
||||
<video
|
||||
controls
|
||||
:src="attachment.dataUrl"
|
||||
:class="{
|
||||
'max-w-48': isReel,
|
||||
'max-w-full': !isReel,
|
||||
}"
|
||||
@error="handleError"
|
||||
/>
|
||||
</BaseBubble>
|
||||
<GalleryView
|
||||
v-if="showGallery"
|
||||
|
||||
@@ -53,7 +53,7 @@ const textColorClass = computed(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="h-9 bg-n-alpha-white gap-2 items-center flex px-2 rounded-lg border border-n-strong"
|
||||
class="h-9 bg-n-alpha-white gap-2 items-center flex px-2 rounded-lg border border-n-container"
|
||||
>
|
||||
<FileIcon class="flex-shrink-0" :file-type="fileType" />
|
||||
<span class="mr-1 max-w-32 truncate" :class="textColorClass">
|
||||
|
||||
@@ -5,6 +5,112 @@ import { ATTACHMENT_TYPES } from './constants';
|
||||
|
||||
const MessageControl = Symbol('MessageControl');
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Sender
|
||||
* @property {Object} additional_attributes - Additional attributes of the sender
|
||||
* @property {Object} custom_attributes - Custom attributes of the sender
|
||||
* @property {string} email - Email of the sender
|
||||
* @property {number} id - ID of the sender
|
||||
* @property {string|null} identifier - Identifier of the sender
|
||||
* @property {string} name - Name of the sender
|
||||
* @property {string|null} phone_number - Phone number of the sender
|
||||
* @property {string} thumbnail - Thumbnail URL of the sender
|
||||
* @property {string} type - Type of sender
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmailContent
|
||||
* @property {string[]|null} bcc - BCC recipients
|
||||
* @property {string[]|null} cc - CC recipients
|
||||
* @property {string} contentType - Content type of the email
|
||||
* @property {string} date - Date the email was sent
|
||||
* @property {string[]} from - From email address
|
||||
* @property {Object} htmlContent - HTML content of the email
|
||||
* @property {string} htmlContent.full - Full HTML content
|
||||
* @property {string} htmlContent.reply - Reply HTML content
|
||||
* @property {string} htmlContent.quoted - Quoted HTML content
|
||||
* @property {string|null} inReplyTo - Message ID being replied to
|
||||
* @property {string} messageId - Unique message identifier
|
||||
* @property {boolean} multipart - Whether the email is multipart
|
||||
* @property {number} numberOfAttachments - Number of attachments
|
||||
* @property {string} subject - Email subject line
|
||||
* @property {Object} textContent - Text content of the email
|
||||
* @property {string} textContent.full - Full text content
|
||||
* @property {string} textContent.reply - Reply text content
|
||||
* @property {string} textContent.quoted - Quoted text content
|
||||
* @property {string[]} to - To email addresses
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ContentAttributes
|
||||
* @property {string} externalError - an error message to be shown if the message failed to send
|
||||
* @property {Object} [data] - Optional data object containing roomName and messageId
|
||||
* @property {string} data.roomName - Name of the room
|
||||
* @property {string} data.messageId - ID of the message
|
||||
* @property {'story_mention'} [imageType] - Flag to indicate this is a story mention
|
||||
* @property {'dyte'} [type] - Flag to indicate this is a dyte call
|
||||
* @property {EmailContent} [email] - Email content and metadata
|
||||
* @property {string|null} [ccEmail] - CC email addresses
|
||||
* @property {string|null} [bccEmail] - BCC email addresses
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {'sent'|'delivered'|'read'|'failed'|'progress'} MessageStatus
|
||||
* @typedef {'text'|'input_text'|'input_textarea'|'input_email'|'input_select'|'cards'|'form'|'article'|'incoming_email'|'input_csat'|'integrations'|'sticker'} MessageContentType
|
||||
* @typedef {0|1|2|3} MessageType
|
||||
* @typedef {'contact'|'user'|'Contact'|'User'} SenderType
|
||||
* @typedef {'user'|'agent'|'activity'|'private'|'bot'|'error'|'template'|'email'|'unsupported'} MessageVariant
|
||||
* @typedef {'left'|'center'|'right'} MessageOrientation
|
||||
|
||||
* @typedef {Object} MessageContext
|
||||
* @property {import('vue').Ref<string>} content - The message content
|
||||
* @property {import('vue').Ref<number>} conversationId - The ID of the conversation to which the message belongs
|
||||
* @property {import('vue').Ref<number>} createdAt - Timestamp when the message was created
|
||||
* @property {import('vue').Ref<number>} currentUserId - The ID of the current user
|
||||
* @property {import('vue').Ref<number>} id - The unique identifier for the message
|
||||
* @property {import('vue').Ref<number>} inboxId - The ID of the inbox to which the message belongs
|
||||
* @property {import('vue').Ref<boolean>} [groupWithNext=false] - Whether the message should be grouped with the next message
|
||||
* @property {import('vue').Ref<boolean>} [isEmailInbox=false] - Whether the message is from an email inbox
|
||||
* @property {import('vue').Ref<boolean>} [private=false] - Whether the message is private
|
||||
* @property {import('vue').Ref<number|null>} [senderId=null] - The ID of the sender
|
||||
* @property {import('vue').Ref<string|null>} [error=null] - Error message if the message failed to send
|
||||
* @property {import('vue').Ref<Attachment[]>} [attachments=[]] - The attachments associated with the message
|
||||
* @property {import('vue').Ref<ContentAttributes>} [contentAttributes={}] - Additional attributes of the message content
|
||||
* @property {import('vue').Ref<MessageContentType>} contentType - Content type of the message
|
||||
* @property {import('vue').Ref<MessageStatus>} status - The delivery status of the message
|
||||
* @property {import('vue').Ref<MessageType>} messageType - The type of message (must be one of MESSAGE_TYPES)
|
||||
* @property {import('vue').Ref<Object|null>} [inReplyTo=null] - The message to which this message is a reply
|
||||
* @property {import('vue').Ref<SenderType>} [senderType=null] - The type of the sender
|
||||
* @property {import('vue').Ref<Sender|null>} [sender=null] - The sender information
|
||||
* @property {import('vue').ComputedRef<MessageOrientation>} orientation - The visual variant of the message
|
||||
* @property {import('vue').ComputedRef<MessageVariant>} variant - The visual variant of the message
|
||||
* @property {import('vue').ComputedRef<boolean>} isMyMessage - Does the message belong to the current user
|
||||
* @property {import('vue').ComputedRef<boolean>} isPrivate - Proxy computed value for private
|
||||
* @property {import('vue').ComputedRef<boolean>} shouldGroupWithNext - Should group with the next message or not, it is differnt from groupWithNext, this has a bypass for a failed message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the message context from the parent Message component.
|
||||
* Must be used within a component that is a child of a Message component.
|
||||
*
|
||||
* @returns {MessageContext & { filteredCurrentChatAttachments: import('vue').ComputedRef<Attachment[]> }}
|
||||
* Message context object containing message properties and computed values
|
||||
* @throws {Error} If used outside of a Message component context
|
||||
*/
|
||||
export function useMessageContext() {
|
||||
const context = inject(MessageControl, null);
|
||||
if (context === null) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useAI } from 'dashboard/composables/useAI';
|
||||
// components
|
||||
import ReplyBox from './ReplyBox.vue';
|
||||
import Message from './Message.vue';
|
||||
import NextMessageList from 'next/message/MessageList.vue';
|
||||
import ConversationLabelSuggestion from './conversation/LabelSuggestion.vue';
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
|
||||
@@ -37,6 +38,7 @@ import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||
export default {
|
||||
components: {
|
||||
Message,
|
||||
NextMessageList,
|
||||
ReplyBox,
|
||||
Banner,
|
||||
ConversationLabelSuggestion,
|
||||
@@ -80,6 +82,10 @@ export default {
|
||||
fetchLabelSuggestions,
|
||||
} = useAI();
|
||||
|
||||
const showNextBubbles = LocalStorage.get(
|
||||
LOCAL_STORAGE_KEYS.USE_NEXT_BUBBLE
|
||||
);
|
||||
|
||||
return {
|
||||
isEnterprise,
|
||||
isPopOutReplyBox,
|
||||
@@ -89,6 +95,7 @@ export default {
|
||||
isLabelSuggestionFeatureEnabled,
|
||||
fetchIntegrationsIfRequired,
|
||||
fetchLabelSuggestions,
|
||||
showNextBubbles,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@@ -106,6 +113,7 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentChat: 'getSelectedChat',
|
||||
currentUserId: 'getCurrentUserID',
|
||||
listLoadingStatus: 'getAllMessagesLoaded',
|
||||
currentAccountId: 'getCurrentAccountId',
|
||||
}),
|
||||
@@ -436,7 +444,6 @@ export default {
|
||||
makeMessagesRead() {
|
||||
this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
|
||||
},
|
||||
|
||||
getInReplyToMessage(parentMessage) {
|
||||
if (!parentMessage) return {};
|
||||
const inReplyToMessageId = parentMessage.content_attributes?.in_reply_to;
|
||||
@@ -473,7 +480,46 @@ export default {
|
||||
@click="onToggleContactPanel"
|
||||
/>
|
||||
</div>
|
||||
<ul class="conversation-panel">
|
||||
<NextMessageList
|
||||
v-if="showNextBubbles"
|
||||
class="conversation-panel"
|
||||
:read-messages="readMessages"
|
||||
:un-read-messages="unReadMessages"
|
||||
:current-user-id="currentUserId"
|
||||
:is-an-email-channel="isAnEmailChannel"
|
||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||
:messages="currentChat ? currentChat.messages : []"
|
||||
>
|
||||
<template #beforeAll>
|
||||
<transition name="slide-up">
|
||||
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
|
||||
<li class="min-h-[4rem]">
|
||||
<span v-if="shouldShowSpinner" class="spinner message" />
|
||||
</li>
|
||||
</transition>
|
||||
</template>
|
||||
<template #beforeUnread>
|
||||
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
||||
<span>
|
||||
{{ unreadMessageCount > 9 ? '9+' : unreadMessageCount }}
|
||||
{{
|
||||
unreadMessageCount > 1
|
||||
? $t('CONVERSATION.UNREAD_MESSAGES')
|
||||
: $t('CONVERSATION.UNREAD_MESSAGE')
|
||||
}}
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
<template #after>
|
||||
<ConversationLabelSuggestion
|
||||
v-if="shouldShowLabelSuggestions"
|
||||
:suggested-labels="labelSuggestions"
|
||||
:chat-labels="currentChat.labels"
|
||||
:conversation-id="currentChat.id"
|
||||
/>
|
||||
</template>
|
||||
</NextMessageList>
|
||||
<ul v-else class="conversation-panel">
|
||||
<transition name="slide-up">
|
||||
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
|
||||
<li class="min-h-[4rem]">
|
||||
@@ -556,7 +602,6 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.rounded-bl-calc {
|
||||
border-bottom-left-radius: calc(1.5rem + 1px);
|
||||
|
||||
141
app/javascript/dashboard/composables/useInbox.js
Normal file
141
app/javascript/dashboard/composables/useInbox.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import { computed } from 'vue';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
import { INBOX_TYPES } from 'dashboard/helper/inbox';
|
||||
|
||||
export const INBOX_FEATURES = {
|
||||
REPLY_TO: 'replyTo',
|
||||
REPLY_TO_OUTGOING: 'replyToOutgoing',
|
||||
};
|
||||
|
||||
// This is a single source of truth for inbox features
|
||||
// This is used to check if a feature is available for a particular inbox or not
|
||||
export const INBOX_FEATURE_MAP = {
|
||||
[INBOX_FEATURES.REPLY_TO]: [
|
||||
INBOX_TYPES.FB,
|
||||
INBOX_TYPES.WEB,
|
||||
INBOX_TYPES.TWITTER,
|
||||
INBOX_TYPES.WHATSAPP,
|
||||
INBOX_TYPES.TELEGRAM,
|
||||
INBOX_TYPES.API,
|
||||
],
|
||||
[INBOX_FEATURES.REPLY_TO_OUTGOING]: [
|
||||
INBOX_TYPES.WEB,
|
||||
INBOX_TYPES.TWITTER,
|
||||
INBOX_TYPES.WHATSAPP,
|
||||
INBOX_TYPES.TELEGRAM,
|
||||
INBOX_TYPES.API,
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Composable for handling macro-related functionality
|
||||
* @returns {Object} An object containing the getMacroDropdownValues function
|
||||
*/
|
||||
export const useInbox = () => {
|
||||
const currentChat = useMapGetter('getSelectedChat');
|
||||
const inboxGetter = useMapGetter('inboxes/getInboxById');
|
||||
|
||||
const inbox = computed(() => {
|
||||
const inboxId = currentChat.value.inbox_id;
|
||||
|
||||
return useCamelCase(inboxGetter.value(inboxId), { deep: true });
|
||||
});
|
||||
|
||||
const channelType = computed(() => {
|
||||
return inbox.value.channelType;
|
||||
});
|
||||
|
||||
const isAPIInbox = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.API;
|
||||
});
|
||||
|
||||
const isAFacebookInbox = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.FB;
|
||||
});
|
||||
|
||||
const isAWebWidgetInbox = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.WEB;
|
||||
});
|
||||
|
||||
const isATwilioChannel = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.TWILIO;
|
||||
});
|
||||
|
||||
const isALineChannel = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.LINE;
|
||||
});
|
||||
|
||||
const isAnEmailChannel = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.EMAIL;
|
||||
});
|
||||
|
||||
const isATelegramChannel = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.TELEGRAM;
|
||||
});
|
||||
|
||||
const whatsAppAPIProvider = computed(() => {
|
||||
return inbox.value.provider || '';
|
||||
});
|
||||
|
||||
const isAMicrosoftInbox = computed(() => {
|
||||
return isAnEmailChannel.value && inbox.value.provider === 'microsoft';
|
||||
});
|
||||
|
||||
const isAGoogleInbox = computed(() => {
|
||||
return isAnEmailChannel.value && inbox.value.provider === 'google';
|
||||
});
|
||||
|
||||
const isATwilioSMSChannel = computed(() => {
|
||||
const { medium: medium = '' } = inbox.value;
|
||||
return isATwilioChannel.value && medium === 'sms';
|
||||
});
|
||||
|
||||
const isASmsInbox = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.SMS || isATwilioSMSChannel.value;
|
||||
});
|
||||
|
||||
const isATwilioWhatsAppChannel = computed(() => {
|
||||
const { medium: medium = '' } = inbox.value;
|
||||
return isATwilioChannel.value && medium === 'whatsapp';
|
||||
});
|
||||
|
||||
const isAWhatsAppCloudChannel = computed(() => {
|
||||
return (
|
||||
channelType.value === INBOX_TYPES.WHATSAPP &&
|
||||
whatsAppAPIProvider.value === 'whatsapp_cloud'
|
||||
);
|
||||
});
|
||||
|
||||
const is360DialogWhatsAppChannel = computed(() => {
|
||||
return (
|
||||
channelType.value === INBOX_TYPES.WHATSAPP &&
|
||||
whatsAppAPIProvider.value === 'default'
|
||||
);
|
||||
});
|
||||
|
||||
const isAWhatsAppChannel = computed(() => {
|
||||
return (
|
||||
channelType.value === INBOX_TYPES.WHATSAPP ||
|
||||
isATwilioWhatsAppChannel.value
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
inbox,
|
||||
isAFacebookInbox,
|
||||
isALineChannel,
|
||||
isAPIInbox,
|
||||
isASmsInbox,
|
||||
isATelegramChannel,
|
||||
isATwilioChannel,
|
||||
isAWebWidgetInbox,
|
||||
isAWhatsAppChannel,
|
||||
isAMicrosoftInbox,
|
||||
isAGoogleInbox,
|
||||
isATwilioWhatsAppChannel,
|
||||
isAWhatsAppCloudChannel,
|
||||
is360DialogWhatsAppChannel,
|
||||
isAnEmailChannel,
|
||||
};
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
import { unref } from 'vue';
|
||||
import camelcaseKeys from 'camelcase-keys';
|
||||
import snakecaseKeys from 'snakecase-keys';
|
||||
import * as Sentry from '@sentry/vue';
|
||||
|
||||
/**
|
||||
* Vue composable that converts object keys to camelCase
|
||||
@@ -12,8 +13,18 @@ import snakecaseKeys from 'snakecase-keys';
|
||||
* @returns {Object|Array} Converted payload with camelCase keys
|
||||
*/
|
||||
export function useCamelCase(payload, options) {
|
||||
const unrefPayload = unref(payload);
|
||||
return camelcaseKeys(unrefPayload, options);
|
||||
try {
|
||||
const unrefPayload = unref(payload);
|
||||
return camelcaseKeys(unrefPayload, options);
|
||||
} catch (e) {
|
||||
Sentry.setContext('transform-keys-error', {
|
||||
payload,
|
||||
options,
|
||||
op: 'camelCase',
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,6 +35,16 @@ export function useCamelCase(payload, options) {
|
||||
* @returns {Object|Array} Converted payload with snake_case keys
|
||||
*/
|
||||
export function useSnakeCase(payload, options) {
|
||||
const unrefPayload = unref(payload);
|
||||
return snakecaseKeys(unrefPayload, options);
|
||||
try {
|
||||
const unrefPayload = unref(payload);
|
||||
return snakecaseKeys(unrefPayload, options);
|
||||
} catch (e) {
|
||||
Sentry.setContext('transform-keys-error', {
|
||||
payload,
|
||||
options,
|
||||
op: 'snakeCase',
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ export const LOCAL_STORAGE_KEYS = {
|
||||
COLOR_SCHEME: 'color_scheme',
|
||||
DISMISSED_LABEL_SUGGESTIONS: 'labelSuggestionsDismissed',
|
||||
MESSAGE_REPLY_TO: 'messageReplyTo',
|
||||
USE_NEXT_BUBBLE: 'useNextBubble',
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"DOWNLOAD": "Download",
|
||||
"UNKNOWN_FILE_TYPE": "Unknown File",
|
||||
"SAVE_CONTACT": "Save Contact",
|
||||
"NO_CONTENT": "No content to display",
|
||||
"SHARED_ATTACHMENT": {
|
||||
"CONTACT": "{sender} has shared a contact",
|
||||
"LOCATION": "{sender} has shared a location",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { mapGetters } from 'vuex';
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import ContextMenu from 'dashboard/components/ui/ContextMenu.vue';
|
||||
import AddCannedModal from 'dashboard/routes/dashboard/settings/canned/AddCanned.vue';
|
||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
import { conversationUrl, frontendURL } from '../../../helper/URLHelper';
|
||||
import {
|
||||
@@ -38,6 +39,10 @@ export default {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
hideButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['open', 'close', 'replyTo'],
|
||||
setup() {
|
||||
@@ -62,7 +67,7 @@ export default {
|
||||
return this.getPlainText(this.messageContent);
|
||||
},
|
||||
conversationId() {
|
||||
return this.message.conversation_id;
|
||||
return this.message.conversation_id ?? this.message.conversationId;
|
||||
},
|
||||
messageId() {
|
||||
return this.message.id;
|
||||
@@ -71,7 +76,9 @@ export default {
|
||||
return this.message.content;
|
||||
},
|
||||
contentAttributes() {
|
||||
return this.message.content_attributes;
|
||||
return useSnakeCase(
|
||||
this.message.content_attributes ?? this.message.contentAttributes
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -183,6 +190,7 @@ export default {
|
||||
:reject-text="$t('CONVERSATION.CONTEXT_MENU.DELETE_CONFIRMATION.CANCEL')"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="!hideButton"
|
||||
icon="more-vertical"
|
||||
color-scheme="secondary"
|
||||
variant="clear"
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
"idb": "^8.0.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"libphonenumber-js": "^1.11.9",
|
||||
"maplibre-gl": "^4.7.1",
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-link-attributes": "^4.0.1",
|
||||
"md5": "^2.3.0",
|
||||
|
||||
259
pnpm-lock.yaml
generated
259
pnpm-lock.yaml
generated
@@ -139,9 +139,6 @@ importers:
|
||||
libphonenumber-js:
|
||||
specifier: ^1.11.9
|
||||
version: 1.11.9
|
||||
maplibre-gl:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1
|
||||
markdown-it:
|
||||
specifier: ^13.0.2
|
||||
version: 13.0.2
|
||||
@@ -994,34 +991,6 @@ packages:
|
||||
resolution: {integrity: sha512-dUz8OmYvlY5A9wXaroHIMSPASpSYRLCqbPvxGSyHguhtTQIy24lC+EGxQlwv71AhRCO55WOtgwhzQLpw27JaJQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
'@mapbox/geojson-rewind@0.5.2':
|
||||
resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==}
|
||||
hasBin: true
|
||||
|
||||
'@mapbox/jsonlint-lines-primitives@2.0.2':
|
||||
resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
'@mapbox/point-geometry@0.1.0':
|
||||
resolution: {integrity: sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==}
|
||||
|
||||
'@mapbox/tiny-sdf@2.0.6':
|
||||
resolution: {integrity: sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==}
|
||||
|
||||
'@mapbox/unitbezier@0.0.1':
|
||||
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
|
||||
|
||||
'@mapbox/vector-tile@1.3.1':
|
||||
resolution: {integrity: sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==}
|
||||
|
||||
'@mapbox/whoots-js@3.1.0':
|
||||
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@maplibre/maplibre-gl-style-spec@20.4.0':
|
||||
resolution: {integrity: sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==}
|
||||
hasBin: true
|
||||
|
||||
'@material/mwc-icon@0.25.3':
|
||||
resolution: {integrity: sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==}
|
||||
deprecated: MWC beta is longer supported. Please upgrade to @material/web
|
||||
@@ -1771,24 +1740,12 @@ packages:
|
||||
'@types/fs-extra@9.0.13':
|
||||
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
||||
|
||||
'@types/geojson-vt@3.2.5':
|
||||
resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==}
|
||||
|
||||
'@types/geojson@7946.0.14':
|
||||
resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==}
|
||||
|
||||
'@types/json5@0.0.29':
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
|
||||
'@types/linkify-it@5.0.0':
|
||||
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
||||
|
||||
'@types/mapbox__point-geometry@0.1.4':
|
||||
resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==}
|
||||
|
||||
'@types/mapbox__vector-tile@1.3.4':
|
||||
resolution: {integrity: sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==}
|
||||
|
||||
'@types/markdown-it@12.2.3':
|
||||
resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==}
|
||||
|
||||
@@ -1798,12 +1755,6 @@ packages:
|
||||
'@types/node@22.7.0':
|
||||
resolution: {integrity: sha512-MOdOibwBs6KW1vfqz2uKMlxq5xAfAZ98SZjO8e3XnAbFnTJtAspqhWk7hrdSAs9/Y14ZWMiy7/MxMUzAOadYEw==}
|
||||
|
||||
'@types/pbf@3.0.5':
|
||||
resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==}
|
||||
|
||||
'@types/supercluster@7.1.3':
|
||||
resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==}
|
||||
|
||||
'@types/trusted-types@2.0.2':
|
||||
resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==}
|
||||
|
||||
@@ -2609,9 +2560,6 @@ packages:
|
||||
resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
earcut@3.0.0:
|
||||
resolution: {integrity: sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
@@ -2982,9 +2930,6 @@ packages:
|
||||
functions-have-names@1.2.3:
|
||||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||
|
||||
geojson-vt@4.0.2:
|
||||
resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==}
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
@@ -3016,9 +2961,6 @@ packages:
|
||||
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
gl-matrix@3.4.3:
|
||||
resolution: {integrity: sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -3039,10 +2981,6 @@ packages:
|
||||
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
global-prefix@4.0.0:
|
||||
resolution: {integrity: sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
global@4.4.0:
|
||||
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
|
||||
|
||||
@@ -3182,9 +3120,6 @@ packages:
|
||||
idb@8.0.0:
|
||||
resolution: {integrity: sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
ignore@5.2.4:
|
||||
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -3217,10 +3152,6 @@ packages:
|
||||
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
ini@4.1.3:
|
||||
resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
internal-slot@1.0.5:
|
||||
resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3372,10 +3303,6 @@ packages:
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
isexe@3.1.1:
|
||||
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3453,9 +3380,6 @@ packages:
|
||||
json-stable-stringify-without-jsonify@1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
|
||||
json-stringify-pretty-compact@4.0.0:
|
||||
resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==}
|
||||
|
||||
json5@1.0.2:
|
||||
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
||||
hasBin: true
|
||||
@@ -3463,9 +3387,6 @@ packages:
|
||||
jsonfile@6.1.0:
|
||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
||||
|
||||
kdbush@4.0.2:
|
||||
resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==}
|
||||
|
||||
keycode@2.2.1:
|
||||
resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==}
|
||||
|
||||
@@ -3619,10 +3540,6 @@ packages:
|
||||
resolution: {integrity: sha512-2L3MIgJynYrZ3TYMriLDLWocz15okFakV6J12HXvMXDHui2x/zgChzg1u9mFFGbbGWE+GsLpQByt4POb9Or+uA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
maplibre-gl@4.7.1:
|
||||
resolution: {integrity: sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==}
|
||||
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
|
||||
|
||||
markdown-it-anchor@8.6.7:
|
||||
resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==}
|
||||
peerDependencies:
|
||||
@@ -3755,9 +3672,6 @@ packages:
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
murmurhash-js@1.0.0:
|
||||
resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==}
|
||||
|
||||
mux.js@6.0.1:
|
||||
resolution: {integrity: sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==}
|
||||
engines: {node: '>=8', npm: '>=5'}
|
||||
@@ -3985,10 +3899,6 @@ packages:
|
||||
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
|
||||
engines: {node: '>= 14.16'}
|
||||
|
||||
pbf@3.3.0:
|
||||
resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==}
|
||||
hasBin: true
|
||||
|
||||
picocolors@1.0.1:
|
||||
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
|
||||
|
||||
@@ -4253,9 +4163,6 @@ packages:
|
||||
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
potpack@2.0.0:
|
||||
resolution: {integrity: sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==}
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -4328,9 +4235,6 @@ packages:
|
||||
proto-list@1.2.4:
|
||||
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
||||
|
||||
protocol-buffers-schema@3.6.0:
|
||||
resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
@@ -4355,12 +4259,6 @@ packages:
|
||||
resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
quickselect@2.0.0:
|
||||
resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==}
|
||||
|
||||
quickselect@3.0.0:
|
||||
resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==}
|
||||
|
||||
radix-vue@1.9.9:
|
||||
resolution: {integrity: sha512-DuL2o7jxNjzlSP5Ko+kJgrW5db+jC3RlnYQIs3WITTqgzfdeP7hXjcqIUveY1f0uXRpOAN3OAd5MZ/SpRyQzQQ==}
|
||||
peerDependencies:
|
||||
@@ -4409,9 +4307,6 @@ packages:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
resolve-protobuf-schema@2.1.0:
|
||||
resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==}
|
||||
|
||||
resolve@1.22.8:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
@@ -4456,9 +4351,6 @@ packages:
|
||||
rust-result@1.0.0:
|
||||
resolution: {integrity: sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==}
|
||||
|
||||
rw@1.3.3:
|
||||
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
||||
|
||||
sade@1.8.1:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4696,9 +4588,6 @@ packages:
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
|
||||
supercluster@8.0.1:
|
||||
resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4777,9 +4666,6 @@ packages:
|
||||
resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
||||
tinyqueue@3.0.0:
|
||||
resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==}
|
||||
|
||||
tinyspy@3.0.2:
|
||||
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -5037,9 +4923,6 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vt-pbf@3.1.3:
|
||||
resolution: {integrity: sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==}
|
||||
|
||||
vue-chartjs@5.3.1:
|
||||
resolution: {integrity: sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==}
|
||||
peerDependencies:
|
||||
@@ -5229,11 +5112,6 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
which@4.0.0:
|
||||
resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==}
|
||||
engines: {node: ^16.13.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6040,35 +5918,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@lukeed/csprng': 1.0.1
|
||||
|
||||
'@mapbox/geojson-rewind@0.5.2':
|
||||
dependencies:
|
||||
get-stream: 6.0.1
|
||||
minimist: 1.2.8
|
||||
|
||||
'@mapbox/jsonlint-lines-primitives@2.0.2': {}
|
||||
|
||||
'@mapbox/point-geometry@0.1.0': {}
|
||||
|
||||
'@mapbox/tiny-sdf@2.0.6': {}
|
||||
|
||||
'@mapbox/unitbezier@0.0.1': {}
|
||||
|
||||
'@mapbox/vector-tile@1.3.1':
|
||||
dependencies:
|
||||
'@mapbox/point-geometry': 0.1.0
|
||||
|
||||
'@mapbox/whoots-js@3.1.0': {}
|
||||
|
||||
'@maplibre/maplibre-gl-style-spec@20.4.0':
|
||||
dependencies:
|
||||
'@mapbox/jsonlint-lines-primitives': 2.0.2
|
||||
'@mapbox/unitbezier': 0.0.1
|
||||
json-stringify-pretty-compact: 4.0.0
|
||||
minimist: 1.2.8
|
||||
quickselect: 2.0.0
|
||||
rw: 1.3.3
|
||||
tinyqueue: 3.0.0
|
||||
|
||||
'@material/mwc-icon@0.25.3':
|
||||
dependencies:
|
||||
lit: 2.2.6
|
||||
@@ -6910,24 +6759,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 22.7.0
|
||||
|
||||
'@types/geojson-vt@3.2.5':
|
||||
dependencies:
|
||||
'@types/geojson': 7946.0.14
|
||||
|
||||
'@types/geojson@7946.0.14': {}
|
||||
|
||||
'@types/json5@0.0.29': {}
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
|
||||
'@types/mapbox__point-geometry@0.1.4': {}
|
||||
|
||||
'@types/mapbox__vector-tile@1.3.4':
|
||||
dependencies:
|
||||
'@types/geojson': 7946.0.14
|
||||
'@types/mapbox__point-geometry': 0.1.4
|
||||
'@types/pbf': 3.0.5
|
||||
|
||||
'@types/markdown-it@12.2.3':
|
||||
dependencies:
|
||||
'@types/linkify-it': 5.0.0
|
||||
@@ -6939,12 +6774,6 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
'@types/pbf@3.0.5': {}
|
||||
|
||||
'@types/supercluster@7.1.3':
|
||||
dependencies:
|
||||
'@types/geojson': 7946.0.14
|
||||
|
||||
'@types/trusted-types@2.0.2': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
@@ -7878,8 +7707,6 @@ snapshots:
|
||||
|
||||
dset@3.1.4: {}
|
||||
|
||||
earcut@3.0.0: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
editorconfig@1.0.4:
|
||||
@@ -8407,8 +8234,6 @@ snapshots:
|
||||
|
||||
functions-have-names@1.2.3: {}
|
||||
|
||||
geojson-vt@4.0.2: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-east-asian-width@1.2.0: {}
|
||||
@@ -8438,8 +8263,6 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
gl-matrix@3.4.3: {}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -8470,12 +8293,6 @@ snapshots:
|
||||
dependencies:
|
||||
ini: 4.1.1
|
||||
|
||||
global-prefix@4.0.0:
|
||||
dependencies:
|
||||
ini: 4.1.3
|
||||
kind-of: 6.0.3
|
||||
which: 4.0.0
|
||||
|
||||
global@4.4.0:
|
||||
dependencies:
|
||||
min-document: 2.19.0
|
||||
@@ -8671,8 +8488,6 @@ snapshots:
|
||||
|
||||
idb@8.0.0: {}
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.2.4: {}
|
||||
|
||||
immutable@4.3.7:
|
||||
@@ -8698,8 +8513,6 @@ snapshots:
|
||||
|
||||
ini@4.1.1: {}
|
||||
|
||||
ini@4.1.3: {}
|
||||
|
||||
internal-slot@1.0.5:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
@@ -8832,8 +8645,6 @@ snapshots:
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
isexe@3.1.1: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
@@ -8955,8 +8766,6 @@ snapshots:
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||
|
||||
json-stringify-pretty-compact@4.0.0: {}
|
||||
|
||||
json5@1.0.2:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
@@ -8967,8 +8776,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
||||
kdbush@4.0.2: {}
|
||||
|
||||
keycode@2.2.1: {}
|
||||
|
||||
keyv@4.5.3:
|
||||
@@ -9142,35 +8949,6 @@ snapshots:
|
||||
|
||||
map-obj@5.0.0: {}
|
||||
|
||||
maplibre-gl@4.7.1:
|
||||
dependencies:
|
||||
'@mapbox/geojson-rewind': 0.5.2
|
||||
'@mapbox/jsonlint-lines-primitives': 2.0.2
|
||||
'@mapbox/point-geometry': 0.1.0
|
||||
'@mapbox/tiny-sdf': 2.0.6
|
||||
'@mapbox/unitbezier': 0.0.1
|
||||
'@mapbox/vector-tile': 1.3.1
|
||||
'@mapbox/whoots-js': 3.1.0
|
||||
'@maplibre/maplibre-gl-style-spec': 20.4.0
|
||||
'@types/geojson': 7946.0.14
|
||||
'@types/geojson-vt': 3.2.5
|
||||
'@types/mapbox__point-geometry': 0.1.4
|
||||
'@types/mapbox__vector-tile': 1.3.4
|
||||
'@types/pbf': 3.0.5
|
||||
'@types/supercluster': 7.1.3
|
||||
earcut: 3.0.0
|
||||
geojson-vt: 4.0.2
|
||||
gl-matrix: 3.4.3
|
||||
global-prefix: 4.0.0
|
||||
kdbush: 4.0.2
|
||||
murmurhash-js: 1.0.0
|
||||
pbf: 3.3.0
|
||||
potpack: 2.0.0
|
||||
quickselect: 3.0.0
|
||||
supercluster: 8.0.1
|
||||
tinyqueue: 3.0.0
|
||||
vt-pbf: 3.1.3
|
||||
|
||||
markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2):
|
||||
dependencies:
|
||||
'@types/markdown-it': 12.2.3
|
||||
@@ -9297,8 +9075,6 @@ snapshots:
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
murmurhash-js@1.0.0: {}
|
||||
|
||||
mux.js@6.0.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.6
|
||||
@@ -9519,11 +9295,6 @@ snapshots:
|
||||
|
||||
pathval@2.0.0: {}
|
||||
|
||||
pbf@3.3.0:
|
||||
dependencies:
|
||||
ieee754: 1.2.1
|
||||
resolve-protobuf-schema: 2.1.0
|
||||
|
||||
picocolors@1.0.1: {}
|
||||
|
||||
picocolors@1.1.0: {}
|
||||
@@ -9814,8 +9585,6 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
potpack@2.0.0: {}
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier-linter-helpers@1.0.0:
|
||||
@@ -9921,8 +9690,6 @@ snapshots:
|
||||
|
||||
proto-list@1.2.4: {}
|
||||
|
||||
protocol-buffers-schema@3.6.0: {}
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
psl@1.9.0: {}
|
||||
@@ -9937,10 +9704,6 @@ snapshots:
|
||||
|
||||
quick-lru@6.1.2: {}
|
||||
|
||||
quickselect@2.0.0: {}
|
||||
|
||||
quickselect@3.0.0: {}
|
||||
|
||||
radix-vue@1.9.9(vue@3.5.12(typescript@5.6.2)):
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.6.12
|
||||
@@ -9996,10 +9759,6 @@ snapshots:
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-protobuf-schema@2.1.0:
|
||||
dependencies:
|
||||
protocol-buffers-schema: 3.6.0
|
||||
|
||||
resolve@1.22.8:
|
||||
dependencies:
|
||||
is-core-module: 2.15.1
|
||||
@@ -10060,8 +9819,6 @@ snapshots:
|
||||
dependencies:
|
||||
individual: 2.0.0
|
||||
|
||||
rw@1.3.3: {}
|
||||
|
||||
sade@1.8.1:
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
@@ -10329,10 +10086,6 @@ snapshots:
|
||||
pirates: 4.0.6
|
||||
ts-interface-checker: 0.1.13
|
||||
|
||||
supercluster@8.0.1:
|
||||
dependencies:
|
||||
kdbush: 4.0.2
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
@@ -10433,8 +10186,6 @@ snapshots:
|
||||
|
||||
tinypool@1.0.0: {}
|
||||
|
||||
tinyqueue@3.0.0: {}
|
||||
|
||||
tinyspy@3.0.2: {}
|
||||
|
||||
to-fast-properties@2.0.0: {}
|
||||
@@ -10719,12 +10470,6 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vt-pbf@3.1.3:
|
||||
dependencies:
|
||||
'@mapbox/point-geometry': 0.1.0
|
||||
'@mapbox/vector-tile': 1.3.1
|
||||
pbf: 3.3.0
|
||||
|
||||
vue-chartjs@5.3.1(chart.js@4.4.4)(vue@3.5.12(typescript@5.6.2)):
|
||||
dependencies:
|
||||
chart.js: 4.4.4
|
||||
@@ -10912,10 +10657,6 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
which@4.0.0:
|
||||
dependencies:
|
||||
isexe: 3.1.1
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
|
||||
@@ -360,6 +360,21 @@ export const colors = {
|
||||
12: 'rgb(var(--teal-12) / <alpha-value>)',
|
||||
},
|
||||
|
||||
gray: {
|
||||
1: 'rgb(var(--gray-1) / <alpha-value>)',
|
||||
2: 'rgb(var(--gray-2) / <alpha-value>)',
|
||||
3: 'rgb(var(--gray-3) / <alpha-value>)',
|
||||
4: 'rgb(var(--gray-4) / <alpha-value>)',
|
||||
5: 'rgb(var(--gray-5) / <alpha-value>)',
|
||||
6: 'rgb(var(--gray-6) / <alpha-value>)',
|
||||
7: 'rgb(var(--gray-7) / <alpha-value>)',
|
||||
8: 'rgb(var(--gray-8) / <alpha-value>)',
|
||||
9: 'rgb(var(--gray-9) / <alpha-value>)',
|
||||
10: 'rgb(var(--gray-10) / <alpha-value>)',
|
||||
11: 'rgb(var(--gray-11) / <alpha-value>)',
|
||||
12: 'rgb(var(--gray-12) / <alpha-value>)',
|
||||
},
|
||||
|
||||
black: '#000000',
|
||||
brand: '#2781F6',
|
||||
background: 'rgb(var(--background-color) / <alpha-value>)',
|
||||
|
||||
Reference in New Issue
Block a user