Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99ef76562b | ||
|
|
3b29214dfc |
@@ -94,6 +94,3 @@ yarn-debug.log*
|
|||||||
.vscode
|
.vscode
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
.cursor
|
.cursor
|
||||||
|
|
||||||
# react component
|
|
||||||
dist
|
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ class ApiClient {
|
|||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
get accountIdFromRoute() {
|
get accountIdFromRoute() {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
if (window.__WOOT_ACCOUNT_ID__) {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return window.__WOOT_ACCOUNT_ID__;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isInsideAccountScopedURLs =
|
const isInsideAccountScopedURLs =
|
||||||
window.location.pathname.includes('/app/accounts');
|
window.location.pathname.includes('/app/accounts');
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,7 @@ import {
|
|||||||
} from '../store/utils/api';
|
} from '../store/utils/api';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async validityCheck() {
|
validityCheck() {
|
||||||
if (this.hasAuthToken()) {
|
|
||||||
const urlData = endPoints('profileUpdate');
|
|
||||||
const response = await axios.get(urlData.url);
|
|
||||||
// to match the response signature of the validityCheck endpoint
|
|
||||||
return Promise.resolve({ data: { payload: response } });
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlData = endPoints('validityCheck');
|
const urlData = endPoints('validityCheck');
|
||||||
return axios.get(urlData.url);
|
return axios.get(urlData.url);
|
||||||
},
|
},
|
||||||
@@ -38,10 +31,6 @@ export default {
|
|||||||
hasAuthCookie() {
|
hasAuthCookie() {
|
||||||
return !!Cookies.get('cw_d_session_info');
|
return !!Cookies.get('cw_d_session_info');
|
||||||
},
|
},
|
||||||
hasAuthToken() {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return !!window.__WOOT_ACCESS_TOKEN__;
|
|
||||||
},
|
|
||||||
getAuthData() {
|
getAuthData() {
|
||||||
if (this.hasAuthCookie()) {
|
if (this.hasAuthCookie()) {
|
||||||
const savedAuthInfo = Cookies.get('cw_d_session_info');
|
const savedAuthInfo = Cookies.get('cw_d_session_info');
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
/* global axios */
|
/* global axios */
|
||||||
import CacheEnabledApiClient from './CacheEnabledApiClient';
|
import CacheEnabledApiClient from './CacheEnabledApiClient';
|
||||||
// import ApiClient from './ApiClient';
|
|
||||||
import ApiClient from './ApiClient';
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
class Inboxes extends CacheEnabledApiClient {
|
||||||
const BaseClass = window.__WOOT_ISOLATED_SHELL__
|
|
||||||
? ApiClient
|
|
||||||
: CacheEnabledApiClient;
|
|
||||||
|
|
||||||
class Inboxes extends BaseClass {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super('inboxes', { accountScoped: true });
|
super('inboxes', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,9 +315,6 @@ const componentToRender = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const shouldShowContextMenu = computed(() => {
|
const shouldShowContextMenu = computed(() => {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
if (window.__WOOT_ISOLATED_SHELL__) return false;
|
|
||||||
|
|
||||||
return !props.contentAttributes?.isUnsupported;
|
return !props.contentAttributes?.isUnsupported;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -444,7 +441,7 @@ const avatarTooltip = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const setupHighlightTimer = () => {
|
const setupHighlightTimer = () => {
|
||||||
if (Number(route?.query?.messageId) !== Number(props.id)) {
|
if (Number(route.query.messageId) !== Number(props.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg
|
|
||||||
class="w-8 h-4"
|
|
||||||
viewBox="0 0 120 30"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<circle cx="15" cy="15" r="12">
|
|
||||||
<animate
|
|
||||||
attributeName="cy"
|
|
||||||
from="15"
|
|
||||||
to="5"
|
|
||||||
begin="0s"
|
|
||||||
dur="0.9s"
|
|
||||||
values="15;5;15;15;15;15"
|
|
||||||
calcMode="linear"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
<circle cx="55" cy="15" r="12">
|
|
||||||
<animate
|
|
||||||
attributeName="cy"
|
|
||||||
from="15"
|
|
||||||
to="5"
|
|
||||||
begin="0.1s"
|
|
||||||
dur="0.9s"
|
|
||||||
values="15;5;15;15;15;15"
|
|
||||||
calcMode="linear"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
<circle cx="95" cy="15" r="12">
|
|
||||||
<animate
|
|
||||||
attributeName="cy"
|
|
||||||
from="15"
|
|
||||||
to="5"
|
|
||||||
begin="0.2s"
|
|
||||||
dur="0.9s"
|
|
||||||
values="15;5;15;15;15;15"
|
|
||||||
calcMode="linear"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -5,7 +5,6 @@ import { useAlert } from 'dashboard/composables';
|
|||||||
import BaseBubble from './Base.vue';
|
import BaseBubble from './Base.vue';
|
||||||
import Button from 'next/button/Button.vue';
|
import Button from 'next/button/Button.vue';
|
||||||
import Icon from 'next/icon/Icon.vue';
|
import Icon from 'next/icon/Icon.vue';
|
||||||
import { useGallery } from '../useGallery';
|
|
||||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||||
import { useMessageContext } from '../provider.js';
|
import { useMessageContext } from '../provider.js';
|
||||||
import { downloadFile } from '@chatwoot/utils';
|
import { downloadFile } from '@chatwoot/utils';
|
||||||
@@ -22,8 +21,8 @@ const attachment = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hasError = ref(false);
|
const hasError = ref(false);
|
||||||
|
const showGallery = ref(false);
|
||||||
const isDownloading = ref(false);
|
const isDownloading = ref(false);
|
||||||
const { showGallery, isGalleryAllowed, toggleGallery } = useGallery();
|
|
||||||
|
|
||||||
const handleError = () => {
|
const handleError = () => {
|
||||||
hasError.value = true;
|
hasError.value = true;
|
||||||
@@ -47,7 +46,7 @@ const downloadAttachment = async () => {
|
|||||||
<BaseBubble
|
<BaseBubble
|
||||||
class="overflow-hidden p-3"
|
class="overflow-hidden p-3"
|
||||||
data-bubble-name="image"
|
data-bubble-name="image"
|
||||||
@click="toggleGallery(true)"
|
@click="showGallery = true"
|
||||||
>
|
>
|
||||||
<div v-if="hasError" class="flex items-center gap-1 text-center rounded-lg">
|
<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" />
|
<Icon icon="i-lucide-circle-off" class="text-n-slate-11" />
|
||||||
@@ -83,7 +82,7 @@ const downloadAttachment = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</BaseBubble>
|
</BaseBubble>
|
||||||
<GalleryView
|
<GalleryView
|
||||||
v-if="showGallery && isGalleryAllowed"
|
v-if="showGallery"
|
||||||
v-model:show="showGallery"
|
v-model:show="showGallery"
|
||||||
:attachment="useSnakeCase(attachment)"
|
:attachment="useSnakeCase(attachment)"
|
||||||
:all-attachments="filteredCurrentChatAttachments"
|
:all-attachments="filteredCurrentChatAttachments"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import BaseBubble from './Base.vue';
|
import BaseBubble from './Base.vue';
|
||||||
import Icon from 'next/icon/Icon.vue';
|
import Icon from 'next/icon/Icon.vue';
|
||||||
import { useGallery } from '../useGallery';
|
|
||||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||||
import { useMessageContext } from '../provider.js';
|
import { useMessageContext } from '../provider.js';
|
||||||
import GalleryView from 'dashboard/components/widgets/conversation/components/GalleryView.vue';
|
import GalleryView from 'dashboard/components/widgets/conversation/components/GalleryView.vue';
|
||||||
@@ -10,7 +9,7 @@ import { ATTACHMENT_TYPES } from '../constants';
|
|||||||
|
|
||||||
const emit = defineEmits(['error']);
|
const emit = defineEmits(['error']);
|
||||||
const hasError = ref(false);
|
const hasError = ref(false);
|
||||||
const { showGallery, isGalleryAllowed, toggleGallery } = useGallery();
|
const showGallery = ref(false);
|
||||||
const { filteredCurrentChatAttachments, attachments } = useMessageContext();
|
const { filteredCurrentChatAttachments, attachments } = useMessageContext();
|
||||||
|
|
||||||
const handleError = () => {
|
const handleError = () => {
|
||||||
@@ -31,7 +30,7 @@ const isReel = computed(() => {
|
|||||||
<BaseBubble
|
<BaseBubble
|
||||||
class="overflow-hidden p-3"
|
class="overflow-hidden p-3"
|
||||||
data-bubble-name="video"
|
data-bubble-name="video"
|
||||||
@click="toggleGallery(true)"
|
@click="showGallery = true"
|
||||||
>
|
>
|
||||||
<div class="relative group rounded-lg overflow-hidden">
|
<div class="relative group rounded-lg overflow-hidden">
|
||||||
<div
|
<div
|
||||||
@@ -53,7 +52,7 @@ const isReel = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</BaseBubble>
|
</BaseBubble>
|
||||||
<GalleryView
|
<GalleryView
|
||||||
v-if="showGallery && isGalleryAllowed"
|
v-if="showGallery"
|
||||||
v-model:show="showGallery"
|
v-model:show="showGallery"
|
||||||
:attachment="useSnakeCase(attachment)"
|
:attachment="useSnakeCase(attachment)"
|
||||||
:all-attachments="filteredCurrentChatAttachments"
|
:all-attachments="filteredCurrentChatAttachments"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import Icon from 'next/icon/Icon.vue';
|
import Icon from 'next/icon/Icon.vue';
|
||||||
import { useGallery } from '../useGallery';
|
|
||||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||||
import { useMessageContext } from '../provider.js';
|
import { useMessageContext } from '../provider.js';
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const hasError = ref(false);
|
const hasError = ref(false);
|
||||||
const { showGallery, toggleGallery, isGalleryAllowed } = useGallery();
|
const showGallery = ref(false);
|
||||||
|
|
||||||
const { filteredCurrentChatAttachments } = useMessageContext();
|
const { filteredCurrentChatAttachments } = useMessageContext();
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ const handleError = () => {
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="size-[72px] overflow-hidden contain-content rounded-xl cursor-pointer"
|
class="size-[72px] overflow-hidden contain-content rounded-xl cursor-pointer"
|
||||||
@click="toggleGallery(true)"
|
@click="showGallery = true"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="hasError"
|
v-if="hasError"
|
||||||
@@ -43,7 +42,7 @@ const handleError = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<GalleryView
|
<GalleryView
|
||||||
v-if="showGallery && isGalleryAllowed"
|
v-if="showGallery"
|
||||||
v-model:show="showGallery"
|
v-model:show="showGallery"
|
||||||
:attachment="useSnakeCase(attachment)"
|
:attachment="useSnakeCase(attachment)"
|
||||||
:all-attachments="filteredCurrentChatAttachments"
|
:all-attachments="filteredCurrentChatAttachments"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
import Icon from 'next/icon/Icon.vue';
|
import Icon from 'next/icon/Icon.vue';
|
||||||
import { useGallery } from '../useGallery';
|
|
||||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||||
import { useMessageContext } from '../provider.js';
|
import { useMessageContext } from '../provider.js';
|
||||||
import GalleryView from 'dashboard/components/widgets/conversation/components/GalleryView.vue';
|
import GalleryView from 'dashboard/components/widgets/conversation/components/GalleryView.vue';
|
||||||
@@ -12,7 +12,7 @@ defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { showGallery, isGalleryAllowed, toggleGallery } = useGallery();
|
const showGallery = ref(false);
|
||||||
|
|
||||||
const { filteredCurrentChatAttachments } = useMessageContext();
|
const { filteredCurrentChatAttachments } = useMessageContext();
|
||||||
</script>
|
</script>
|
||||||
@@ -20,7 +20,7 @@ const { filteredCurrentChatAttachments } = useMessageContext();
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="size-[72px] overflow-hidden contain-content rounded-xl cursor-pointer relative group"
|
class="size-[72px] overflow-hidden contain-content rounded-xl cursor-pointer relative group"
|
||||||
@click="toggleGallery(true)"
|
@click="showGallery = true"
|
||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
:src="attachment.dataUrl"
|
:src="attachment.dataUrl"
|
||||||
@@ -42,7 +42,7 @@ const { filteredCurrentChatAttachments } = useMessageContext();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GalleryView
|
<GalleryView
|
||||||
v-if="showGallery && isGalleryAllowed"
|
v-if="showGallery"
|
||||||
v-model:show="showGallery"
|
v-model:show="showGallery"
|
||||||
:attachment="useSnakeCase(attachment)"
|
:attachment="useSnakeCase(attachment)"
|
||||||
:all-attachments="filteredCurrentChatAttachments"
|
:all-attachments="filteredCurrentChatAttachments"
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { computed } from 'vue';
|
|
||||||
import { useToggle } from '@vueuse/core';
|
|
||||||
|
|
||||||
export function useGallery() {
|
|
||||||
const [showGallery] = useToggle(false);
|
|
||||||
|
|
||||||
const isGalleryAllowed = computed(() => {
|
|
||||||
return true;
|
|
||||||
// return !window.__WOOT_ISOLATED_SHELL__;
|
|
||||||
});
|
|
||||||
|
|
||||||
const toggleGallery = value => {
|
|
||||||
if (!isGalleryAllowed.value) return;
|
|
||||||
|
|
||||||
showGallery.value = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
showGallery,
|
|
||||||
isGalleryAllowed,
|
|
||||||
toggleGallery,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -170,10 +170,6 @@ const shouldShowCannedResponses = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const editorMenuOptions = computed(() => {
|
const editorMenuOptions = computed(() => {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
if (window.__WOOT_ISOLATED_SHELL__) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return props.enabledMenuOptions.length
|
return props.enabledMenuOptions.length
|
||||||
? props.enabledMenuOptions
|
? props.enabledMenuOptions
|
||||||
: MESSAGE_EDITOR_MENU_OPTIONS;
|
: MESSAGE_EDITOR_MENU_OPTIONS;
|
||||||
@@ -426,14 +422,10 @@ function updateImgToolbarOnDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isEnterToSendEnabled() {
|
function isEnterToSendEnabled() {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
if (window.__WOOT_ISOLATED_SHELL__) return false;
|
|
||||||
return isEditorHotKeyEnabled('enter');
|
return isEditorHotKeyEnabled('enter');
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCmdPlusEnterToSendEnabled() {
|
function isCmdPlusEnterToSendEnabled() {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
if (window.__WOOT_ISOLATED_SHELL__) return true;
|
|
||||||
return isEditorHotKeyEnabled('cmd_enter');
|
return isEditorHotKeyEnabled('cmd_enter');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,7 +653,7 @@ watch(sendWithSignature, newValue => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
// [VITE] state assignment was done in created before
|
// [VITE] state assignment was done in created before
|
||||||
state = createState(
|
state = createState(
|
||||||
props.modelValue,
|
props.modelValue,
|
||||||
@@ -742,10 +734,6 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
|
|||||||
.ProseMirror-menubar-wrapper {
|
.ProseMirror-menubar-wrapper {
|
||||||
@apply flex flex-col;
|
@apply flex flex-col;
|
||||||
|
|
||||||
.ProseMirror-menubar:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror-menubar {
|
.ProseMirror-menubar {
|
||||||
min-height: var(--space-two) !important;
|
min-height: var(--space-two) !important;
|
||||||
@apply -ml-2.5 pb-0 bg-transparent text-n-slate-11;
|
@apply -ml-2.5 pb-0 bg-transparent text-n-slate-11;
|
||||||
|
|||||||
@@ -55,16 +55,10 @@ const translateValue = computed(() => {
|
|||||||
class="flex items-center w-auto h-8 p-1 transition-all border rounded-full bg-n-alpha-2 group relative duration-300 ease-in-out z-0"
|
class="flex items-center w-auto h-8 p-1 transition-all border rounded-full bg-n-alpha-2 group relative duration-300 ease-in-out z-0"
|
||||||
@click="$emit('toggleMode')"
|
@click="$emit('toggleMode')"
|
||||||
>
|
>
|
||||||
<div
|
<div ref="wootEditorReplyMode" class="flex items-center gap-1 px-2 z-20">
|
||||||
ref="wootEditorReplyMode"
|
|
||||||
class="flex items-center gap-1 px-2 z-20 text-n-slate-11"
|
|
||||||
>
|
|
||||||
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
|
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div ref="wootEditorPrivateMode" class="flex items-center gap-1 px-2 z-20">
|
||||||
ref="wootEditorPrivateMode"
|
|
||||||
class="flex items-center gap-1 px-2 z-20 text-n-slate-11"
|
|
||||||
>
|
|
||||||
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -118,12 +118,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
allowSignature: { type: Boolean, default: false },
|
|
||||||
allowEmoji: { type: Boolean, default: false },
|
|
||||||
allowAiAssist: { type: Boolean, default: false },
|
|
||||||
allowVideoCall: { type: Boolean, default: false },
|
|
||||||
allowFileUpload: { type: Boolean, default: false },
|
|
||||||
allowAudioRecorder: { type: Boolean, default: false },
|
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'replaceText',
|
'replaceText',
|
||||||
@@ -270,7 +264,6 @@ export default {
|
|||||||
<div class="flex justify-between p-3" :class="wrapClass">
|
<div class="flex justify-between p-3" :class="wrapClass">
|
||||||
<div class="left-wrap">
|
<div class="left-wrap">
|
||||||
<NextButton
|
<NextButton
|
||||||
v-if="allowEmoji"
|
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||||
icon="i-ph-smiley-sticker"
|
icon="i-ph-smiley-sticker"
|
||||||
slate
|
slate
|
||||||
@@ -279,7 +272,6 @@ export default {
|
|||||||
@click="toggleEmojiPicker"
|
@click="toggleEmojiPicker"
|
||||||
/>
|
/>
|
||||||
<FileUpload
|
<FileUpload
|
||||||
v-if="allowFileUpload"
|
|
||||||
ref="uploadRef"
|
ref="uploadRef"
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||||
input-id="conversationAttachment"
|
input-id="conversationAttachment"
|
||||||
@@ -303,40 +295,36 @@ export default {
|
|||||||
sm
|
sm
|
||||||
/>
|
/>
|
||||||
</FileUpload>
|
</FileUpload>
|
||||||
<template v-if="allowAudioRecorder">
|
|
||||||
<NextButton
|
|
||||||
v-if="showAudioRecorderButton"
|
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
|
|
||||||
:icon="
|
|
||||||
!isRecordingAudio ? 'i-ph-microphone' : 'i-ph-microphone-slash'
|
|
||||||
"
|
|
||||||
slate
|
|
||||||
faded
|
|
||||||
sm
|
|
||||||
@click="toggleAudioRecorder"
|
|
||||||
/>
|
|
||||||
<NextButton
|
|
||||||
v-if="showEditorToggle"
|
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
|
||||||
icon="i-ph-quotes"
|
|
||||||
slate
|
|
||||||
faded
|
|
||||||
sm
|
|
||||||
@click="$emit('toggleEditor')"
|
|
||||||
/>
|
|
||||||
<NextButton
|
|
||||||
v-if="showAudioPlayStopButton"
|
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
|
||||||
:icon="audioRecorderPlayStopIcon"
|
|
||||||
slate
|
|
||||||
faded
|
|
||||||
sm
|
|
||||||
:label="recordingAudioDurationText"
|
|
||||||
@click="toggleAudioRecorderPlayPause"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<NextButton
|
<NextButton
|
||||||
v-if="allowSignature && showMessageSignatureButton"
|
v-if="showAudioRecorderButton"
|
||||||
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
|
||||||
|
:icon="!isRecordingAudio ? 'i-ph-microphone' : 'i-ph-microphone-slash'"
|
||||||
|
slate
|
||||||
|
faded
|
||||||
|
sm
|
||||||
|
@click="toggleAudioRecorder"
|
||||||
|
/>
|
||||||
|
<NextButton
|
||||||
|
v-if="showEditorToggle"
|
||||||
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||||
|
icon="i-ph-quotes"
|
||||||
|
slate
|
||||||
|
faded
|
||||||
|
sm
|
||||||
|
@click="$emit('toggleEditor')"
|
||||||
|
/>
|
||||||
|
<NextButton
|
||||||
|
v-if="showAudioPlayStopButton"
|
||||||
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||||
|
:icon="audioRecorderPlayStopIcon"
|
||||||
|
slate
|
||||||
|
faded
|
||||||
|
sm
|
||||||
|
:label="recordingAudioDurationText"
|
||||||
|
@click="toggleAudioRecorderPlayPause"
|
||||||
|
/>
|
||||||
|
<NextButton
|
||||||
|
v-if="showMessageSignatureButton"
|
||||||
v-tooltip.top-end="signatureToggleTooltip"
|
v-tooltip.top-end="signatureToggleTooltip"
|
||||||
icon="i-ph-signature"
|
icon="i-ph-signature"
|
||||||
slate
|
slate
|
||||||
@@ -354,15 +342,11 @@ export default {
|
|||||||
@click="$emit('selectWhatsappTemplate')"
|
@click="$emit('selectWhatsappTemplate')"
|
||||||
/>
|
/>
|
||||||
<VideoCallButton
|
<VideoCallButton
|
||||||
v-if="
|
v-if="(isAWebWidgetInbox || isAPIInbox) && !isOnPrivateNote"
|
||||||
allowVideoCall &&
|
|
||||||
(isAWebWidgetInbox || isAPIInbox) &&
|
|
||||||
!isOnPrivateNote
|
|
||||||
"
|
|
||||||
:conversation-id="conversationId"
|
:conversation-id="conversationId"
|
||||||
/>
|
/>
|
||||||
<AIAssistanceButton
|
<AIAssistanceButton
|
||||||
v-if="allowAiAssist && !isFetchingAppIntegrations"
|
v-if="!isFetchingAppIntegrations"
|
||||||
:conversation-id="conversationId"
|
:conversation-id="conversationId"
|
||||||
:is-private-note="isOnPrivateNote"
|
:is-private-note="isOnPrivateNote"
|
||||||
:message="message"
|
:message="message"
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: REPLY_EDITOR_MODES.REPLY,
|
default: REPLY_EDITOR_MODES.REPLY,
|
||||||
},
|
},
|
||||||
disablePopout: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isMessageLengthReachingThreshold: {
|
isMessageLengthReachingThreshold: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: () => false,
|
default: () => false,
|
||||||
@@ -89,7 +85,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex justify-between h-[3.25rem] gap-2 ms-3">
|
<div class="flex justify-between h-[3.25rem] gap-2 ltr:pl-3 rtl:pr-3">
|
||||||
<EditorModeToggle
|
<EditorModeToggle
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
@@ -103,7 +99,6 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NextButton
|
<NextButton
|
||||||
v-if="!disablePopout"
|
|
||||||
ghost
|
ghost
|
||||||
class="ltr:rounded-bl-md rtl:rounded-br-md ltr:rounded-br-none rtl:rounded-bl-none ltr:rounded-tl-none rtl:rounded-tr-none text-n-slate-11 ltr:rounded-tr-[11px] rtl:rounded-tl-[11px]"
|
class="ltr:rounded-bl-md rtl:rounded-br-md ltr:rounded-br-none rtl:rounded-bl-none ltr:rounded-tl-none rtl:rounded-tr-none text-n-slate-11 ltr:rounded-tr-[11px] rtl:rounded-tl-[11px]"
|
||||||
icon="i-lucide-maximize-2"
|
icon="i-lucide-maximize-2"
|
||||||
|
|||||||
@@ -1240,12 +1240,6 @@ export default {
|
|||||||
:message="message"
|
:message="message"
|
||||||
:portal-slug="connectedPortalSlug"
|
:portal-slug="connectedPortalSlug"
|
||||||
:new-conversation-modal-active="newConversationModalActive"
|
:new-conversation-modal-active="newConversationModalActive"
|
||||||
allow-signature
|
|
||||||
allow-emoji
|
|
||||||
allow-ai-assist
|
|
||||||
allow-video-call
|
|
||||||
allow-audio-recorder
|
|
||||||
allow-file-upload
|
|
||||||
@select-whatsapp-template="openWhatsappTemplateModal"
|
@select-whatsapp-template="openWhatsappTemplateModal"
|
||||||
@toggle-editor="toggleRichContentEditor"
|
@toggle-editor="toggleRichContentEditor"
|
||||||
@replace-text="replaceText"
|
@replace-text="replaceText"
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex-1 mx-2 px-2 truncate text-sm font-medium text-center text-n-slate-12 hidden sm:block"
|
class="flex-1 mx-2 px-2 truncate text-sm font-medium text-center text-n-slate-12"
|
||||||
>
|
>
|
||||||
<span v-dompurify-html="fileNameFromDataUrl" class="truncate" />
|
<span v-dompurify-html="fileNameFromDataUrl" class="truncate" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ import { computed, unref } from 'vue';
|
|||||||
import { getCurrentInstance } from 'vue';
|
import { getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
export const useStore = () => {
|
export const useStore = () => {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
if (window.__CHATWOOT_STORE__) {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return window.__CHATWOOT_STORE__;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vm = getCurrentInstance();
|
const vm = getCurrentInstance();
|
||||||
if (!vm) throw new Error('must be called in setup');
|
if (!vm) throw new Error('must be called in setup');
|
||||||
return vm.proxy.$store;
|
return vm.proxy.$store;
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ const { isImpersonating } = useImpersonation();
|
|||||||
class ActionCableConnector extends BaseActionCableConnector {
|
class ActionCableConnector extends BaseActionCableConnector {
|
||||||
constructor(app, pubsubToken) {
|
constructor(app, pubsubToken) {
|
||||||
const { websocketURL = '' } = window.chatwootConfig || {};
|
const { websocketURL = '' } = window.chatwootConfig || {};
|
||||||
|
super(app, pubsubToken, websocketURL);
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
const wsURL = websocketURL || window.__WEBSOCKET_URL__ || '';
|
|
||||||
super(app, pubsubToken, wsURL);
|
|
||||||
this.CancelTyping = [];
|
this.CancelTyping = [];
|
||||||
this.events = {
|
this.events = {
|
||||||
'message.created': this.onMessageCreated,
|
'message.created': this.onMessageCreated,
|
||||||
@@ -51,9 +48,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
};
|
};
|
||||||
|
|
||||||
isAValidEvent = data => {
|
isAValidEvent = data => {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
return this.app.$store.getters.getCurrentAccountId === data.account_id;
|
||||||
const currentAccountId = this.app.$store.getters.getCurrentAccountId;
|
|
||||||
return currentAccountId === data.account_id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMessageUpdated = data => {
|
onMessageUpdated = data => {
|
||||||
@@ -103,10 +98,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
conversation: { last_activity_at: lastActivityAt },
|
conversation: { last_activity_at: lastActivityAt },
|
||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
} = data;
|
} = data;
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
DashboardAudioNotificationHelper.onNewMessage(data);
|
||||||
if (!window.__WOOT_ISOLATED_SHELL__) {
|
|
||||||
DashboardAudioNotificationHelper.onNewMessage(data);
|
|
||||||
}
|
|
||||||
this.app.$store.dispatch('addMessage', data);
|
this.app.$store.dispatch('addMessage', data);
|
||||||
this.app.$store.dispatch('updateConversationLastActivity', {
|
this.app.$store.dispatch('updateConversationLastActivity', {
|
||||||
lastActivityAt,
|
lastActivityAt,
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ export const getters = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getCurrentAccountId(_, __, rootState) {
|
getCurrentAccountId(_, __, rootState) {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
if (window.__WOOT_ACCOUNT_ID__) return window.__WOOT_ACCOUNT_ID__;
|
|
||||||
|
|
||||||
if (rootState.route.params && rootState.route.params.accountId) {
|
if (rootState.route.params && rootState.route.params.accountId) {
|
||||||
return Number(rootState.route.params.accountId);
|
return Number(rootState.route.params.accountId);
|
||||||
}
|
}
|
||||||
@@ -117,7 +114,7 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setUser({ commit, dispatch }) {
|
async setUser({ commit, dispatch }) {
|
||||||
if (authAPI.hasAuthCookie() || authAPI.hasAuthToken()) {
|
if (authAPI.hasAuthCookie()) {
|
||||||
await dispatch('validityCheck');
|
await dispatch('validityCheck');
|
||||||
} else {
|
} else {
|
||||||
commit(types.CLEAR_USER);
|
commit(types.CLEAR_USER);
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import { createApp } from 'vue';
|
|
||||||
|
|
||||||
import '../dashboard/assets/scss/app.scss';
|
|
||||||
import 'vue-multiselect/dist/vue-multiselect.css';
|
|
||||||
import 'floating-vue/dist/style.css';
|
|
||||||
// import tailwindStyles from '../dashboard/assets/scss/_woot.scss?inline';
|
|
||||||
|
|
||||||
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
|
||||||
import WootUiKit from 'dashboard/components';
|
|
||||||
import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer.js';
|
|
||||||
|
|
||||||
import store from '../dashboard/store';
|
|
||||||
import constants from '../dashboard/constants/globals';
|
|
||||||
import axios from 'axios';
|
|
||||||
import createAxios from '../ui/axios';
|
|
||||||
import commonHelpers from '../dashboard/helper/commons';
|
|
||||||
import vueActionCable from '../dashboard/helper/actionCable';
|
|
||||||
|
|
||||||
import MessageList from '../ui/MessageList.vue';
|
|
||||||
import en from '../dashboard/i18n/locale/en';
|
|
||||||
import { createI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
const i18n = createI18n({
|
|
||||||
legacy: false,
|
|
||||||
locale: 'en',
|
|
||||||
messages: {
|
|
||||||
en,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const init = async () => {
|
|
||||||
commonHelpers();
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
window.__CHATWOOT_STORE__ = store;
|
|
||||||
window.WootConstants = constants;
|
|
||||||
window.axios = createAxios(axios);
|
|
||||||
|
|
||||||
return store.dispatch('setUser').then(() => {
|
|
||||||
const app = createApp(MessageList);
|
|
||||||
|
|
||||||
app.use(store);
|
|
||||||
app.use(i18n);
|
|
||||||
app.use(WootUiKit);
|
|
||||||
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
vueActionCable.init(store, window.__PUBSUB_TOKEN__);
|
|
||||||
|
|
||||||
app.mount('#app');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof window.chatwootCallback === 'function') {
|
|
||||||
window.chatwootCallback(init);
|
|
||||||
} else {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// scss-lint:disable SpaceAfterPropertyColon
|
|
||||||
@import 'tailwindcss/base';
|
@import 'tailwindcss/base';
|
||||||
@import 'tailwindcss/components';
|
@import 'tailwindcss/components';
|
||||||
@import 'tailwindcss/utilities';
|
@import 'tailwindcss/utilities';
|
||||||
@@ -8,21 +7,7 @@
|
|||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
font-family:
|
font-family: 'InterDisplay', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
'InterDisplay',
|
|
||||||
-apple-system,
|
|
||||||
system-ui,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
'Helvetica Neue',
|
|
||||||
Tahoma,
|
|
||||||
Arial,
|
|
||||||
sans-serif,
|
|
||||||
'Noto Sans',
|
|
||||||
'Apple Color Emoji',
|
|
||||||
'Segoe UI Emoji',
|
|
||||||
'Noto Color Emoji';
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default {
|
|||||||
}
|
}
|
||||||
if (el.tag === 'h2') {
|
if (el.tag === 'h2') {
|
||||||
if (this.h1Count > 0) {
|
if (this.h1Count > 0) {
|
||||||
return 'ltr:ml-2 rtl:mr-2';
|
return 'ml-2';
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ export default {
|
|||||||
if (!this.h1Count && !this.h2Count) {
|
if (!this.h1Count && !this.h2Count) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return 'ltr:ml-5 rtl:mr-5';
|
return 'ml-5';
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
@@ -94,19 +94,17 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="hidden lg:block flex-1 py-6 scroll-mt-24 pl-4 sticky top-24">
|
||||||
class="hidden lg:block flex-1 py-6 scroll-mt-24 ltr:pl-4 rtl:pr-4 sticky top-24"
|
|
||||||
>
|
|
||||||
<div v-if="rows.length > 0" class="py-2 overflow-auto">
|
<div v-if="rows.length > 0" class="py-2 overflow-auto">
|
||||||
<nav class="max-w-2xl">
|
<nav class="max-w-2xl">
|
||||||
<ol
|
<ol
|
||||||
role="list"
|
role="list"
|
||||||
class="flex flex-col gap-2 text-base ltr:border-l-2 rtl:border-r-2 border-solid border-slate-100 dark:border-slate-800"
|
class="flex flex-col gap-2 text-base border-l-2 border-solid border-slate-100 dark:border-slate-800"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="element in rows"
|
v-for="element in rows"
|
||||||
:key="element.slug"
|
:key="element.slug"
|
||||||
class="leading-6 ltr:border-l-2 rtl:border-r-2 relative ltr:-left-0.5 rtl:-right-0.5 border-solid"
|
class="leading-6 border-l-2 relative -left-0.5 border-solid"
|
||||||
:class="elementBorderStyles(element)"
|
:class="elementBorderStyles(element)"
|
||||||
>
|
>
|
||||||
<p class="py-1 px-3" :class="getClassName(element)">
|
<p class="py-1 px-3" :class="getClassName(element)">
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import slugifyWithCounter from '@sindresorhus/slugify';
|
|||||||
import PublicArticleSearch from './components/PublicArticleSearch.vue';
|
import PublicArticleSearch from './components/PublicArticleSearch.vue';
|
||||||
import TableOfContents from './components/TableOfContents.vue';
|
import TableOfContents from './components/TableOfContents.vue';
|
||||||
import { initializeTheme } from './portalThemeHelper.js';
|
import { initializeTheme } from './portalThemeHelper.js';
|
||||||
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages.js';
|
|
||||||
|
|
||||||
export const getHeadingsfromTheArticle = () => {
|
export const getHeadingsfromTheArticle = () => {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -115,19 +114,10 @@ export const InitializationHelpers = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setDirectionAttribute: () => {
|
|
||||||
const portalElement = document.getElementById('portal');
|
|
||||||
if (!portalElement) return;
|
|
||||||
|
|
||||||
const locale = document.querySelector('.locale-switcher')?.value;
|
|
||||||
portalElement.dir = locale && getLanguageDirection(locale) ? 'rtl' : 'ltr';
|
|
||||||
},
|
|
||||||
|
|
||||||
initializeThemesInPortal: initializeTheme,
|
initializeThemesInPortal: initializeTheme,
|
||||||
|
|
||||||
initialize: () => {
|
initialize: () => {
|
||||||
openExternalLinksInNewTab();
|
openExternalLinksInNewTab();
|
||||||
InitializationHelpers.setDirectionAttribute();
|
|
||||||
if (window.portalConfig.isPlainLayoutEnabled === 'true') {
|
if (window.portalConfig.isPlainLayoutEnabled === 'true') {
|
||||||
InitializationHelpers.appendPlainParamToURLs();
|
InitializationHelpers.appendPlainParamToURLs();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
# Chatwoot React Components
|
|
||||||
|
|
||||||
This module exports Chatwoot UI components as React components that can be embedded in other applications. It uses a hybrid approach combining Vue Web Components with React wrappers.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
The system works by:
|
|
||||||
1. **Vue Components** → Compiled as custom web components using Vue's `customElement: true`
|
|
||||||
2. **React Wrappers** → Provide React-friendly interfaces to the Vue web components
|
|
||||||
3. **Vite Build** → Packages everything into distributable ES/CJS modules
|
|
||||||
|
|
||||||
## Build System
|
|
||||||
|
|
||||||
### Build Modes (vite.config.ts)
|
|
||||||
- **`BUILD_MODE=react-components`** - Builds React components library
|
|
||||||
- Entry point: `./app/javascript/react-components/src/index.jsx`
|
|
||||||
- Output: ES modules (`react-components.es.js`) and CommonJS (`react-components.cjs.js`)
|
|
||||||
- External dependencies: `react` and `react-dom` (peer dependencies)
|
|
||||||
|
|
||||||
### Available Scripts (package.json)
|
|
||||||
```bash
|
|
||||||
# Build the React components once
|
|
||||||
pnpm build:react
|
|
||||||
|
|
||||||
# Build and watch for changes during development
|
|
||||||
pnpm dev:react
|
|
||||||
|
|
||||||
# Package for NPM distribution (builds + creates dist/react-components/)
|
|
||||||
pnpm package:react
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
In any React app, version 16+, you could use the following snippet
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
import * as ChatwootComponents from '@chatwoot/agent-react-components';
|
|
||||||
import '@chatwoot/agent-react-components/style.css'
|
|
||||||
|
|
||||||
const { ChatwootProvider, ChatwootConversation } = ChatwootComponents;
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
|
||||||
<ChatwootProvider
|
|
||||||
baseURL="http://localhost:3000"
|
|
||||||
accountId="1"
|
|
||||||
conversationId={13918}
|
|
||||||
userToken="XLwgvCQiewUJMGuYpZqa4J3M"
|
|
||||||
pubsubToken="HkAhoq8aWm2ecdY2tn9FV4BU"
|
|
||||||
websocketURL="ws://localhost:3000"
|
|
||||||
>
|
|
||||||
<ChatwootConversation/>
|
|
||||||
</ChatwootProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
|
||||||
```
|
|
||||||
|
|
||||||
The `<ChatwootProvider>` component ensures all the data necessary for mounting the web component is present before rendering the ChatwootConversation.
|
|
||||||
|
|
||||||
The package also exposes a `useChatwoot` hook for accessing the config instance being used by the conversation, it can be used as a read-only source of truth for the config being used.
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
To test this package, you can create a separate React application where you can use the snippet from above, run the following command in your Rails App:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dev:react
|
|
||||||
```
|
|
||||||
|
|
||||||
This will start a development server that serves the React components and their dependencies.
|
|
||||||
|
|
||||||
Following this, in a separate terminal, run the following command
|
|
||||||
|
|
||||||
```bash
|
|
||||||
watchexec --restart -N --watch public/packs/react-components.es.js "pnpm package:react --copyMode"
|
|
||||||
```
|
|
||||||
|
|
||||||
This will ensure that the built files are copied correctly to the dist/react-components folder.
|
|
||||||
|
|
||||||
The `package:react` script has a copy mode, which just copies the build from the `public/` folder to the dist folder for linking purposes. It also creates a package.json file with proper entry points, ensure you run the script without copyMode once before.
|
|
||||||
|
|
||||||
Once done, in your React app, you can use `pnpm link <rails-app-dir>/dist/react-components` to link the package. Running the development server will automatically link the package and any changes to it.
|
|
||||||
|
|
||||||
## Publishing
|
|
||||||
|
|
||||||
The `scripts/package-react-components.js` handles:
|
|
||||||
- Building the components with Vite
|
|
||||||
- Copying built files to `dist/react-components/`
|
|
||||||
- Generating `package.json` with proper entry points
|
|
||||||
- Creating development versions with git hash suffixes
|
|
||||||
|
|
||||||
Once the script is packaged, you can run `npm publish` to publish the package. It will be published as `@chatwoot/agent-react-components` with a version that is the latest commit hash
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { useChatwoot } from './ChatwootProvider';
|
|
||||||
import { ChatwootMessageListWrapper } from './ChatwootMessageListWrapper';
|
|
||||||
|
|
||||||
export const ChatwootConversation = ({
|
|
||||||
conversationId,
|
|
||||||
className = '',
|
|
||||||
style = {},
|
|
||||||
...otherProps
|
|
||||||
}) => {
|
|
||||||
// Ensure we're inside a ChatwootProvider
|
|
||||||
useChatwoot(); // This will throw if not in Provider context
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`chatwoot-conversation ${className} text-n-slate-11`}
|
|
||||||
style={{
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
position: 'relative',
|
|
||||||
...style,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ChatwootMessageListWrapper className="h-full w-full" {...otherProps} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { useRef, useEffect } from 'react';
|
|
||||||
|
|
||||||
export const ChatwootMessageListWrapper = ({
|
|
||||||
conversationId,
|
|
||||||
className = '',
|
|
||||||
style = {},
|
|
||||||
onError = null,
|
|
||||||
onLoad = null,
|
|
||||||
...otherProps
|
|
||||||
}) => {
|
|
||||||
const elementRef = useRef();
|
|
||||||
|
|
||||||
// Update Web Component props when React props change
|
|
||||||
useEffect(() => {
|
|
||||||
if (!elementRef.current) return;
|
|
||||||
|
|
||||||
const element = elementRef.current;
|
|
||||||
|
|
||||||
// Set conversation ID on the Web Component
|
|
||||||
element.conversationId = conversationId;
|
|
||||||
|
|
||||||
}, [conversationId]);
|
|
||||||
|
|
||||||
// Handle Web Component events
|
|
||||||
useEffect(() => {
|
|
||||||
if (!elementRef.current) return;
|
|
||||||
|
|
||||||
const element = elementRef.current;
|
|
||||||
|
|
||||||
const handleLoad = () => {
|
|
||||||
onLoad?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleError = (event) => {
|
|
||||||
const errorMessage = event.detail?.message || 'Unknown error occurred';
|
|
||||||
onError?.(new Error(errorMessage));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Listen for custom events from the Web Component
|
|
||||||
element.addEventListener('chatwoot:loaded', handleLoad);
|
|
||||||
element.addEventListener('chatwoot:error', handleError);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
element.removeEventListener('chatwoot:loaded', handleLoad);
|
|
||||||
element.removeEventListener('chatwoot:error', handleError);
|
|
||||||
};
|
|
||||||
}, [onLoad, onError]);
|
|
||||||
|
|
||||||
// Render the Web Component
|
|
||||||
// Global setup is handled by ChatwootProvider
|
|
||||||
return (
|
|
||||||
<chatwoot-message-list
|
|
||||||
ref={elementRef}
|
|
||||||
className={className}
|
|
||||||
style={style}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
||||||
import { registerVueWebComponents } from '../vue-components/registerWebComponents';
|
|
||||||
import store from '../../../dashboard/store';
|
|
||||||
import constants from '../../../dashboard/constants/globals';
|
|
||||||
import axios from 'axios';
|
|
||||||
import createAxios from '../../../ui/axios';
|
|
||||||
import commonHelpers from '../../../dashboard/helper/commons';
|
|
||||||
import vueActionCable from '../../../dashboard/helper/actionCable';
|
|
||||||
|
|
||||||
const ChatwootContext = createContext();
|
|
||||||
|
|
||||||
export const ChatwootProvider = ({
|
|
||||||
baseURL,
|
|
||||||
accountId,
|
|
||||||
userToken,
|
|
||||||
websocketURL,
|
|
||||||
pubsubToken,
|
|
||||||
conversationId,
|
|
||||||
disableUpload,
|
|
||||||
disableEditor,
|
|
||||||
disablePrivateNote,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const isInitialized = useRef(false);
|
|
||||||
const [initializationComplete, setInitializationComplete] = useState(false);
|
|
||||||
|
|
||||||
// Validate required props
|
|
||||||
if (!baseURL) {
|
|
||||||
throw new Error('ChatwootProvider: baseURL is required');
|
|
||||||
}
|
|
||||||
if (!userToken) {
|
|
||||||
throw new Error('ChatwootProvider: userToken is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration object passed to all child components
|
|
||||||
const config = {
|
|
||||||
baseURL: baseURL.replace(/\/$/, ''), // Remove trailing slash
|
|
||||||
userToken,
|
|
||||||
accountId: Number(accountId),
|
|
||||||
conversationId: Number(conversationId),
|
|
||||||
disableEditor: disableEditor || false,
|
|
||||||
disablePrivateNote: disablePrivateNote || false,
|
|
||||||
disableUpload: disableUpload || false,
|
|
||||||
websocketURL: websocketURL,
|
|
||||||
pubsubToken: pubsubToken,
|
|
||||||
};
|
|
||||||
|
|
||||||
function initializeChatwootGlobals() {
|
|
||||||
// Register Web Components
|
|
||||||
registerVueWebComponents();
|
|
||||||
|
|
||||||
// Set up global variables that Vue components expect
|
|
||||||
/* eslint-disable no-underscore-dangle */
|
|
||||||
window.__WOOT_API_HOST__ = config.baseURL;
|
|
||||||
window.__WOOT_ACCOUNT_ID__ = config.accountId;
|
|
||||||
window.__WOOT_ACCESS_TOKEN__ = config.userToken;
|
|
||||||
window.__WEBSOCKET_URL__ = config.websocketURL;
|
|
||||||
window.__PUBSUB_TOKEN__ = config.pubsubToken;
|
|
||||||
window.__WOOT_CONVERSATION_ID__ = config.conversationId;
|
|
||||||
window.__EDITOR_DISABLE_UPLOAD__ = config.disableUpload;
|
|
||||||
window.__EDITOR_DISABLE_PRIVATE_NOTE__ = config.disablePrivateNote;
|
|
||||||
window.__DISABLE_EDITOR__ = config.disableEditor;
|
|
||||||
window.__WOOT_ISOLATED_SHELL__ = true;
|
|
||||||
/* eslint-enable no-underscore-dangle */
|
|
||||||
|
|
||||||
// Initialize common helpers
|
|
||||||
commonHelpers();
|
|
||||||
|
|
||||||
// Set up global objects
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
window.WootConstants = constants;
|
|
||||||
window.axios = createAxios(axios);
|
|
||||||
|
|
||||||
// Initialize user in store and ActionCable
|
|
||||||
store.dispatch('setUser').then(() => {
|
|
||||||
vueActionCable.init(store, config.pubsubToken);
|
|
||||||
setInitializationComplete(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isInitialized.current) return;
|
|
||||||
|
|
||||||
initializeChatwootGlobals();
|
|
||||||
isInitialized.current = true;
|
|
||||||
}, [
|
|
||||||
config.baseURL,
|
|
||||||
config.userToken,
|
|
||||||
config.websocketURL,
|
|
||||||
config.pubsubToken,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!initializationComplete) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChatwootContext.Provider value={config}>
|
|
||||||
{children}
|
|
||||||
</ChatwootContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useChatwoot = () => {
|
|
||||||
const context = useContext(ChatwootContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useChatwoot must be used within ChatwootProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
// For backwards compatibility and explicit configuration access
|
|
||||||
export const useChawootConfig = useChatwoot;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// Export all React components for the Chatwoot React Components library
|
|
||||||
|
|
||||||
// Main API components
|
|
||||||
export { ChatwootProvider, useChatwoot } from './components/ChatwootProvider';
|
|
||||||
export { ChatwootConversation } from './components/ChatwootConversation';
|
|
||||||
|
|
||||||
// Lower-level components (advanced usage)
|
|
||||||
export { ChatwootMessageListWrapper } from './components/ChatwootMessageListWrapper';
|
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import MessageList from '../../../ui/MessageList.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MessageList />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@import '../../../dashboard/assets/scss/app.scss';
|
|
||||||
@import 'vue-multiselect/dist/vue-multiselect.css';
|
|
||||||
@import 'floating-vue/dist/style.css';
|
|
||||||
|
|
||||||
.file-uploads {
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-uploads.file-uploads-html4 input,
|
|
||||||
.file-uploads.file-uploads-html5 label {
|
|
||||||
/* background fix ie click */
|
|
||||||
background: #fff;
|
|
||||||
opacity: 0;
|
|
||||||
font-size: 20em;
|
|
||||||
z-index: 1;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-uploads.file-uploads-html5 input,
|
|
||||||
.file-uploads.file-uploads-html4 label {
|
|
||||||
/* background fix ie click */
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(255, 255, 255, 0);
|
|
||||||
overflow: hidden;
|
|
||||||
position: fixed;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
z-index: -1;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { defineCustomElement } from 'vue';
|
|
||||||
import ChatwootMessageListWebComponent from './ChatwootMessageListWebComponent.vue';
|
|
||||||
import '../../../dashboard/assets/scss/app.scss';
|
|
||||||
import en from '../../../dashboard/i18n/locale/en';
|
|
||||||
import store from '../../../dashboard/store';
|
|
||||||
import vueActionCable from '../../../dashboard/helper/actionCable';
|
|
||||||
import { createI18n, I18nInjectionKey } from 'vue-i18n';
|
|
||||||
|
|
||||||
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
|
||||||
import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer.js';
|
|
||||||
|
|
||||||
const i18n = createI18n({
|
|
||||||
legacy: false,
|
|
||||||
locale: 'en',
|
|
||||||
messages: {
|
|
||||||
en,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const ceOptions = {
|
|
||||||
configureApp(app) {
|
|
||||||
app.use(store);
|
|
||||||
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
|
||||||
|
|
||||||
// I18n has to be injected inside that can be picked
|
|
||||||
// up by the compononent, the API stays the same, just use `useI18n`
|
|
||||||
// https://vue-i18n.intlify.dev/guide/advanced/wc
|
|
||||||
// Adding this link for my goldfish brain
|
|
||||||
app.use(i18n);
|
|
||||||
app.provide(I18nInjectionKey, i18n);
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
vueActionCable.init(store, window.__PUBSUB_TOKEN__);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert Vue components to Web Components
|
|
||||||
const ChatwootMessageListElement = defineCustomElement(
|
|
||||||
ChatwootMessageListWebComponent,
|
|
||||||
ceOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register Web Components
|
|
||||||
export const registerVueWebComponents = () => {
|
|
||||||
if (!customElements.get('chatwoot-message-list')) {
|
|
||||||
customElements.define('chatwoot-message-list', ChatwootMessageListElement);
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('✅ Registered chatwoot-message-list Web Component');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Export for manual registration if needed
|
|
||||||
export { ChatwootMessageListElement };
|
|
||||||
@@ -1,538 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { useTemplateRef } from 'vue';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
import { useAlert } from 'dashboard/composables';
|
|
||||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
|
||||||
|
|
||||||
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
|
|
||||||
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.vue';
|
|
||||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
|
|
||||||
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
|
||||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
|
||||||
|
|
||||||
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
|
|
||||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
|
||||||
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
AttachmentPreview,
|
|
||||||
ReplyTopPanel,
|
|
||||||
ReplyBottomPanel,
|
|
||||||
WootMessageEditor,
|
|
||||||
},
|
|
||||||
mixins: [inboxMixin, fileUploadMixin, keyboardEventListenerMixins],
|
|
||||||
setup() {
|
|
||||||
const replyEditor = useTemplateRef('replyEditor');
|
|
||||||
|
|
||||||
return { replyEditor };
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
message: '',
|
|
||||||
isFocused: false,
|
|
||||||
attachedFiles: [],
|
|
||||||
isUploading: false,
|
|
||||||
replyType: REPLY_EDITOR_MODES.REPLY,
|
|
||||||
hasSlashCommand: false,
|
|
||||||
doAutoSaveDraft: () => {},
|
|
||||||
updateEditorSelectionWith: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters({
|
|
||||||
currentChat: 'getSelectedChat',
|
|
||||||
currentUser: 'getCurrentUser',
|
|
||||||
globalConfig: 'globalConfig/get',
|
|
||||||
}),
|
|
||||||
currentContact() {
|
|
||||||
return this.$store.getters['contacts/getContact'](
|
|
||||||
this.currentChat.meta.sender.id
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showRichContentEditor() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
isPrivate() {
|
|
||||||
// if the current chat is not loaded, assume we can reply
|
|
||||||
// this avoids rendering the editor with is-private yellow bg
|
|
||||||
// optimisitaclly defaulting to reply editor
|
|
||||||
if (!this.currentChat || !Object.keys(this.currentChat).length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.currentChat.can_reply) {
|
|
||||||
return this.isOnPrivateNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
inboxId() {
|
|
||||||
return this.currentChat.inbox_id;
|
|
||||||
},
|
|
||||||
inbox() {
|
|
||||||
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
|
||||||
},
|
|
||||||
messagePlaceHolder() {
|
|
||||||
return this.isPrivate
|
|
||||||
? this.$t('CONVERSATION.FOOTER.PRIVATE_MSG_INPUT')
|
|
||||||
: this.$t('CONVERSATION.FOOTER.MSG_INPUT');
|
|
||||||
},
|
|
||||||
isMessageLengthReachingThreshold() {
|
|
||||||
return this.message.length > this.maxLength - 50;
|
|
||||||
},
|
|
||||||
charactersRemaining() {
|
|
||||||
return this.maxLength - this.message.length;
|
|
||||||
},
|
|
||||||
isReplyButtonDisabled() {
|
|
||||||
if (this.hasAttachments || this.hasRecordedAudio) return false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
this.isMessageEmpty ||
|
|
||||||
this.message.length === 0 ||
|
|
||||||
this.message.length > this.maxLength
|
|
||||||
);
|
|
||||||
},
|
|
||||||
sender() {
|
|
||||||
return {
|
|
||||||
name: this.currentUser.name,
|
|
||||||
thumbnail: this.currentUser.avatar_url,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
conversationType() {
|
|
||||||
const { additional_attributes: additionalAttributes } = this.currentChat;
|
|
||||||
const type = additionalAttributes ? additionalAttributes.type : '';
|
|
||||||
return type || '';
|
|
||||||
},
|
|
||||||
maxLength() {
|
|
||||||
return MESSAGE_MAX_LENGTH.GENERAL;
|
|
||||||
},
|
|
||||||
allowPrivateNote() {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return window.__EDITOR_DISABLE_PRIVATE_NOTE__ !== true;
|
|
||||||
},
|
|
||||||
isEditorDisabled() {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return window.__DISABLE_EDITOR__;
|
|
||||||
},
|
|
||||||
allowFileUpload() {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return window.__EDITOR_DISABLE_UPLOAD__ !== true;
|
|
||||||
},
|
|
||||||
replyButtonLabel() {
|
|
||||||
if (this.isPrivate) {
|
|
||||||
return this.$t('CONVERSATION.REPLYBOX.CREATE');
|
|
||||||
}
|
|
||||||
return this.$t('CONVERSATION.REPLYBOX.SEND');
|
|
||||||
},
|
|
||||||
replyBoxClass() {
|
|
||||||
return {
|
|
||||||
'is-private': this.isPrivate && this.allowPrivateNote,
|
|
||||||
'is-focused': this.isFocused || this.hasAttachments,
|
|
||||||
'pointer-events-none grayscale opacity-70': this.isEditorDisabled,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
hasAttachments() {
|
|
||||||
return this.attachedFiles.length;
|
|
||||||
},
|
|
||||||
isOnPrivateNote() {
|
|
||||||
if (!this.allowPrivateNote) return false;
|
|
||||||
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
|
||||||
},
|
|
||||||
isOnExpandedLayout() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
isMessageEmpty() {
|
|
||||||
if (!this.message) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !this.message.trim().replace(/\n/g, '').length;
|
|
||||||
},
|
|
||||||
showReplyHead() {
|
|
||||||
return !this.isOnPrivateNote;
|
|
||||||
},
|
|
||||||
enableMultipleFileUpload() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
editorMessageKey() {
|
|
||||||
const { editor_message_key: isEnabled } = this.uiSettings;
|
|
||||||
return isEnabled;
|
|
||||||
},
|
|
||||||
conversationId() {
|
|
||||||
return this.currentChat.id;
|
|
||||||
},
|
|
||||||
editorStateId() {
|
|
||||||
return `draft-${this.conversationId}-${this.replyType}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.addEventListener('paste', this.onPaste);
|
|
||||||
document.addEventListener('keydown', this.handleKeyEvents);
|
|
||||||
|
|
||||||
// // A hacky fix to solve the drag and drop
|
|
||||||
// // Is showing on top of new conversation modal drag and drop
|
|
||||||
// // TODO need to find a better solution
|
|
||||||
// emitter.on(
|
|
||||||
// BUS_EVENTS.NEW_CONVERSATION_MODAL,
|
|
||||||
// this.onNewConversationModalActive
|
|
||||||
// );
|
|
||||||
// emitter.on(BUS_EVENTS.INSERT_INTO_NORMAL_EDITOR, this.addIntoEditor);
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
document.removeEventListener('paste', this.onPaste);
|
|
||||||
document.removeEventListener('keydown', this.handleKeyEvents);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getElementToBind() {
|
|
||||||
return this.replyEditor;
|
|
||||||
},
|
|
||||||
onPaste(e) {
|
|
||||||
const data = e.clipboardData.files;
|
|
||||||
if (!this.showRichContentEditor && data.length !== 0) {
|
|
||||||
this.$refs.messageInput.$el.blur();
|
|
||||||
}
|
|
||||||
if (!data.length || !data[0]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
data.forEach(file => {
|
|
||||||
const { name, type, size } = file;
|
|
||||||
this.onFileUpload({ name, type, size, file: file });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
confirmOnSendReply() {
|
|
||||||
if (this.isReplyButtonDisabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.showMentions) {
|
|
||||||
const messagePayload = this.getMessagePayload(this.message);
|
|
||||||
this.sendMessage(messagePayload);
|
|
||||||
this.clearMessage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async onSendReply() {
|
|
||||||
this.confirmOnSendReply();
|
|
||||||
},
|
|
||||||
async sendMessage(messagePayload) {
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch(
|
|
||||||
'createPendingMessageAndSend',
|
|
||||||
messagePayload
|
|
||||||
);
|
|
||||||
// emitter.emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
|
||||||
// emitter.emit(BUS_EVENTS.MESSAGE_SENT);
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage =
|
|
||||||
error?.response?.data?.error || this.$t('CONVERSATION.MESSAGE_ERROR');
|
|
||||||
useAlert(errorMessage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setReplyMode(mode = REPLY_EDITOR_MODES.REPLY) {
|
|
||||||
if (!this.allowPrivateNote) return;
|
|
||||||
|
|
||||||
const { can_reply: canReply } = this.currentChat;
|
|
||||||
this.$store.dispatch('draftMessages/setReplyEditorMode', {
|
|
||||||
mode,
|
|
||||||
});
|
|
||||||
if (canReply) this.replyType = mode;
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
// This block addresses a scrolling issue on iOS Safari when the virtual
|
|
||||||
// keyboard opens and can obscure the focused ProseMirror editor.
|
|
||||||
// This behavior is primarily an iOS Safari quirk related to how it handles
|
|
||||||
// the viewport with the virtual keyboard. It's not something we
|
|
||||||
// directly control or can easily fix at the component levels.
|
|
||||||
//
|
|
||||||
// `this.$nextTick()`: Ensures this code runs after Vue has finished its
|
|
||||||
// DOM update cycle. This is crucial if the editor's visibility or focus
|
|
||||||
// was just programmatically changed, guaranteeing we operate on the
|
|
||||||
// finalized DOM.
|
|
||||||
//
|
|
||||||
// `setTimeout(() => { ... }, 300)`: A delay is necessary because iOS
|
|
||||||
// Safari needs time for the virtual keyboard to fully animate into view
|
|
||||||
// and for the page layout to adjust. Attempting to scroll immediately
|
|
||||||
// (even after $nextTick) often fails as the keyboard isn't yet fully
|
|
||||||
// present or the viewport dimensions haven't updated. The 300ms is a
|
|
||||||
// common heuristic.
|
|
||||||
//
|
|
||||||
// The `scrollIntoView()` method then attempts to bring the editor
|
|
||||||
// into the visible part of the viewport. This manual scroll is a
|
|
||||||
// pragmatic workaround for this specific iOS Safari behavior.
|
|
||||||
setTimeout(() => {
|
|
||||||
document.querySelector('.ProseMirror')?.scrollIntoView();
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.showRichContentEditor) {
|
|
||||||
if (this.isRecordingAudio) {
|
|
||||||
this.toggleAudioRecorder();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$nextTick(() => this.$refs.messageInput.focus());
|
|
||||||
},
|
|
||||||
clearEditorSelection() {
|
|
||||||
this.updateEditorSelectionWith = '';
|
|
||||||
},
|
|
||||||
insertIntoTextEditor(text, selectionStart, selectionEnd) {
|
|
||||||
const { message } = this;
|
|
||||||
const newMessage =
|
|
||||||
message.slice(0, selectionStart) +
|
|
||||||
text +
|
|
||||||
message.slice(selectionEnd, message.length);
|
|
||||||
this.message = newMessage;
|
|
||||||
},
|
|
||||||
addIntoEditor(content) {
|
|
||||||
if (this.showRichContentEditor) {
|
|
||||||
this.updateEditorSelectionWith = content;
|
|
||||||
this.onFocus();
|
|
||||||
}
|
|
||||||
if (!this.showRichContentEditor) {
|
|
||||||
const { selectionStart, selectionEnd } = this.$refs.messageInput.$el;
|
|
||||||
this.insertIntoTextEditor(content, selectionStart, selectionEnd);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearMessage() {
|
|
||||||
this.message = '';
|
|
||||||
this.attachedFiles = [];
|
|
||||||
this.isRecordingAudio = false;
|
|
||||||
},
|
|
||||||
onTypingOn() {
|
|
||||||
this.toggleTyping('on');
|
|
||||||
},
|
|
||||||
onTypingOff() {
|
|
||||||
this.toggleTyping('off');
|
|
||||||
},
|
|
||||||
onBlur() {
|
|
||||||
this.isFocused = false;
|
|
||||||
},
|
|
||||||
onFocus() {
|
|
||||||
this.isFocused = true;
|
|
||||||
},
|
|
||||||
toggleTyping(status) {
|
|
||||||
const conversationId = this.currentChat.id;
|
|
||||||
const isPrivate = this.isPrivate;
|
|
||||||
|
|
||||||
if (!conversationId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch('conversationTypingStatus/toggleTyping', {
|
|
||||||
status,
|
|
||||||
conversationId,
|
|
||||||
isPrivate,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
attachFile({ blob, file }) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file.file);
|
|
||||||
reader.onloadend = () => {
|
|
||||||
this.attachedFiles.push({
|
|
||||||
currentChatId: this.currentChat.id,
|
|
||||||
resource: blob || file,
|
|
||||||
isPrivate: this.isPrivate,
|
|
||||||
thumb: reader.result,
|
|
||||||
blobSignedId: blob ? blob.signed_id : undefined,
|
|
||||||
isRecordedAudio: file?.isRecordedAudio || false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
},
|
|
||||||
removeAttachment(attachments) {
|
|
||||||
this.attachedFiles = attachments;
|
|
||||||
},
|
|
||||||
setReplyToInPayload(payload) {
|
|
||||||
if (this.inReplyTo?.id) {
|
|
||||||
return {
|
|
||||||
...payload,
|
|
||||||
contentAttributes: {
|
|
||||||
...payload.contentAttributes,
|
|
||||||
in_reply_to: this.inReplyTo.id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
},
|
|
||||||
getMessagePayload(message) {
|
|
||||||
let messagePayload = {
|
|
||||||
conversationId: this.currentChat.id,
|
|
||||||
message,
|
|
||||||
private: this.isPrivate,
|
|
||||||
sender: this.sender,
|
|
||||||
};
|
|
||||||
messagePayload = this.setReplyToInPayload(messagePayload);
|
|
||||||
|
|
||||||
if (this.attachedFiles && this.attachedFiles.length) {
|
|
||||||
messagePayload.files = [];
|
|
||||||
this.attachedFiles.forEach(attachment => {
|
|
||||||
if (this.globalConfig.directUploadsEnabled) {
|
|
||||||
messagePayload.files.push(attachment.blobSignedId);
|
|
||||||
} else {
|
|
||||||
messagePayload.files.push(attachment.resource.file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return messagePayload;
|
|
||||||
},
|
|
||||||
getKeyboardEvents() {
|
|
||||||
return {
|
|
||||||
'$mod+Enter': {
|
|
||||||
action: this.onSendReply,
|
|
||||||
allowOnFocusedInput: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="replyEditor" class="reply-box" :class="replyBoxClass">
|
|
||||||
<ReplyTopPanel
|
|
||||||
v-if="allowPrivateNote"
|
|
||||||
:mode="replyType"
|
|
||||||
disable-popout
|
|
||||||
:is-message-length-reaching-threshold="isMessageLengthReachingThreshold"
|
|
||||||
:characters-remaining="charactersRemaining"
|
|
||||||
@set-reply-mode="setReplyMode"
|
|
||||||
/>
|
|
||||||
<div class="reply-box__top">
|
|
||||||
<WootMessageEditor
|
|
||||||
v-model="message"
|
|
||||||
:editor-id="editorStateId"
|
|
||||||
:disabled="isEditorDisabled"
|
|
||||||
class="input"
|
|
||||||
:is-private="isOnPrivateNote"
|
|
||||||
:placeholder="messagePlaceHolder"
|
|
||||||
:update-selection-with="updateEditorSelectionWith"
|
|
||||||
:min-height="4"
|
|
||||||
enable-variables
|
|
||||||
:variables="messageVariables"
|
|
||||||
:signature="signatureToApply"
|
|
||||||
allow-signature
|
|
||||||
:channel-type="channelType"
|
|
||||||
@typing-off="onTypingOff"
|
|
||||||
@typing-on="onTypingOn"
|
|
||||||
@focus="onFocus"
|
|
||||||
@blur="onBlur"
|
|
||||||
@toggle-user-mention="toggleUserMention"
|
|
||||||
@toggle-canned-menu="toggleCannedMenu"
|
|
||||||
@toggle-variables-menu="toggleVariablesMenu"
|
|
||||||
@clear-selection="clearEditorSelection"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="hasAttachments && !showAudioRecorderEditor"
|
|
||||||
class="attachment-preview-box"
|
|
||||||
@paste="onPaste"
|
|
||||||
>
|
|
||||||
<AttachmentPreview
|
|
||||||
class="flex-col mt-4"
|
|
||||||
:attachments="attachedFiles"
|
|
||||||
@remove-attachment="removeAttachment"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ReplyBottomPanel
|
|
||||||
:conversation-id="conversationId"
|
|
||||||
:inbox="inbox"
|
|
||||||
:is-on-private-note="isOnPrivateNote"
|
|
||||||
:is-send-disabled="isReplyButtonDisabled"
|
|
||||||
:mode="replyType"
|
|
||||||
:on-file-upload="onFileUpload"
|
|
||||||
:on-send="onSendReply"
|
|
||||||
:conversation-type="conversationType"
|
|
||||||
:send-button-text="replyButtonLabel"
|
|
||||||
:show-audio-recorder="showAudioRecorder"
|
|
||||||
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
|
||||||
:show-emoji-picker="false"
|
|
||||||
:show-file-upload="allowFileUpload"
|
|
||||||
:message="message"
|
|
||||||
:enable-multiple-file-upload="allowFileUpload"
|
|
||||||
:allow-file-upload="allowFileUpload"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.send-button {
|
|
||||||
@apply mb-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner--self-assign {
|
|
||||||
@apply py-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachment-preview-box {
|
|
||||||
@apply bg-transparent py-0 px-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply-box {
|
|
||||||
transition: height 2s cubic-bezier(0.37, 0, 0.63, 1);
|
|
||||||
|
|
||||||
@apply relative mb-2 mx-2 border border-n-weak rounded-xl bg-n-solid-1;
|
|
||||||
|
|
||||||
&.is-private {
|
|
||||||
@apply bg-n-solid-amber dark:border-n-amber-3/10 border-n-amber-12/5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* in safari the list marker is truncated */
|
|
||||||
/* We could add, the following, but in Safari, it behaves weirdly with the curosr */
|
|
||||||
/* ::v-deep .ProseMirror li {
|
|
||||||
list-style-position: inside !important;
|
|
||||||
} */
|
|
||||||
|
|
||||||
@supports (-webkit-hyphens: none) {
|
|
||||||
::v-deep .ProseMirror ul {
|
|
||||||
margin-left: 2ch !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .ProseMirror ol {
|
|
||||||
margin-left: 2ch !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-button {
|
|
||||||
@apply mb-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply-box__top {
|
|
||||||
@apply relative py-0 px-4 -mt-px;
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
@apply shadow-none outline-none border-transparent bg-transparent m-0 max-h-60 min-h-[3rem] pt-4 pb-0 px-0 resize-none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-dialog {
|
|
||||||
@apply top-[unset] -bottom-10 -left-80 right-[unset];
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
transform: rotate(270deg);
|
|
||||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
|
|
||||||
@apply -right-4 bottom-2 rtl:right-0 rtl:-left-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-dialog--rtl {
|
|
||||||
@apply left-[unset] -right-80;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-dialog--expanded {
|
|
||||||
@apply left-[unset] bottom-0 absolute z-[100];
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
@apply left-1 -bottom-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.normal-editor__canned-box {
|
|
||||||
width: calc(100% - 2 * var(--space-normal));
|
|
||||||
left: var(--space-normal);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, onMounted, computed, watch, useTemplateRef } from 'vue';
|
|
||||||
import Message from 'next/message/Message.vue';
|
|
||||||
import TypingIndicator from 'next/message/TypingIndicator.vue';
|
|
||||||
import Snipper from 'next/spinner/Spinner.vue';
|
|
||||||
import LiteReplyBox from './LiteReplyBox.vue';
|
|
||||||
import {
|
|
||||||
useStore,
|
|
||||||
useMapGetter,
|
|
||||||
useFunctionGetter,
|
|
||||||
//
|
|
||||||
} from '../dashboard/composables/store';
|
|
||||||
import { getTypingUsersText } from '../dashboard/helper/commons';
|
|
||||||
import { useCamelCase } from '../dashboard/composables/useTransformKeys';
|
|
||||||
import { useInfiniteScroll, useThrottleFn } from '@vueuse/core';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
const conversationId = computed(() => {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return window.__WOOT_CONVERSATION_ID__;
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const messageListRef = useTemplateRef('messageListRef');
|
|
||||||
const store = useStore();
|
|
||||||
const isAllLoaded = useMapGetter('getAllMessagesLoaded');
|
|
||||||
const isFetching = ref(false);
|
|
||||||
|
|
||||||
const typingUserList = useFunctionGetter(
|
|
||||||
'conversationTypingStatus/getUserList',
|
|
||||||
conversationId.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const isAnyoneTyping = computed(() => {
|
|
||||||
return typingUserList.value.length > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const typingUserNames = computed(() => {
|
|
||||||
if (!isAnyoneTyping.value) return '';
|
|
||||||
const [i18nKey, params] = getTypingUsersText(typingUserList.value);
|
|
||||||
// eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys
|
|
||||||
return t(i18nKey, params);
|
|
||||||
});
|
|
||||||
|
|
||||||
const conversation = computed(() => {
|
|
||||||
return store.getters.getConversationById(conversationId.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const allMessages = computed(() => {
|
|
||||||
if (!conversation.value) return [];
|
|
||||||
|
|
||||||
return useCamelCase(conversation.value.messages, { deep: true }).reverse();
|
|
||||||
});
|
|
||||||
|
|
||||||
const disablePrivateNote = computed(() => {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return window.__EDITOR_DISABLE_PRIVATE_NOTE__ === true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchMore = async () => {
|
|
||||||
if (isFetching.value) return;
|
|
||||||
if (!conversation?.value?.id) return;
|
|
||||||
if (!allMessages.value?.length) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isFetching.value = true;
|
|
||||||
|
|
||||||
await store.dispatch('fetchPreviousMessages', {
|
|
||||||
conversationId: conversation.value.id,
|
|
||||||
before: allMessages.value[allMessages.value.length - 1].id,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
isFetching.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await store.dispatch('inboxes/get');
|
|
||||||
await Promise.all([
|
|
||||||
store.dispatch('getConversation', conversationId.value),
|
|
||||||
store.dispatch('fetchAllAttachments', conversationId.value),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(conversation, () => {
|
|
||||||
store.dispatch('setActiveChat', {
|
|
||||||
data: {
|
|
||||||
id: conversation.value.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
useInfiniteScroll(messageListRef, useThrottleFn(fetchMore, 1000), {
|
|
||||||
canLoadMore: () => {
|
|
||||||
return !isAllLoaded.value;
|
|
||||||
},
|
|
||||||
distance: 10,
|
|
||||||
direction: 'top',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="relative">
|
|
||||||
<ul
|
|
||||||
ref="messageListRef"
|
|
||||||
class="px-4 pt-4 flex flex-col-reverse bg-n-background overflow-scroll h-screen"
|
|
||||||
:class="{
|
|
||||||
'pb-60': !disablePrivateNote,
|
|
||||||
'pb-48': disablePrivateNote,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="isAnyoneTyping"
|
|
||||||
id="conversationFooter"
|
|
||||||
class="my-2 py-2 flex items-center w-full"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex py-2 px-4 shadow-md rounded-full bg-white dark:bg-slate-700 text-n-slate-11 text-xs font-semibold mx-auto items-center gap-1"
|
|
||||||
>
|
|
||||||
{{ typingUserNames }}
|
|
||||||
<TypingIndicator class="text-n-slate-9" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-for="message in allMessages" :key="message.id">
|
|
||||||
<Message
|
|
||||||
v-bind="message"
|
|
||||||
:is-email-inbox="isAnEmailChannel"
|
|
||||||
:group-with-next="false"
|
|
||||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
|
||||||
:current-user-id="currentUserId"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<div v-show="isFetching" class="w-full py-4">
|
|
||||||
<Snipper class="mx-auto" />
|
|
||||||
</div>
|
|
||||||
</ul>
|
|
||||||
<div class="p-2 w-full bg-white absolute bottom-0">
|
|
||||||
<LiteReplyBox />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* this will disable elastic scrolling on the y axis */
|
|
||||||
* {
|
|
||||||
overscroll-behavior-y: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
const parseErrorCode = error => Promise.reject(error);
|
|
||||||
|
|
||||||
export default axios => {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
const apiHost = window.__WOOT_API_HOST__;
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
const accessToken = window.__WOOT_ACCESS_TOKEN__;
|
|
||||||
const wootApi = axios.create({ baseURL: `${apiHost}/` });
|
|
||||||
|
|
||||||
Object.assign(wootApi.defaults.headers.common, {
|
|
||||||
api_access_token: accessToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
wootApi.interceptors.response.use(
|
|
||||||
response => response,
|
|
||||||
error => parseErrorCode(error)
|
|
||||||
);
|
|
||||||
return wootApi;
|
|
||||||
};
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# Chatwoot UI Components Usage Guide
|
|
||||||
|
|
||||||
This document explains how to use the Chatwoot UI components library, which can be used both as Web Components (Custom Elements) and as Vue components.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @chatwoot/ui
|
|
||||||
# or
|
|
||||||
yarn add @chatwoot/ui
|
|
||||||
# or
|
|
||||||
pnpm add @chatwoot/ui
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### As Web Components (Custom Elements)
|
|
||||||
|
|
||||||
Web Components can be used in any HTML page or application, regardless of the framework.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Include the built JS file -->
|
|
||||||
<script src="https://unpkg.com/@chatwoot/ui/dist/ui.js"></script>
|
|
||||||
|
|
||||||
<!-- Use the custom element anywhere in your HTML -->
|
|
||||||
<chat-button label="Click me"></chat-button>
|
|
||||||
```
|
|
||||||
|
|
||||||
When the script loads, it automatically registers all custom elements with the browser, making them immediately available for use.
|
|
||||||
|
|
||||||
#### Accessing the Store in Web Components
|
|
||||||
|
|
||||||
The components have access to a global store instance. When using Web Components, the store is available through `window.__CHATWOOT_STORE__`.
|
|
||||||
|
|
||||||
You can interact with the store from your JavaScript:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Access the store
|
|
||||||
const store = window.__CHATWOOT_STORE__;
|
|
||||||
|
|
||||||
// Check store state
|
|
||||||
console.log(store.state.someData);
|
|
||||||
|
|
||||||
// Dispatch actions
|
|
||||||
store.dispatch('someAction', { data: 'example' });
|
|
||||||
```
|
|
||||||
@@ -12,7 +12,7 @@ class ContactPolicy < ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def export?
|
def export?
|
||||||
@account_user.administrator?
|
@account_user.administrator? || @account_user.agent?
|
||||||
end
|
end
|
||||||
|
|
||||||
def search?
|
def search?
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<% author_count = category.articles.published.order(position: :asc).map(&:author).uniq.size %>
|
<% author_count = category.articles.published.order(position: :asc).map(&:author).uniq.size %>
|
||||||
<% if author_count > 0 %>
|
<% if author_count > 0 %>
|
||||||
<div class="flex flex-row items-center gap-1">
|
<div class="flex flex-row items-center gap-1">
|
||||||
<div class="flex items-center ltr:flex-row rtl:flex-row-reverse -space-x-2">
|
<div class="flex flex-row items-center -space-x-2">
|
||||||
<% category.articles.published.order(position: :asc).map(&:author).uniq.take(3).each do |author| %>
|
<% category.articles.published.order(position: :asc).map(&:author).uniq.take(3).each do |author| %>
|
||||||
<%= render "public/api/v1/portals/thumbnail", author: author, size: 5 %>
|
<%= render "public/api/v1/portals/thumbnail", author: author, size: 5 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% first_author = category.articles.published.order(position: :asc).map(&:author).uniq.first&.name || '' %>
|
<% first_author = category.articles.published.order(position: :asc).map(&:author).uniq.first&.name || '' %>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="flex items-center w-full py-5 overflow-hidden">
|
<div class="flex items-center w-full py-5 overflow-hidden">
|
||||||
<a href="<%= generate_home_link(@portal.slug, @portal.config['default_locale'] || params[:locale], @theme_from_params, @is_plain_layout_enabled) %>" class="flex items-center h-10 text-lg font-semibold text-slate-900 dark:text-white">
|
<a href="<%= generate_home_link(@portal.slug, @portal.config['default_locale'] || params[:locale], @theme_from_params, @is_plain_layout_enabled) %>" class="flex items-center h-10 text-lg font-semibold text-slate-900 dark:text-white">
|
||||||
<% if @portal.logo.present? %>
|
<% if @portal.logo.present? %>
|
||||||
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" />
|
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 mr-2" />
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= @portal.name %>
|
<%= @portal.name %>
|
||||||
</a>
|
</a>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<%# Go to homepage link section %>
|
<%# Go to homepage link section %>
|
||||||
<div class="flex items-center justify-between gap-2 sm:gap-5">
|
<div class="flex items-center justify-between gap-2 sm:gap-5">
|
||||||
<% if @portal.homepage_link %>
|
<% if @portal.homepage_link %>
|
||||||
<div class="hidden px-1 py-2 ltr:ml-8 rtl:mr-8 cursor-pointer md:block">
|
<div class="hidden px-1 py-2 ml-8 cursor-pointer border-l-1 border-slate-50 dark:border-slate-800 md:block">
|
||||||
<div class="flex-grow flex-shrink-0">
|
<div class="flex-grow flex-shrink-0">
|
||||||
<a id="header-action-button" target="_blank" rel="noopener noreferrer nofollow" href="<%= @portal.homepage_link %>" class="flex flex-row items-center gap-1 text-sm font-medium whitespace-nowrap text-slate-800 dark:text-slate-100 stroke-slate-700 dark:stroke-slate-200">
|
<a id="header-action-button" target="_blank" rel="noopener noreferrer nofollow" href="<%= @portal.homepage_link %>" class="flex flex-row items-center gap-1 text-sm font-medium whitespace-nowrap text-slate-800 dark:text-slate-100 stroke-slate-700 dark:stroke-slate-200">
|
||||||
<%= render partial: 'icons/redirect' %>
|
<%= render partial: 'icons/redirect' %>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<%# Appearance dropdown section %>
|
<%# Appearance dropdown section %>
|
||||||
<div id="appearance-dropdown" data-current-theme="<%= @theme_from_params %>" class="absolute flex-col w-32 h-auto bg-white border border-solid rounded dark:bg-slate-900 top-9 ltr:right-1 rtl:left-1 border-slate-100 dark:border-slate-800" aria-hidden="true" style="display: none;" data-dropdown="appearance-dropdown">
|
<div id="appearance-dropdown" data-current-theme="<%= @theme_from_params %>" class="absolute flex-col w-32 h-auto bg-white border border-solid rounded dark:bg-slate-900 top-9 right-1 border-slate-100 dark:border-slate-800" aria-hidden="true" style="display: none;" data-dropdown="appearance-dropdown">
|
||||||
<button id="toggle-theme-button" data-theme="system" class="flex flex-row items-center justify-between gap-1 px-2 py-2 border-b border-solid border-slate-100 dark:border-slate-800 stroke-slate-700 dark:stroke-slate-200 text-slate-800 dark:text-slate-100">
|
<button id="toggle-theme-button" data-theme="system" class="flex flex-row items-center justify-between gap-1 px-2 py-2 border-b border-solid border-slate-100 dark:border-slate-800 stroke-slate-700 dark:stroke-slate-200 text-slate-800 dark:text-slate-100">
|
||||||
<div class="flex flex-row items-center gap-1">
|
<div class="flex flex-row items-center gap-1">
|
||||||
<%= render partial: 'icons/monitor' %>
|
<%= render partial: 'icons/monitor' %>
|
||||||
|
|||||||
@@ -13,10 +13,6 @@
|
|||||||
"dev": "overmind start -f ./Procfile.dev",
|
"dev": "overmind start -f ./Procfile.dev",
|
||||||
"ruby:prettier": "bundle exec rubocop -a",
|
"ruby:prettier": "bundle exec rubocop -a",
|
||||||
"build:sdk": "BUILD_MODE=library vite build",
|
"build:sdk": "BUILD_MODE=library vite build",
|
||||||
"build:ui": "BUILD_MODE=ui vite build",
|
|
||||||
"build:react": "BUILD_MODE=react-components vite build",
|
|
||||||
"dev:react": "BUILD_MODE=react-components vite build --watch",
|
|
||||||
"package:react": "node scripts/package-react-components.js",
|
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"size": "size-limit",
|
"size": "size-limit",
|
||||||
"story:dev": "histoire dev",
|
"story:dev": "histoire dev",
|
||||||
@@ -119,7 +115,6 @@
|
|||||||
"@iconify-json/teenyicons": "^1.2.1",
|
"@iconify-json/teenyicons": "^1.2.1",
|
||||||
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
|
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
|
||||||
"@size-limit/file": "^8.2.4",
|
"@size-limit/file": "^8.2.4",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
|
||||||
"@vitest/coverage-v8": "3.0.5",
|
"@vitest/coverage-v8": "3.0.5",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
@@ -141,8 +136,6 @@
|
|||||||
"postcss-preset-env": "^8.5.1",
|
"postcss-preset-env": "^8.5.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prosemirror-model": "^1.22.3",
|
"prosemirror-model": "^1.22.3",
|
||||||
"react": "^19.1.0",
|
|
||||||
"react-dom": "^19.1.0",
|
|
||||||
"size-limit": "^8.2.4",
|
"size-limit": "^8.2.4",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
"vite": "^5.4.19",
|
"vite": "^5.4.19",
|
||||||
|
|||||||
Generated
-361
@@ -260,9 +260,6 @@ importers:
|
|||||||
'@size-limit/file':
|
'@size-limit/file':
|
||||||
specifier: ^8.2.4
|
specifier: ^8.2.4
|
||||||
version: 8.2.6(size-limit@8.2.6)
|
version: 8.2.6(size-limit@8.2.6)
|
||||||
'@vitejs/plugin-react':
|
|
||||||
specifier: ^4.5.2
|
|
||||||
version: 4.5.2(vite@5.4.19(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))
|
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: 3.0.5
|
specifier: 3.0.5
|
||||||
version: 3.0.5(vitest@3.0.5(@types/node@22.7.0)(jsdom@24.1.3)(sass@1.79.3)(terser@5.33.0))
|
version: 3.0.5(vitest@3.0.5(@types/node@22.7.0)(jsdom@24.1.3)(sass@1.79.3)(terser@5.33.0))
|
||||||
@@ -326,12 +323,6 @@ importers:
|
|||||||
prosemirror-model:
|
prosemirror-model:
|
||||||
specifier: ^1.22.3
|
specifier: ^1.22.3
|
||||||
version: 1.22.3
|
version: 1.22.3
|
||||||
react:
|
|
||||||
specifier: ^19.1.0
|
|
||||||
version: 19.1.0
|
|
||||||
react-dom:
|
|
||||||
specifier: ^19.1.0
|
|
||||||
version: 19.1.0(react@19.1.0)
|
|
||||||
size-limit:
|
size-limit:
|
||||||
specifier: ^8.2.4
|
specifier: ^8.2.4
|
||||||
version: 8.2.6
|
version: 8.2.6
|
||||||
@@ -372,64 +363,14 @@ packages:
|
|||||||
'@antfu/utils@0.7.10':
|
'@antfu/utils@0.7.10':
|
||||||
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
|
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
|
||||||
|
|
||||||
'@babel/code-frame@7.27.1':
|
|
||||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/compat-data@7.27.5':
|
|
||||||
resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/core@7.27.4':
|
|
||||||
resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/generator@7.27.5':
|
|
||||||
resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-compilation-targets@7.27.2':
|
|
||||||
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-module-imports@7.27.1':
|
|
||||||
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-module-transforms@7.27.3':
|
|
||||||
resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
peerDependencies:
|
|
||||||
'@babel/core': ^7.0.0
|
|
||||||
|
|
||||||
'@babel/helper-plugin-utils@7.27.1':
|
|
||||||
resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-string-parser@7.25.9':
|
'@babel/helper-string-parser@7.25.9':
|
||||||
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
|
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helper-string-parser@7.27.1':
|
|
||||||
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-validator-identifier@7.25.9':
|
'@babel/helper-validator-identifier@7.25.9':
|
||||||
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/helper-validator-identifier@7.27.1':
|
|
||||||
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helper-validator-option@7.27.1':
|
|
||||||
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/helpers@7.27.6':
|
|
||||||
resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/parser@7.25.6':
|
'@babel/parser@7.25.6':
|
||||||
resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==}
|
resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -440,23 +381,6 @@ packages:
|
|||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@babel/parser@7.27.5':
|
|
||||||
resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
|
|
||||||
engines: {node: '>=6.0.0'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
'@babel/plugin-transform-react-jsx-self@7.27.1':
|
|
||||||
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
peerDependencies:
|
|
||||||
'@babel/core': ^7.0.0-0
|
|
||||||
|
|
||||||
'@babel/plugin-transform-react-jsx-source@7.27.1':
|
|
||||||
resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
peerDependencies:
|
|
||||||
'@babel/core': ^7.0.0-0
|
|
||||||
|
|
||||||
'@babel/runtime@7.25.6':
|
'@babel/runtime@7.25.6':
|
||||||
resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
|
resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -465,22 +389,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==}
|
resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
|
||||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/traverse@7.27.4':
|
|
||||||
resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@babel/types@7.26.0':
|
'@babel/types@7.26.0':
|
||||||
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
|
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/types@7.27.6':
|
|
||||||
resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
'@bcoe/v8-coverage@1.0.2':
|
'@bcoe/v8-coverage@1.0.2':
|
||||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1127,9 +1039,6 @@ packages:
|
|||||||
'@rails/ujs@7.1.400':
|
'@rails/ujs@7.1.400':
|
||||||
resolution: {integrity: sha512-YwvXm3BR5tn+VCAKYGycLejMRVZE3Ionj5gFjEeGXCZnI0Rpi+7dKpmyu90kdUY7dRUFpHTdu9zZceEzFLl38w==}
|
resolution: {integrity: sha512-YwvXm3BR5tn+VCAKYGycLejMRVZE3Ionj5gFjEeGXCZnI0Rpi+7dKpmyu90kdUY7dRUFpHTdu9zZceEzFLl38w==}
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.11':
|
|
||||||
resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
|
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.40.2':
|
'@rollup/rollup-android-arm-eabi@4.40.2':
|
||||||
resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==}
|
resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
@@ -1837,18 +1746,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
|
||||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
|
||||||
|
|
||||||
'@types/babel__generator@7.27.0':
|
|
||||||
resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
|
|
||||||
|
|
||||||
'@types/babel__template@7.4.4':
|
|
||||||
resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
|
|
||||||
|
|
||||||
'@types/babel__traverse@7.20.7':
|
|
||||||
resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
|
|
||||||
|
|
||||||
'@types/estree@1.0.7':
|
'@types/estree@1.0.7':
|
||||||
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
||||||
|
|
||||||
@@ -1899,12 +1796,6 @@ packages:
|
|||||||
'@videojs/xhr@2.6.0':
|
'@videojs/xhr@2.6.0':
|
||||||
resolution: {integrity: sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==}
|
resolution: {integrity: sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==}
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.5.2':
|
|
||||||
resolution: {integrity: sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==}
|
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
|
||||||
peerDependencies:
|
|
||||||
vite: 5.4.19
|
|
||||||
|
|
||||||
'@vitejs/plugin-vue@5.1.4':
|
'@vitejs/plugin-vue@5.1.4':
|
||||||
resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==}
|
resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@@ -2271,11 +2162,6 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
browserslist@4.25.0:
|
|
||||||
resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==}
|
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
@@ -2320,9 +2206,6 @@ packages:
|
|||||||
caniuse-lite@1.0.30001651:
|
caniuse-lite@1.0.30001651:
|
||||||
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
|
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001721:
|
|
||||||
resolution: {integrity: sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==}
|
|
||||||
|
|
||||||
capital-case@1.0.4:
|
capital-case@1.0.4:
|
||||||
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
|
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
|
||||||
|
|
||||||
@@ -2449,9 +2332,6 @@ packages:
|
|||||||
constant-case@3.0.4:
|
constant-case@3.0.4:
|
||||||
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
|
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
|
||||||
|
|
||||||
core-js@3.38.1:
|
core-js@3.38.1:
|
||||||
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
|
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
|
||||||
|
|
||||||
@@ -2693,9 +2573,6 @@ packages:
|
|||||||
electron-to-chromium@1.5.13:
|
electron-to-chromium@1.5.13:
|
||||||
resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==}
|
resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==}
|
||||||
|
|
||||||
electron-to-chromium@1.5.166:
|
|
||||||
resolution: {integrity: sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==}
|
|
||||||
|
|
||||||
email-validator@2.0.4:
|
email-validator@2.0.4:
|
||||||
resolution: {integrity: sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==}
|
resolution: {integrity: sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==}
|
||||||
engines: {node: '>4.0'}
|
engines: {node: '>4.0'}
|
||||||
@@ -3071,10 +2948,6 @@ packages:
|
|||||||
functions-have-names@1.2.3:
|
functions-have-names@1.2.3:
|
||||||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2:
|
|
||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
|
||||||
engines: {node: '>=6.9.0'}
|
|
||||||
|
|
||||||
get-caller-file@2.0.5:
|
get-caller-file@2.0.5:
|
||||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||||
engines: {node: 6.* || 8.* || >= 10.*}
|
engines: {node: 6.* || 8.* || >= 10.*}
|
||||||
@@ -3130,10 +3003,6 @@ packages:
|
|||||||
global@4.4.0:
|
global@4.4.0:
|
||||||
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
|
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
|
||||||
|
|
||||||
globals@11.12.0:
|
|
||||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
globals@13.24.0:
|
globals@13.24.0:
|
||||||
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3508,9 +3377,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
|
||||||
|
|
||||||
js-yaml@3.14.1:
|
js-yaml@3.14.1:
|
||||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -3537,11 +3403,6 @@ packages:
|
|||||||
canvas:
|
canvas:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
jsesc@3.1.0:
|
|
||||||
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
json-buffer@3.0.1:
|
json-buffer@3.0.1:
|
||||||
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
||||||
|
|
||||||
@@ -3686,9 +3547,6 @@ packages:
|
|||||||
lru-cache@10.4.3:
|
lru-cache@10.4.3:
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
|
||||||
|
|
||||||
lru-cache@6.0.0:
|
lru-cache@6.0.0:
|
||||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3893,9 +3751,6 @@ packages:
|
|||||||
node-releases@2.0.18:
|
node-releases@2.0.18:
|
||||||
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
|
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
|
||||||
|
|
||||||
node-releases@2.0.19:
|
|
||||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
|
||||||
|
|
||||||
nopt@7.2.1:
|
nopt@7.2.1:
|
||||||
resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
|
resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
|
||||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||||
@@ -4425,19 +4280,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==}
|
resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
react-dom@19.1.0:
|
|
||||||
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^19.1.0
|
|
||||||
|
|
||||||
react-refresh@0.17.0:
|
|
||||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
react@19.1.0:
|
|
||||||
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
read-cache@1.0.0:
|
read-cache@1.0.0:
|
||||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||||
|
|
||||||
@@ -4556,9 +4398,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||||
engines: {node: '>=v12.22.7'}
|
engines: {node: '>=v12.22.7'}
|
||||||
|
|
||||||
scheduler@0.26.0:
|
|
||||||
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
|
||||||
|
|
||||||
sdp@3.2.0:
|
sdp@3.2.0:
|
||||||
resolution: {integrity: sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==}
|
resolution: {integrity: sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==}
|
||||||
|
|
||||||
@@ -4976,12 +4815,6 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
browserslist: '>= 4.21.0'
|
browserslist: '>= 4.21.0'
|
||||||
|
|
||||||
update-browserslist-db@1.1.3:
|
|
||||||
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
|
|
||||||
hasBin: true
|
|
||||||
peerDependencies:
|
|
||||||
browserslist: '>= 4.21.0'
|
|
||||||
|
|
||||||
upper-case-first@2.0.2:
|
upper-case-first@2.0.2:
|
||||||
resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==}
|
resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==}
|
||||||
|
|
||||||
@@ -5317,9 +5150,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
yallist@3.1.1:
|
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
|
||||||
|
|
||||||
yallist@4.0.0:
|
yallist@4.0.0:
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
|
|
||||||
@@ -5372,83 +5202,10 @@ snapshots:
|
|||||||
|
|
||||||
'@antfu/utils@0.7.10': {}
|
'@antfu/utils@0.7.10': {}
|
||||||
|
|
||||||
'@babel/code-frame@7.27.1':
|
|
||||||
dependencies:
|
|
||||||
'@babel/helper-validator-identifier': 7.27.1
|
|
||||||
js-tokens: 4.0.0
|
|
||||||
picocolors: 1.1.1
|
|
||||||
|
|
||||||
'@babel/compat-data@7.27.5': {}
|
|
||||||
|
|
||||||
'@babel/core@7.27.4':
|
|
||||||
dependencies:
|
|
||||||
'@ampproject/remapping': 2.3.0
|
|
||||||
'@babel/code-frame': 7.27.1
|
|
||||||
'@babel/generator': 7.27.5
|
|
||||||
'@babel/helper-compilation-targets': 7.27.2
|
|
||||||
'@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
|
|
||||||
'@babel/helpers': 7.27.6
|
|
||||||
'@babel/parser': 7.27.5
|
|
||||||
'@babel/template': 7.27.2
|
|
||||||
'@babel/traverse': 7.27.4
|
|
||||||
'@babel/types': 7.27.6
|
|
||||||
convert-source-map: 2.0.0
|
|
||||||
debug: 4.4.0
|
|
||||||
gensync: 1.0.0-beta.2
|
|
||||||
json5: 2.2.3
|
|
||||||
semver: 6.3.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/generator@7.27.5':
|
|
||||||
dependencies:
|
|
||||||
'@babel/parser': 7.27.5
|
|
||||||
'@babel/types': 7.27.6
|
|
||||||
'@jridgewell/gen-mapping': 0.3.8
|
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
|
||||||
jsesc: 3.1.0
|
|
||||||
|
|
||||||
'@babel/helper-compilation-targets@7.27.2':
|
|
||||||
dependencies:
|
|
||||||
'@babel/compat-data': 7.27.5
|
|
||||||
'@babel/helper-validator-option': 7.27.1
|
|
||||||
browserslist: 4.25.0
|
|
||||||
lru-cache: 5.1.1
|
|
||||||
semver: 6.3.1
|
|
||||||
|
|
||||||
'@babel/helper-module-imports@7.27.1':
|
|
||||||
dependencies:
|
|
||||||
'@babel/traverse': 7.27.4
|
|
||||||
'@babel/types': 7.27.6
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.27.4
|
|
||||||
'@babel/helper-module-imports': 7.27.1
|
|
||||||
'@babel/helper-validator-identifier': 7.27.1
|
|
||||||
'@babel/traverse': 7.27.4
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/helper-plugin-utils@7.27.1': {}
|
|
||||||
|
|
||||||
'@babel/helper-string-parser@7.25.9': {}
|
'@babel/helper-string-parser@7.25.9': {}
|
||||||
|
|
||||||
'@babel/helper-string-parser@7.27.1': {}
|
|
||||||
|
|
||||||
'@babel/helper-validator-identifier@7.25.9': {}
|
'@babel/helper-validator-identifier@7.25.9': {}
|
||||||
|
|
||||||
'@babel/helper-validator-identifier@7.27.1': {}
|
|
||||||
|
|
||||||
'@babel/helper-validator-option@7.27.1': {}
|
|
||||||
|
|
||||||
'@babel/helpers@7.27.6':
|
|
||||||
dependencies:
|
|
||||||
'@babel/template': 7.27.2
|
|
||||||
'@babel/types': 7.27.6
|
|
||||||
|
|
||||||
'@babel/parser@7.25.6':
|
'@babel/parser@7.25.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
@@ -5457,20 +5214,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.26.0
|
'@babel/types': 7.26.0
|
||||||
|
|
||||||
'@babel/parser@7.27.5':
|
|
||||||
dependencies:
|
|
||||||
'@babel/types': 7.27.6
|
|
||||||
|
|
||||||
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.27.4
|
|
||||||
'@babel/helper-plugin-utils': 7.27.1
|
|
||||||
|
|
||||||
'@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.4)':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.27.4
|
|
||||||
'@babel/helper-plugin-utils': 7.27.1
|
|
||||||
|
|
||||||
'@babel/runtime@7.25.6':
|
'@babel/runtime@7.25.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
@@ -5479,34 +5222,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
|
||||||
dependencies:
|
|
||||||
'@babel/code-frame': 7.27.1
|
|
||||||
'@babel/parser': 7.27.5
|
|
||||||
'@babel/types': 7.27.6
|
|
||||||
|
|
||||||
'@babel/traverse@7.27.4':
|
|
||||||
dependencies:
|
|
||||||
'@babel/code-frame': 7.27.1
|
|
||||||
'@babel/generator': 7.27.5
|
|
||||||
'@babel/parser': 7.27.5
|
|
||||||
'@babel/template': 7.27.2
|
|
||||||
'@babel/types': 7.27.6
|
|
||||||
debug: 4.4.0
|
|
||||||
globals: 11.12.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@babel/types@7.26.0':
|
'@babel/types@7.26.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-string-parser': 7.25.9
|
'@babel/helper-string-parser': 7.25.9
|
||||||
'@babel/helper-validator-identifier': 7.25.9
|
'@babel/helper-validator-identifier': 7.25.9
|
||||||
|
|
||||||
'@babel/types@7.27.6':
|
|
||||||
dependencies:
|
|
||||||
'@babel/helper-string-parser': 7.27.1
|
|
||||||
'@babel/helper-validator-identifier': 7.27.1
|
|
||||||
|
|
||||||
'@bcoe/v8-coverage@1.0.2': {}
|
'@bcoe/v8-coverage@1.0.2': {}
|
||||||
|
|
||||||
'@breezystack/lamejs@1.2.7': {}
|
'@breezystack/lamejs@1.2.7': {}
|
||||||
@@ -6224,8 +5944,6 @@ snapshots:
|
|||||||
|
|
||||||
'@rails/ujs@7.1.400': {}
|
'@rails/ujs@7.1.400': {}
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.11': {}
|
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.40.2':
|
'@rollup/rollup-android-arm-eabi@4.40.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -7026,27 +6744,6 @@ snapshots:
|
|||||||
|
|
||||||
'@tootallnate/once@2.0.0': {}
|
'@tootallnate/once@2.0.0': {}
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
|
||||||
dependencies:
|
|
||||||
'@babel/parser': 7.26.2
|
|
||||||
'@babel/types': 7.26.0
|
|
||||||
'@types/babel__generator': 7.27.0
|
|
||||||
'@types/babel__template': 7.4.4
|
|
||||||
'@types/babel__traverse': 7.20.7
|
|
||||||
|
|
||||||
'@types/babel__generator@7.27.0':
|
|
||||||
dependencies:
|
|
||||||
'@babel/types': 7.26.0
|
|
||||||
|
|
||||||
'@types/babel__template@7.4.4':
|
|
||||||
dependencies:
|
|
||||||
'@babel/parser': 7.26.2
|
|
||||||
'@babel/types': 7.26.0
|
|
||||||
|
|
||||||
'@types/babel__traverse@7.20.7':
|
|
||||||
dependencies:
|
|
||||||
'@babel/types': 7.26.0
|
|
||||||
|
|
||||||
'@types/estree@1.0.7': {}
|
'@types/estree@1.0.7': {}
|
||||||
|
|
||||||
'@types/flexsearch@0.7.6': {}
|
'@types/flexsearch@0.7.6': {}
|
||||||
@@ -7105,18 +6802,6 @@ snapshots:
|
|||||||
global: 4.4.0
|
global: 4.4.0
|
||||||
is-function: 1.0.2
|
is-function: 1.0.2
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.5.2(vite@5.4.19(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))':
|
|
||||||
dependencies:
|
|
||||||
'@babel/core': 7.27.4
|
|
||||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4)
|
|
||||||
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.4)
|
|
||||||
'@rolldown/pluginutils': 1.0.0-beta.11
|
|
||||||
'@types/babel__core': 7.20.5
|
|
||||||
react-refresh: 0.17.0
|
|
||||||
vite: 5.4.19(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@vitejs/plugin-vue@5.1.4(vite@5.4.19(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))':
|
'@vitejs/plugin-vue@5.1.4(vite@5.4.19(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 5.4.19(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
|
vite: 5.4.19(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
|
||||||
@@ -7609,13 +7294,6 @@ snapshots:
|
|||||||
node-releases: 2.0.18
|
node-releases: 2.0.18
|
||||||
update-browserslist-db: 1.1.0(browserslist@4.23.3)
|
update-browserslist-db: 1.1.0(browserslist@4.23.3)
|
||||||
|
|
||||||
browserslist@4.25.0:
|
|
||||||
dependencies:
|
|
||||||
caniuse-lite: 1.0.30001721
|
|
||||||
electron-to-chromium: 1.5.166
|
|
||||||
node-releases: 2.0.19
|
|
||||||
update-browserslist-db: 1.1.3(browserslist@4.25.0)
|
|
||||||
|
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -7661,8 +7339,6 @@ snapshots:
|
|||||||
|
|
||||||
caniuse-lite@1.0.30001651: {}
|
caniuse-lite@1.0.30001651: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001721: {}
|
|
||||||
|
|
||||||
capital-case@1.0.4:
|
capital-case@1.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
no-case: 3.0.4
|
no-case: 3.0.4
|
||||||
@@ -7816,8 +7492,6 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
upper-case: 2.0.2
|
upper-case: 2.0.2
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
|
||||||
|
|
||||||
core-js@3.38.1: {}
|
core-js@3.38.1: {}
|
||||||
|
|
||||||
countries-and-timezones@3.6.0: {}
|
countries-and-timezones@3.6.0: {}
|
||||||
@@ -8028,8 +7702,6 @@ snapshots:
|
|||||||
|
|
||||||
electron-to-chromium@1.5.13: {}
|
electron-to-chromium@1.5.13: {}
|
||||||
|
|
||||||
electron-to-chromium@1.5.166: {}
|
|
||||||
|
|
||||||
email-validator@2.0.4: {}
|
email-validator@2.0.4: {}
|
||||||
|
|
||||||
emoji-regex@10.4.0: {}
|
emoji-regex@10.4.0: {}
|
||||||
@@ -8553,8 +8225,6 @@ snapshots:
|
|||||||
|
|
||||||
functions-have-names@1.2.3: {}
|
functions-have-names@1.2.3: {}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
|
||||||
|
|
||||||
get-caller-file@2.0.5: {}
|
get-caller-file@2.0.5: {}
|
||||||
|
|
||||||
get-east-asian-width@1.2.0: {}
|
get-east-asian-width@1.2.0: {}
|
||||||
@@ -8633,8 +8303,6 @@ snapshots:
|
|||||||
min-document: 2.19.0
|
min-document: 2.19.0
|
||||||
process: 0.11.10
|
process: 0.11.10
|
||||||
|
|
||||||
globals@11.12.0: {}
|
|
||||||
|
|
||||||
globals@13.24.0:
|
globals@13.24.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 0.20.2
|
type-fest: 0.20.2
|
||||||
@@ -9035,8 +8703,6 @@ snapshots:
|
|||||||
|
|
||||||
js-cookie@3.0.5: {}
|
js-cookie@3.0.5: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
|
||||||
|
|
||||||
js-yaml@3.14.1:
|
js-yaml@3.14.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 1.0.10
|
argparse: 1.0.10
|
||||||
@@ -9107,8 +8773,6 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
jsesc@3.1.0: {}
|
|
||||||
|
|
||||||
json-buffer@3.0.1: {}
|
json-buffer@3.0.1: {}
|
||||||
|
|
||||||
json-logic-js@2.0.5: {}
|
json-logic-js@2.0.5: {}
|
||||||
@@ -9267,10 +8931,6 @@ snapshots:
|
|||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
|
||||||
dependencies:
|
|
||||||
yallist: 3.1.1
|
|
||||||
|
|
||||||
lru-cache@6.0.0:
|
lru-cache@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
@@ -9467,8 +9127,6 @@ snapshots:
|
|||||||
|
|
||||||
node-releases@2.0.18: {}
|
node-releases@2.0.18: {}
|
||||||
|
|
||||||
node-releases@2.0.19: {}
|
|
||||||
|
|
||||||
nopt@7.2.1:
|
nopt@7.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
abbrev: 2.0.0
|
abbrev: 2.0.0
|
||||||
@@ -10048,15 +9706,6 @@ snapshots:
|
|||||||
|
|
||||||
quick-lru@6.1.2: {}
|
quick-lru@6.1.2: {}
|
||||||
|
|
||||||
react-dom@19.1.0(react@19.1.0):
|
|
||||||
dependencies:
|
|
||||||
react: 19.1.0
|
|
||||||
scheduler: 0.26.0
|
|
||||||
|
|
||||||
react-refresh@0.17.0: {}
|
|
||||||
|
|
||||||
react@19.1.0: {}
|
|
||||||
|
|
||||||
read-cache@1.0.0:
|
read-cache@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pify: 2.3.0
|
pify: 2.3.0
|
||||||
@@ -10204,8 +9853,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
xmlchars: 2.2.0
|
xmlchars: 2.2.0
|
||||||
|
|
||||||
scheduler@0.26.0: {}
|
|
||||||
|
|
||||||
sdp@3.2.0: {}
|
sdp@3.2.0: {}
|
||||||
|
|
||||||
section-matter@1.0.0:
|
section-matter@1.0.0:
|
||||||
@@ -10666,12 +10313,6 @@ snapshots:
|
|||||||
escalade: 3.2.0
|
escalade: 3.2.0
|
||||||
picocolors: 1.0.1
|
picocolors: 1.0.1
|
||||||
|
|
||||||
update-browserslist-db@1.1.3(browserslist@4.25.0):
|
|
||||||
dependencies:
|
|
||||||
browserslist: 4.25.0
|
|
||||||
escalade: 3.2.0
|
|
||||||
picocolors: 1.1.1
|
|
||||||
|
|
||||||
upper-case-first@2.0.2:
|
upper-case-first@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -11024,8 +10665,6 @@ snapshots:
|
|||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
|
||||||
|
|
||||||
yallist@4.0.0: {}
|
yallist@4.0.0: {}
|
||||||
|
|
||||||
yaml-eslint-parser@1.2.3:
|
yaml-eslint-parser@1.2.3:
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Web Components Test</title>
|
|
||||||
<script>
|
|
||||||
window.__WOOT_ISOLATED_SHELL__ = true;
|
|
||||||
window.__WOOT_ACCOUNT_ID__ = 1;
|
|
||||||
window.__WOOT_API_HOST__ = 'http://localhost:3000';
|
|
||||||
window.__WOOT_ACCESS_TOKEN__ = 'token';
|
|
||||||
window.__PUBSUB_TOKEN__ = 'token';
|
|
||||||
window.__WEBSOCKET_URL__ = 'ws://localhost:3000';
|
|
||||||
window.__WOOT_CONVERSATION_ID__ = 13918;
|
|
||||||
window.__DISABLE_EDITOR__ = true;
|
|
||||||
window.__EDITOR_DISABLE_UPLOAD__ = true;
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app" dir="ltr">
|
|
||||||
<div class="py-20 grid place-content-center">
|
|
||||||
<svg
|
|
||||||
width="40"
|
|
||||||
height="40"
|
|
||||||
viewBox="0 0 40 40"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
cx="20"
|
|
||||||
cy="20"
|
|
||||||
r="18"
|
|
||||||
fill="none"
|
|
||||||
stroke="#ccc"
|
|
||||||
stroke-width="4"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="20"
|
|
||||||
cy="20"
|
|
||||||
r="18"
|
|
||||||
fill="none"
|
|
||||||
stroke="#333"
|
|
||||||
stroke-width="4"
|
|
||||||
stroke-dasharray="56.5487 56.5487"
|
|
||||||
stroke-dashoffset="0"
|
|
||||||
>
|
|
||||||
<animateTransform
|
|
||||||
attributeName="transform"
|
|
||||||
type="rotate"
|
|
||||||
from="0 20 20"
|
|
||||||
to="360 20 20"
|
|
||||||
dur="1s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<link rel="stylesheet" href="/packs/style.css" />
|
|
||||||
<script src="/packs/js/ui.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
|
|
||||||
// Check for copy-only mode
|
|
||||||
const isCopyMode = process.argv.includes('--copyMode');
|
|
||||||
|
|
||||||
if (isCopyMode) {
|
|
||||||
console.log('📋 Copy mode: Copying built files to dist/react-components...');
|
|
||||||
} else {
|
|
||||||
console.log('🚀 Building Chatwoot React Components for NPM...');
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyBuildFiles(packageDir) {
|
|
||||||
// Copy main build outputs
|
|
||||||
const files = [
|
|
||||||
{ src: 'public/packs/react-components.es.js', dest: 'index.js' }, // ES module (main)
|
|
||||||
{ src: 'public/packs/react-components.cjs.js', dest: 'index.cjs' }, // CommonJS
|
|
||||||
{ src: 'public/packs/style.css', dest: 'style.css' }, // CSS styles
|
|
||||||
];
|
|
||||||
|
|
||||||
files.forEach(({ src, dest }) => {
|
|
||||||
if (fs.existsSync(src)) {
|
|
||||||
fs.copyFileSync(src, path.join(packageDir, dest));
|
|
||||||
console.log(` ✓ Copied ${dest}`);
|
|
||||||
} else {
|
|
||||||
console.warn(` ⚠️ Warning: ${src} not found`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatePackageJson(packageDir) {
|
|
||||||
// Read version from main package.json
|
|
||||||
const mainPackage = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
||||||
|
|
||||||
// Generate a clean epoch-based version
|
|
||||||
function generateTimestampVersion() {
|
|
||||||
try {
|
|
||||||
const epochSeconds = Math.floor(Date.now() / 1000);
|
|
||||||
|
|
||||||
// Get git hash for reference
|
|
||||||
const gitHash = execSync('git rev-parse --short HEAD', {
|
|
||||||
encoding: 'utf8',
|
|
||||||
stdio: 'pipe',
|
|
||||||
}).trim();
|
|
||||||
|
|
||||||
// Parse base version and increment patch
|
|
||||||
const [major, minor, patch] = mainPackage.version.split('.').map(Number);
|
|
||||||
const newPatch = patch + 1;
|
|
||||||
|
|
||||||
return `${major}.${minor}.${newPatch}-${epochSeconds}.${gitHash}`;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
' ⚠️ Could not generate timestamp version, using incremented patch'
|
|
||||||
);
|
|
||||||
const [major, minor, patch] = mainPackage.version.split('.').map(Number);
|
|
||||||
return `${major}.${minor}.${patch + 1}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalVersion = generateTimestampVersion();
|
|
||||||
|
|
||||||
console.log(` 📋 Package version: ${finalVersion}`);
|
|
||||||
|
|
||||||
const packageJson = {
|
|
||||||
name: '@chatwoot/agent-react-components',
|
|
||||||
version: finalVersion,
|
|
||||||
description:
|
|
||||||
'React components for Chatwoot messaging interface with Vue Web Components',
|
|
||||||
|
|
||||||
// Entry points for different module systems
|
|
||||||
main: 'index.cjs', // CommonJS entry point for Node.js
|
|
||||||
module: 'index.js', // ES module entry point for bundlers
|
|
||||||
exports: {
|
|
||||||
'.': {
|
|
||||||
import: './index.js', // ES modules
|
|
||||||
require: './index.cjs', // CommonJS
|
|
||||||
},
|
|
||||||
'./style.css': './style.css',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Peer dependencies - what the consuming app must provide
|
|
||||||
peerDependencies: {
|
|
||||||
react: '>=16.8.0', // Hooks support
|
|
||||||
'react-dom': '>=16.8.0',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Package metadata
|
|
||||||
keywords: [
|
|
||||||
'chatwoot',
|
|
||||||
'react',
|
|
||||||
'vue',
|
|
||||||
'webcomponents',
|
|
||||||
'chat',
|
|
||||||
'messaging',
|
|
||||||
'customer-support',
|
|
||||||
'ui-components',
|
|
||||||
],
|
|
||||||
author: 'Chatwoot',
|
|
||||||
license: 'MIT',
|
|
||||||
repository: {
|
|
||||||
type: 'git',
|
|
||||||
url: 'https://github.com/chatwoot/chatwoot.git',
|
|
||||||
directory: 'app/javascript/react-components',
|
|
||||||
},
|
|
||||||
homepage: 'https://chatwoot.com',
|
|
||||||
bugs: {
|
|
||||||
url: 'https://github.com/chatwoot/chatwoot/issues',
|
|
||||||
},
|
|
||||||
|
|
||||||
// npm publish configuration
|
|
||||||
files: ['index.js', 'index.cjs', 'style.css', 'package.json'],
|
|
||||||
|
|
||||||
// Engine requirements
|
|
||||||
engines: {
|
|
||||||
node: '>=16.0.0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(packageDir, 'package.json'),
|
|
||||||
JSON.stringify(packageJson, null, 2)
|
|
||||||
);
|
|
||||||
console.log(' ✓ Generated package.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function publishReactComponents() {
|
|
||||||
try {
|
|
||||||
if (!isCopyMode) {
|
|
||||||
// Step 1: Clean previous builds
|
|
||||||
console.log('📦 Cleaning previous builds...');
|
|
||||||
if (fs.existsSync('dist')) {
|
|
||||||
fs.rmSync('dist', { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean previous React component builds
|
|
||||||
const reactFiles = [
|
|
||||||
'public/packs/react-components.es.js',
|
|
||||||
'public/packs/react-components.cjs.js',
|
|
||||||
'public/packs/style.css',
|
|
||||||
];
|
|
||||||
|
|
||||||
reactFiles.forEach(file => {
|
|
||||||
if (fs.existsSync(file)) {
|
|
||||||
fs.unlinkSync(file);
|
|
||||||
console.log(` 🗑️ Removed ${file}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step 2: Build the React components library
|
|
||||||
console.log('🔨 Building React components...');
|
|
||||||
execSync('pnpm build:react', { stdio: 'inherit' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Create package directory
|
|
||||||
console.log('📁 Creating package directory...');
|
|
||||||
const packageDir = 'dist/react-components';
|
|
||||||
fs.mkdirSync(packageDir, { recursive: true });
|
|
||||||
|
|
||||||
// Step 4: Copy built files
|
|
||||||
console.log('📋 Copying built files...');
|
|
||||||
copyBuildFiles(packageDir);
|
|
||||||
|
|
||||||
// Step 5: Generate package.json
|
|
||||||
console.log('📄 Generating package.json...');
|
|
||||||
generatePackageJson(packageDir);
|
|
||||||
|
|
||||||
console.log('✅ Package ready in dist/react-components/');
|
|
||||||
console.log('');
|
|
||||||
console.log('📦 Publishing options:');
|
|
||||||
console.log(' • npm publish: cd dist/react-components && npm publish');
|
|
||||||
console.log(' • yalc publish: pnpm package:react:yalc-publish');
|
|
||||||
console.log(' • local test: cd dist/react-components && npm pack');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Build failed:', error.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the script
|
|
||||||
publishReactComponents();
|
|
||||||
/* eslint-enable no-console */
|
|
||||||
@@ -29,13 +29,10 @@ const tailwindConfig = {
|
|||||||
'./app/javascript/portal/**/*.vue',
|
'./app/javascript/portal/**/*.vue',
|
||||||
'./app/javascript/shared/**/*.vue',
|
'./app/javascript/shared/**/*.vue',
|
||||||
'./app/javascript/survey/**/*.vue',
|
'./app/javascript/survey/**/*.vue',
|
||||||
'./app/javascript/ui/**/*.vue',
|
|
||||||
'./app/javascript/dashboard/components-next/**/*.vue',
|
'./app/javascript/dashboard/components-next/**/*.vue',
|
||||||
'./app/javascript/dashboard/helper/**/*.js',
|
'./app/javascript/dashboard/helper/**/*.js',
|
||||||
'./app/javascript/dashboard/components-next/**/*.js',
|
'./app/javascript/dashboard/components-next/**/*.js',
|
||||||
'./app/javascript/dashboard/routes/dashboard/**/**/*.js',
|
'./app/javascript/dashboard/routes/dashboard/**/**/*.js',
|
||||||
'./app/javascript/react-components/**/*.{js,jsx,vue}',
|
|
||||||
'./app/javascript/dashboard/assets/scss/**/*.scss',
|
|
||||||
'./app/views/**/*.html.erb',
|
'./app/views/**/*.html.erb',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
+15
-65
@@ -22,11 +22,8 @@ import { defineConfig } from 'vite';
|
|||||||
import ruby from 'vite-plugin-ruby';
|
import ruby from 'vite-plugin-ruby';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import react from '@vitejs/plugin-react';
|
|
||||||
|
|
||||||
const isLibraryMode = process.env.BUILD_MODE === 'library';
|
const isLibraryMode = process.env.BUILD_MODE === 'library';
|
||||||
const uiMode = process.env.BUILD_MODE === 'ui';
|
|
||||||
const reactComponentMode = process.env.BUILD_MODE === 'react-components';
|
|
||||||
const isTestMode = process.env.TEST === 'true';
|
const isTestMode = process.env.TEST === 'true';
|
||||||
|
|
||||||
const vueOptions = {
|
const vueOptions = {
|
||||||
@@ -41,85 +38,38 @@ let plugins = [ruby(), vue(vueOptions)];
|
|||||||
|
|
||||||
if (isLibraryMode) {
|
if (isLibraryMode) {
|
||||||
plugins = [];
|
plugins = [];
|
||||||
} else if (uiMode) {
|
|
||||||
plugins = [vue()];
|
|
||||||
} else if (reactComponentMode) {
|
|
||||||
plugins = [vue({ ...vueOptions, customElement: true }), react()];
|
|
||||||
} else if (isTestMode) {
|
} else if (isTestMode) {
|
||||||
plugins = [vue(vueOptions)];
|
plugins = [vue(vueOptions)];
|
||||||
}
|
}
|
||||||
|
|
||||||
let lib;
|
|
||||||
let rollupOptions = {};
|
|
||||||
|
|
||||||
if (isLibraryMode) {
|
|
||||||
lib = {
|
|
||||||
entry: path.resolve(__dirname, './app/javascript/entrypoints/sdk.js'),
|
|
||||||
formats: ['iife'], // IIFE format for single file
|
|
||||||
name: 'sdk',
|
|
||||||
};
|
|
||||||
} else if (uiMode) {
|
|
||||||
lib = {
|
|
||||||
entry: path.resolve(__dirname, './app/javascript/entrypoints/ui.js'),
|
|
||||||
formats: ['iife'], // IIFE format for single file
|
|
||||||
name: 'ui',
|
|
||||||
};
|
|
||||||
} else if (reactComponentMode) {
|
|
||||||
lib = {
|
|
||||||
entry: path.resolve(
|
|
||||||
__dirname,
|
|
||||||
'./app/javascript/react-components/src/index.jsx'
|
|
||||||
),
|
|
||||||
formats: ['es', 'cjs'], // ES modules and CommonJS only
|
|
||||||
fileName: format => `react-components.${format}.js`,
|
|
||||||
};
|
|
||||||
|
|
||||||
rollupOptions = {
|
|
||||||
external: ['react', 'react-dom'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunkBuilder = chunkInfo => {
|
|
||||||
if (chunkInfo.name === 'sdk') {
|
|
||||||
return 'js/sdk.js';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunkInfo.name === 'ui') {
|
|
||||||
return `js/ui.js`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For react components, we need to return different names but can't access format here
|
|
||||||
// So we'll handle this differently
|
|
||||||
return '[name].js';
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: plugins,
|
plugins: plugins,
|
||||||
define: {
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
||||||
},
|
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
...rollupOptions,
|
|
||||||
output: {
|
output: {
|
||||||
...rollupOptions.output,
|
|
||||||
// [NOTE] when not in library mode, no new keys will be addedd or overwritten
|
// [NOTE] when not in library mode, no new keys will be addedd or overwritten
|
||||||
// setting dir: isLibraryMode ? 'public/packs' : undefined will not work
|
// setting dir: isLibraryMode ? 'public/packs' : undefined will not work
|
||||||
...(isLibraryMode || uiMode
|
...(isLibraryMode
|
||||||
? {
|
? {
|
||||||
dir: 'public/packs',
|
dir: 'public/packs',
|
||||||
entryFileNames: chunkBuilder,
|
entryFileNames: chunkInfo => {
|
||||||
|
if (chunkInfo.name === 'sdk') {
|
||||||
|
return 'js/sdk.js';
|
||||||
|
}
|
||||||
|
return '[name].js';
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(reactComponentMode
|
inlineDynamicImports: isLibraryMode, // Disable code-splitting for SDK
|
||||||
? {
|
|
||||||
dir: 'public/packs',
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
inlineDynamicImports: isLibraryMode || uiMode || reactComponentMode, // Disable code-splitting for SDK
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lib,
|
lib: isLibraryMode
|
||||||
|
? {
|
||||||
|
entry: path.resolve(__dirname, './app/javascript/entrypoints/sdk.js'),
|
||||||
|
formats: ['iife'], // IIFE format for single file
|
||||||
|
name: 'sdk',
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user