mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-11 13:28:57 +00:00
fix: email channel content not being rendered in template
- fix: resolved_at, closed_at timestamps getting cleared. - feat: generic code editor for Email templates - feat: Quill editor for canned responses - Refactor: to reuse forms across /admin/conversations components - Update sample toml remove unused key. - Fix: multiple system user pass prompts when setting up for the first time - Remove unused file session.go - Fix: Inline images showing up in reply box as an attachment - Update vite config to proxy requests to `/uploads`
This commit is contained in:
@@ -78,7 +78,6 @@ func main() {
|
||||
// Installer.
|
||||
if ko.Bool("install") {
|
||||
install(db, fs)
|
||||
setSystemUserPass(db)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package main
|
||||
+1
-1
@@ -5,7 +5,7 @@ env = "dev"
|
||||
|
||||
# HTTP server.
|
||||
[app.server]
|
||||
address = "0.0.0.0:9009"
|
||||
address = "0.0.0.0:9000"
|
||||
socket = ""
|
||||
read_timeout = "5s"
|
||||
write_timeout = "5s"
|
||||
|
||||
@@ -33,10 +33,12 @@
|
||||
"@vee-validate/zod": "^4.13.2",
|
||||
"@vue/reactivity": "^3.4.15",
|
||||
"@vue/runtime-core": "^3.4.15",
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"add": "^2.0.6",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"codeflask": "^1.4.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"install": "^0.13.0",
|
||||
"lucide-vue-next": "^0.378.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Toaster />
|
||||
<TooltipProvider :delay-duration="200">
|
||||
<div class="font-poppins">
|
||||
<div class="font-inter">
|
||||
<div v-if="$route.path !== '/'">
|
||||
<div class="flex">
|
||||
<NavBar :is-collapsed="isCollapsed" :links="navLinks" :bottom-links="bottomLinks"
|
||||
@@ -24,24 +24,24 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed, onUnmounted } from 'vue'
|
||||
import { ref, onMounted, computed, onUnmounted } from 'vue'
|
||||
import { RouterView, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { initWS } from '@/websocket.js'
|
||||
import { useEmitter } from '@/composables/useEmitter'
|
||||
import { Toaster } from '@/components/ui/toast'
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
import { useToast } from '@/components/ui/toast/use-toast'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { toast } = useToast()
|
||||
const emitter = useEmitter()
|
||||
const isCollapsed = ref(true)
|
||||
const allNavLinks = reactive([
|
||||
const allNavLinks = [
|
||||
{
|
||||
title: t('navbar.dashboard'),
|
||||
to: '/dashboard',
|
||||
@@ -68,15 +68,15 @@ const allNavLinks = reactive([
|
||||
icon: 'lucide:settings',
|
||||
permission: 'admin:read'
|
||||
}
|
||||
])
|
||||
]
|
||||
|
||||
const bottomLinks = ref([
|
||||
const bottomLinks = [
|
||||
{
|
||||
to: '/logout',
|
||||
icon: 'lucide:log-out',
|
||||
title: 'Logout'
|
||||
}
|
||||
])
|
||||
]
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -93,7 +93,7 @@ onUnmounted(() => {
|
||||
const getCurrentUser = () => {
|
||||
userStore.getCurrentUser().catch((err) => {
|
||||
if (err.response && err.response.status === 401) {
|
||||
router.push('/login')
|
||||
router.push('/')
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -104,7 +104,7 @@ const initToaster = () => {
|
||||
|
||||
const navLinks = computed(() =>
|
||||
allNavLinks.filter((link) =>
|
||||
link.permission ? userStore.hasPermission(link.permission) : true
|
||||
!link.permission || (userStore.userPermissions.includes(link.permission) && link.permission)
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -173,3 +173,35 @@ body {
|
||||
.admin-main-content {
|
||||
@apply p-0;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #888;
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.code-editor {
|
||||
@apply rounded-md border shadow h-[65vh] min-h-[250px] w-full relative
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
margin-top: 0 !important;
|
||||
height: 200px !important;
|
||||
}
|
||||
@@ -44,9 +44,9 @@ const allNavItems = [
|
||||
permission: null,
|
||||
},
|
||||
{
|
||||
title: 'Templates',
|
||||
title: 'Email templates',
|
||||
href: '/admin/templates',
|
||||
description: 'Manage email templates',
|
||||
description: 'Manage outgoing email templates',
|
||||
permission: null,
|
||||
},
|
||||
{
|
||||
@@ -58,7 +58,7 @@ const allNavItems = [
|
||||
]
|
||||
|
||||
const sidebarNavItems = computed(() =>
|
||||
allNavItems.filter((item) => userStore.hasPermission(item.permission))
|
||||
allNavItems.filter((item) => !item.permission || item.permission && userStore.userPermissions.includes(item.permission))
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -9,34 +9,16 @@
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a canned response</DialogTitle>
|
||||
<DialogDescription> Set canned response name. Click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<FormField v-slot="{ field }" name="title">
|
||||
<FormItem>
|
||||
<FormLabel>Title</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="" v-bind="field" />
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="content">
|
||||
<FormItem>
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea v-bind="componentField" class="h-52"></Textarea>
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<DialogFooter class="mt-7">
|
||||
<Button type="submit" size="sm">Save Changes</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
<DialogTitle>New canned response</DialogTitle>
|
||||
<DialogDescription>Set title and content, click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<CannedResponsesForm @submit="onSubmit">
|
||||
<template #footer>
|
||||
<DialogFooter class="mt-7">
|
||||
<Button type="submit" size="sm">Save Changes</Button>
|
||||
</DialogFooter>
|
||||
</template>
|
||||
</CannedResponsesForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
@@ -54,17 +36,7 @@ import DataTable from '@/components/admin/DataTable.vue'
|
||||
import { columns } from './dataTableColumns.js'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import PageHeader from '@/components/admin/common/PageHeader.vue'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -74,6 +46,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/dialog'
|
||||
import CannedResponsesForm from './CannedResponsesForm.vue'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { formSchema } from './formSchema.js'
|
||||
@@ -86,9 +59,17 @@ const cannedResponses = ref([])
|
||||
const emit = useEmitter()
|
||||
const dialogOpen = ref(false)
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(formSchema)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getCannedResponses()
|
||||
emit.on(EMITTER_EVENTS.REFRESH_LIST, refreshList)
|
||||
form.setValues({
|
||||
title: "",
|
||||
content: "",
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -99,10 +80,6 @@ const refreshList = (data) => {
|
||||
if (data?.model === 'canned_responses') getCannedResponses()
|
||||
}
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(formSchema)
|
||||
})
|
||||
|
||||
const getCannedResponses = async () => {
|
||||
try {
|
||||
formLoading.value = true
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<form class="space-y-6">
|
||||
<FormField v-slot="{ componentField }" name="title">
|
||||
<FormItem>
|
||||
<FormLabel>Title</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField, handleInput }" name="content">
|
||||
<FormItem>
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<QuillEditor theme="snow" v-model:content="componentField.modelValue" contentType="html"
|
||||
@update:content="handleInput"></QuillEditor>
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Form submit button slot -->
|
||||
<slot name="footer"></slot>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { QuillEditor } from '@vueup/vue-quill'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
</script>
|
||||
@@ -34,12 +34,12 @@ export const columns = [
|
||||
id: 'actions',
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const canned_response = row.original
|
||||
const cannedResponse = row.original
|
||||
return h(
|
||||
'div',
|
||||
{ class: 'relative' },
|
||||
h(dropdown, {
|
||||
canned_response
|
||||
cannedResponse
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,33 +17,15 @@
|
||||
<DialogContent class="sm:max-w-[625px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit canned response</DialogTitle>
|
||||
<DialogDescription>Click save when you're done. </DialogDescription>
|
||||
<DialogDescription>Edit title and content, click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="title">
|
||||
<FormItem>
|
||||
<FormLabel>Title</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="content">
|
||||
<FormItem>
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea v-bind="componentField" class="h-52"></Textarea>
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<DialogFooter>
|
||||
<Button type="submit" size="sm"> Save changes </Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
<CannedResponsesForm @submit="onSubmit">
|
||||
<template #footer>
|
||||
<DialogFooter class="mt-7">
|
||||
<Button type="submit" size="sm">Save Changes</Button>
|
||||
</DialogFooter>
|
||||
</template>
|
||||
</CannedResponsesForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -60,15 +42,9 @@ import {
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import CannedResponsesForm from './CannedResponsesForm.vue'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
import { formSchema } from './formSchema.js'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -78,8 +54,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useEmitter } from '@/composables/useEmitter'
|
||||
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
|
||||
import api from '@/api/index.js'
|
||||
@@ -88,13 +62,9 @@ const dialogOpen = ref(false)
|
||||
const emit = useEmitter()
|
||||
|
||||
const props = defineProps({
|
||||
canned_response: {
|
||||
cannedResponse: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
id: '',
|
||||
name: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -103,13 +73,13 @@ const form = useForm({
|
||||
})
|
||||
|
||||
const onSubmit = form.handleSubmit(async (values) => {
|
||||
await api.updateCannedResponse(props.canned_response.id, values)
|
||||
await api.updateCannedResponse(props.cannedResponse.id, values)
|
||||
dialogOpen.value = false
|
||||
emitRefreshCannedResponseList()
|
||||
})
|
||||
|
||||
const deleteCannedResponse = async () => {
|
||||
await api.deleteCannedResponse(props.canned_response.id)
|
||||
await api.deleteCannedResponse(props.cannedResponse.id)
|
||||
dialogOpen.value = false
|
||||
emitRefreshCannedResponseList()
|
||||
}
|
||||
@@ -122,7 +92,7 @@ const emitRefreshCannedResponseList = () => {
|
||||
|
||||
// Watch for changes in initialValues and update the form.
|
||||
watch(
|
||||
() => props.canned_response,
|
||||
() => props.cannedResponse,
|
||||
(newValues) => {
|
||||
form.setValues(newValues)
|
||||
},
|
||||
|
||||
@@ -12,26 +12,17 @@
|
||||
<DialogTitle>New status</DialogTitle>
|
||||
<DialogDescription> Set status name. Click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<FormField v-slot="{ field }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="Processing" v-bind="field" />
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<DialogFooter class="mt-7">
|
||||
<Button type="submit" size="sm">Save Changes</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
<StatusForm @submit.prevent="onSubmit">
|
||||
<template #footer>
|
||||
<DialogFooter class="mt-10">
|
||||
<Button type="submit" size="sm"> Save changes </Button>
|
||||
</DialogFooter>
|
||||
</template>
|
||||
</StatusForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Spinner v-if="isLoading"></Spinner>
|
||||
<div>
|
||||
<DataTable :columns="columns" :data="statuses" />
|
||||
@@ -46,15 +37,7 @@ import { columns } from './dataTableColumns.js'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import PageHeader from '@/components/admin/common/PageHeader.vue'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import StatusForm from './StatusForm.vue'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<form>
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="Spam" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<!-- Form submit button slot -->
|
||||
<slot name="footer"></slot>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
</script>
|
||||
@@ -19,21 +19,13 @@
|
||||
<DialogTitle>Edit status</DialogTitle>
|
||||
<DialogDescription> Change the status name. Click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="billing, tech" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription>Renaming the status will rename it across all conversations.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<DialogFooter>
|
||||
<Button type="submit" size="sm"> Save changes </Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
<StatusForm @submit.prevent="onSubmit">
|
||||
<template #footer>
|
||||
<DialogFooter class="mt-10">
|
||||
<Button type="submit" size="sm"> Save changes </Button>
|
||||
</DialogFooter>
|
||||
</template>
|
||||
</StatusForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -51,14 +43,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { formSchema } from './formSchema.js'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import StatusForm from './StatusForm.vue'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -68,7 +53,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useEmitter } from '@/composables/useEmitter'
|
||||
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
|
||||
import api from '@/api/index.js'
|
||||
@@ -80,10 +64,6 @@ const props = defineProps({
|
||||
status: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
id: '',
|
||||
name: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,40 +1,31 @@
|
||||
<template>
|
||||
<div class="flex justify-between mb-5">
|
||||
<PageHeader title="Tags" description="Manage conversation tags" />
|
||||
<div class="flex justify-end mb-4">
|
||||
<Dialog v-model:open="dialogOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button size="sm">New Tag</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a Tag</DialogTitle>
|
||||
<DialogDescription> Set tag name. Click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<FormField v-slot="{ field }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="billing, tech" v-bind="field" />
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<DialogFooter class="mt-7">
|
||||
<Button type="submit" size="sm">Save Changes</Button>
|
||||
<div class="flex justify-between mb-5">
|
||||
<PageHeader title="Tags" description="Manage conversation tags" />
|
||||
<div class="flex justify-end mb-4">
|
||||
<Dialog v-model:open="dialogOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button size="sm">New Tag</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create new tag</DialogTitle>
|
||||
<DialogDescription> Set tag name. Click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<TagsForm @submit.prevent="onSubmit">
|
||||
<template #footer>
|
||||
<DialogFooter class="mt-10">
|
||||
<Button type="submit" size="sm"> Save changes </Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Spinner v-if="isLoading"></Spinner>
|
||||
<div v-else>
|
||||
<DataTable :columns="columns" :data="tags" />
|
||||
</template>
|
||||
</TagsForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
<Spinner v-if="isLoading"></Spinner>
|
||||
<div v-else>
|
||||
<DataTable :columns="columns" :data="tags" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -44,15 +35,7 @@ import { Spinner } from '@/components/ui/spinner'
|
||||
import { columns } from '@/components/admin/conversation/tags/dataTableColumns.js'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import PageHeader from '@/components/admin/common/PageHeader.vue'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import TagsForm from './TagsForm.vue'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<form>
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="billing, order" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription></FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<!-- Form submit button slot -->
|
||||
<slot name="footer" ></slot>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
</script>
|
||||
@@ -19,23 +19,13 @@
|
||||
<DialogTitle>Edit tag</DialogTitle>
|
||||
<DialogDescription> Change the tag name. Click save when you're done. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="billing, tech" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription
|
||||
>Renaming the tag will rename it across all conversations.</FormDescription
|
||||
>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<DialogFooter>
|
||||
<Button type="submit" size="sm"> Save changes </Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
<TagsForm @submit.prevent="onSubmit">
|
||||
<template #footer>
|
||||
<DialogFooter class="mt-10">
|
||||
<Button type="submit" size="sm"> Save changes </Button>
|
||||
</DialogFooter>
|
||||
</template>
|
||||
</TagsForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -53,14 +43,6 @@ import { Button } from '@/components/ui/button'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { formSchema } from './formSchema.js'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -70,9 +52,9 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useEmitter } from '@/composables/useEmitter'
|
||||
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
|
||||
import TagsForm from './TagsForm.vue'
|
||||
import api from '@/api/index.js'
|
||||
|
||||
const dialogOpen = ref(false)
|
||||
|
||||
@@ -142,18 +142,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { smtpConfigSchema } from './formSchema.js';
|
||||
import { watch } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { smtpConfigSchema } from './formSchema.js'
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form';
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -161,10 +161,10 @@ import {
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/select';
|
||||
} from '@/components/ui/select'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
const props = defineProps({
|
||||
initialValues: {
|
||||
@@ -188,11 +188,11 @@ const props = defineProps({
|
||||
|
||||
const smtpForm = useForm({
|
||||
validationSchema: toTypedSchema(smtpConfigSchema)
|
||||
});
|
||||
})
|
||||
|
||||
const onSmtpSubmit = smtpForm.handleSubmit((values) => {
|
||||
props.submitForm(values);
|
||||
});
|
||||
})
|
||||
|
||||
// Watch for changes in initialValues and update the form.
|
||||
watch(
|
||||
@@ -201,5 +201,5 @@ watch(
|
||||
smtpForm.setValues(newValues);
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
)
|
||||
</script>
|
||||
@@ -19,7 +19,7 @@
|
||||
<Label>Auto assign conversations</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormDescription>Auto assign new conversations to agents in this team.</FormDescription>
|
||||
<FormDescription>Automatically assign new conversations to agents in this team in a round-robin fashion.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<form @submit="onSubmit" class="space-y-6">
|
||||
<form @submit.prevent="onSubmit" class="space-y-6">
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<FormItem v-auto-animate>
|
||||
<FormLabel>Name</FormLabel>
|
||||
@@ -10,13 +10,13 @@
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="body">
|
||||
<FormItem v-auto-animate>
|
||||
<FormLabel>HTML body</FormLabel>
|
||||
<FormField v-slot="{ componentField, handleChange }" name="body">
|
||||
<FormItem>
|
||||
<FormLabel>Body</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder="HTML here.." v-bind="componentField" class="h-52" />
|
||||
<CodeEditor v-model="componentField.modelValue" @update:modelValue="handleChange"></CodeEditor>
|
||||
</FormControl>
|
||||
<FormDescription>{{ templateBodyDescription() }}</FormDescription>
|
||||
<FormDescription>{{ `Make sure the template has \{\{ .Content \}\}` }}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
@@ -54,10 +54,9 @@ import {
|
||||
FormDescription
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import CodeEditor from '@/components/common/CodeEditor.vue';
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
const props = defineProps({
|
||||
initialValues: {
|
||||
type: Object,
|
||||
@@ -78,8 +77,6 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const templateBodyDescription = () => 'Make sure the template has {{ .Content }}'
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(formSchema)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex justify-between mb-5">
|
||||
<PageHeader title="Templates" description="Manage email templates" />
|
||||
<PageHeader title="Email Templates" description="Manage outgoing email templates" />
|
||||
<div class="flex justify-end mb-4">
|
||||
<Button @click="navigateToAddTemplate" size="sm"> New template </Button>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ export const formSchema = z.object({
|
||||
required_error: 'Template name is required.'
|
||||
}),
|
||||
body: z.string({
|
||||
required_error: 'Template body is required.'
|
||||
required_error: 'Template content is required.'
|
||||
}),
|
||||
is_default: z.boolean().optional()
|
||||
})
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex m-2 items-end text-sm overflow-hidden text-ellipsis whitespace-nowrap cursor-pointer"
|
||||
>
|
||||
<div
|
||||
v-for="attachment in attachments"
|
||||
:key="attachment.uuid"
|
||||
class="flex items-center p-1 bg-[#F5F5F4] gap-1 rounded-md max-w-[15rem]"
|
||||
>
|
||||
<div class="flex m-2 items-end text-sm overflow-hidden text-ellipsis whitespace-nowrap cursor-pointer">
|
||||
<div v-for="attachment in attachments" :key="attachment.uuid"
|
||||
class="flex items-center p-1 bg-[#F5F5F4] gap-1 rounded-md max-w-[15rem]">
|
||||
<!-- Filename tooltip -->
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
@@ -30,7 +25,6 @@
|
||||
|
||||
<script setup>
|
||||
import { formatBytes } from '@/utils/file.js'
|
||||
|
||||
import { X } from 'lucide-vue-next'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div ref="codeEditor" id="code-editor" class="code-editor" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, nextTick } from 'vue'
|
||||
import CodeFlask from 'codeflask'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
language: { type: String, default: 'html' },
|
||||
disabled: Boolean
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const codeEditor = ref(null)
|
||||
const data = ref('')
|
||||
const flask = ref(null)
|
||||
|
||||
const initCodeEditor = (body) => {
|
||||
const el = document.createElement('code-flask')
|
||||
el.attachShadow({ mode: 'open' })
|
||||
el.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
.codeflask .codeflask__flatten {
|
||||
font-size: 15px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.codeflask .codeflask__lines { background: #fafafa; z-index: 10; }
|
||||
.codeflask .token.tag { font-weight: bold; }
|
||||
.codeflask .token.attr-name { color: #111; }
|
||||
.codeflask .token.attr-value { color: #000 !important; }
|
||||
</style>
|
||||
<div id="area"></div>
|
||||
`
|
||||
codeEditor.value.appendChild(el)
|
||||
|
||||
flask.value = new CodeFlask(el.shadowRoot.getElementById('area'), {
|
||||
language: props.language,
|
||||
lineNumbers: false,
|
||||
styleParent: el.shadowRoot,
|
||||
readonly: props.disabled
|
||||
})
|
||||
|
||||
flask.value.onUpdate((v) => {
|
||||
emit('update:modelValue', v)
|
||||
data.value = v
|
||||
})
|
||||
|
||||
flask.value.updateCode(body)
|
||||
|
||||
nextTick(() => {
|
||||
document.querySelector('code-flask').shadowRoot.querySelector('textarea').focus()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initCodeEditor(props.modelValue || '')
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal !== data.value) {
|
||||
flask.value.updateCode(newVal)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -31,7 +31,7 @@
|
||||
:messageType="messageType" :contentToSet="contentToSet" :cannedResponses="cannedResponses" />
|
||||
|
||||
<!-- Attachments preview -->
|
||||
<AttachmentsPreview :attachments="uploadedFiles" :onDelete="handleOnFileDelete"></AttachmentsPreview>
|
||||
<AttachmentsPreview :attachments="attachments" :onDelete="handleOnFileDelete"></AttachmentsPreview>
|
||||
|
||||
<!-- Bottom menu bar -->
|
||||
<ReplyBoxBottomMenuBar :handleFileUpload="handleFileUpload" :handleInlineImageUpload="handleInlineImageUpload"
|
||||
@@ -100,7 +100,11 @@ const toggleItalic = () => {
|
||||
}
|
||||
|
||||
const editorPlaceholder = computed(() => {
|
||||
return "Shift + Enter to add a new line; Press '/' to select a Canned Response."
|
||||
return "Press enter to add a new line; Press '/' to select a Canned Response."
|
||||
})
|
||||
|
||||
const attachments = computed(() => {
|
||||
return uploadedFiles.value.filter(upload => upload.disposition === 'attachment')
|
||||
})
|
||||
|
||||
const filterCannedResponses = (input) => {
|
||||
@@ -187,8 +191,23 @@ const handleContentCleared = () => {
|
||||
|
||||
const handleSend = async () => {
|
||||
try {
|
||||
// Replace image source url with cid.
|
||||
// Replace image url with cid.
|
||||
const message = transformImageSrcToCID(editorHTML.value)
|
||||
|
||||
// Check which images are still in editor before sending.
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(editorHTML.value, 'text/html')
|
||||
const inlineImageUUIDs = Array.from(doc.querySelectorAll('img.inline-image'))
|
||||
.map(img => img.getAttribute('title'))
|
||||
.filter(Boolean)
|
||||
|
||||
uploadedFiles.value = uploadedFiles.value.filter(file =>
|
||||
// Keep if:
|
||||
// 1. Not an inline image OR
|
||||
// 2. Is an inline image that exists in editor
|
||||
file.disposition !== 'inline' || inlineImageUUIDs.includes(file.uuid)
|
||||
)
|
||||
|
||||
await api.sendMessage(conversationStore.current.uuid, {
|
||||
private: messageType.value === 'private_note',
|
||||
message: message,
|
||||
|
||||
@@ -61,12 +61,6 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user has a specific permission
|
||||
const hasPermission = (permission) => {
|
||||
if (!permission) return true
|
||||
return userPermissions.value.includes(permission)
|
||||
}
|
||||
|
||||
const clearAvatar = () => {
|
||||
userAvatar.value = ''
|
||||
}
|
||||
@@ -87,7 +81,6 @@ export const useUserStore = defineStore('user', () => {
|
||||
setFirstName,
|
||||
setLastName,
|
||||
getCurrentUser,
|
||||
hasPermission,
|
||||
clearAvatar
|
||||
}
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ module.exports = {
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
poppins: ['Inter', 'sans-serif'],
|
||||
inter: ['Inter', 'Helvetica Neue', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
|
||||
@@ -12,6 +12,10 @@ export default defineConfig({
|
||||
target: 'http://127.0.0.1:9000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/uploads': {
|
||||
target: 'http://127.0.0.1:9000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/ws': {
|
||||
target: 'ws://127.0.0.1:9000',
|
||||
ws: true,
|
||||
|
||||
+175
-5
@@ -1782,6 +1782,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404"
|
||||
integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==
|
||||
|
||||
"@types/prismjs@^1.9.1":
|
||||
version "1.26.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6"
|
||||
integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==
|
||||
|
||||
"@types/sinonjs__fake-timers@8.1.1":
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3"
|
||||
@@ -2112,6 +2117,14 @@
|
||||
dependencies:
|
||||
"@vue/compiler-core" "^3.0.0"
|
||||
|
||||
"@vueup/vue-quill@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueup/vue-quill/-/vue-quill-1.2.0.tgz#cd0d93559256d069f639723dd91c044e8162c72a"
|
||||
integrity sha512-kd5QPSHMDpycklojPXno2Kw2JSiKMYduKYQckTm1RJoVDA557MnyUXgcuuDpry4HY/Rny9nGNcK+m3AHk94wag==
|
||||
dependencies:
|
||||
quill "^1.3.7"
|
||||
quill-delta "^4.2.2"
|
||||
|
||||
"@vueuse/core@^10.11.0", "@vueuse/core@^10.11.1":
|
||||
version "10.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6"
|
||||
@@ -2749,7 +2762,7 @@ cachedir@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d"
|
||||
integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==
|
||||
|
||||
call-bind@^1.0.5, call-bind@^1.0.7:
|
||||
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
|
||||
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
|
||||
@@ -2972,6 +2985,11 @@ clone@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
||||
|
||||
clone@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
|
||||
|
||||
clsx@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
|
||||
@@ -3010,6 +3028,14 @@ code-point-at@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==
|
||||
|
||||
codeflask@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/codeflask/-/codeflask-1.4.1.tgz#c5229854e3f648377922a75f1145f7316030d3db"
|
||||
integrity sha512-4vb2IbE/iwvP0Uubhd2ixVeysm3KNC2pl7SoDaisxq1juhZzvap3qbaX7B2CtpQVvv5V9sjcQK8hO0eTcY0V9Q==
|
||||
dependencies:
|
||||
"@types/prismjs" "^1.9.1"
|
||||
prismjs "^1.14.0"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
@@ -3653,6 +3679,18 @@ decamelize@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
|
||||
|
||||
deep-equal@^1.0.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.2.tgz#78a561b7830eef3134c7f6f3a3d6af272a678761"
|
||||
integrity sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==
|
||||
dependencies:
|
||||
is-arguments "^1.1.1"
|
||||
is-date-object "^1.0.5"
|
||||
is-regex "^1.1.4"
|
||||
object-is "^1.1.5"
|
||||
object-keys "^1.1.1"
|
||||
regexp.prototype.flags "^1.5.1"
|
||||
|
||||
deep-extend@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
@@ -3670,7 +3708,7 @@ defaults@^1.0.3:
|
||||
dependencies:
|
||||
clone "^1.0.2"
|
||||
|
||||
define-data-property@^1.1.4:
|
||||
define-data-property@^1.0.1, define-data-property@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
|
||||
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
|
||||
@@ -3679,6 +3717,15 @@ define-data-property@^1.1.4:
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.0.1"
|
||||
|
||||
define-properties@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
|
||||
integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
|
||||
dependencies:
|
||||
define-data-property "^1.0.1"
|
||||
has-property-descriptors "^1.0.0"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
defu@^6.1.4:
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479"
|
||||
@@ -4121,6 +4168,11 @@ eventemitter2@6.4.7:
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d"
|
||||
integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==
|
||||
|
||||
eventemitter3@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
|
||||
integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==
|
||||
|
||||
execa@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
||||
@@ -4204,7 +4256,7 @@ exponential-backoff@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
|
||||
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
|
||||
|
||||
extend@~3.0.0, extend@~3.0.2:
|
||||
extend@^3.0.2, extend@~3.0.0, extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
@@ -4235,6 +4287,16 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-diff@1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
|
||||
integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
|
||||
|
||||
fast-diff@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||
|
||||
fast-diff@^1.1.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
|
||||
@@ -4513,6 +4575,11 @@ function-bind@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||
|
||||
functions-have-names@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
|
||||
gauge@~2.7.3:
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
|
||||
@@ -4796,7 +4863,7 @@ has-flag@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
has-property-descriptors@^1.0.2:
|
||||
has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
|
||||
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
|
||||
@@ -4813,6 +4880,13 @@ has-symbols@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||
|
||||
has-tostringtag@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
|
||||
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
|
||||
dependencies:
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
has-unicode@^2.0.0, has-unicode@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
@@ -5106,6 +5180,14 @@ ip@^1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396"
|
||||
integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==
|
||||
|
||||
is-arguments@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
|
||||
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-arrayish@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||
@@ -5153,6 +5235,13 @@ is-core-module@^2.13.0, is-core-module@^2.8.1:
|
||||
dependencies:
|
||||
hasown "^2.0.0"
|
||||
|
||||
is-date-object@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
|
||||
integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
@@ -5240,6 +5329,14 @@ is-redirect@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
|
||||
integrity sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==
|
||||
|
||||
is-regex@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
||||
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-retry-allowed@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
|
||||
@@ -5751,11 +5848,16 @@ lodash.castarray@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
|
||||
integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
|
||||
|
||||
lodash.clonedeep@~4.5.0:
|
||||
lodash.clonedeep@^4.5.0, lodash.clonedeep@~4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
@@ -6763,6 +6865,14 @@ object-inspect@^1.13.1:
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
|
||||
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
|
||||
|
||||
object-is@^1.1.5:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07"
|
||||
integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==
|
||||
dependencies:
|
||||
call-bind "^1.0.7"
|
||||
define-properties "^1.2.1"
|
||||
|
||||
object-keys@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
@@ -6996,6 +7106,11 @@ parallel-transform@^1.1.0:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.1.5"
|
||||
|
||||
parchment@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5"
|
||||
integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
@@ -7293,6 +7408,11 @@ pretty-bytes@^5.6.0:
|
||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
|
||||
|
||||
prismjs@^1.14.0:
|
||||
version "1.29.0"
|
||||
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
|
||||
integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
|
||||
|
||||
proc-log@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8"
|
||||
@@ -7665,6 +7785,36 @@ quickselect@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
|
||||
integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
|
||||
|
||||
quill-delta@^3.6.2:
|
||||
version "3.6.3"
|
||||
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
|
||||
integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==
|
||||
dependencies:
|
||||
deep-equal "^1.0.1"
|
||||
extend "^3.0.2"
|
||||
fast-diff "1.1.2"
|
||||
|
||||
quill-delta@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-4.2.2.tgz#015397d046e0a3bed087cd8a51f98c11a1b8f351"
|
||||
integrity sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==
|
||||
dependencies:
|
||||
fast-diff "1.2.0"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lodash.isequal "^4.5.0"
|
||||
|
||||
quill@^1.3.7:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
|
||||
integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==
|
||||
dependencies:
|
||||
clone "^2.1.1"
|
||||
deep-equal "^1.0.1"
|
||||
eventemitter3 "^2.0.3"
|
||||
extend "^3.0.2"
|
||||
parchment "^1.1.4"
|
||||
quill-delta "^3.6.2"
|
||||
|
||||
radix-vue@^1.7.3:
|
||||
version "1.8.5"
|
||||
resolved "https://registry.yarnpkg.com/radix-vue/-/radix-vue-1.8.5.tgz#d16118470f318706a3b18726dbebcf9a67d111e8"
|
||||
@@ -7850,6 +8000,16 @@ regenerator-runtime@^0.14.0:
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
|
||||
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
|
||||
|
||||
regexp.prototype.flags@^1.5.1:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42"
|
||||
integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.7"
|
||||
define-properties "^1.2.1"
|
||||
es-errors "^1.3.0"
|
||||
set-function-name "^2.0.2"
|
||||
|
||||
registry-auth-token@^3.0.1:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
|
||||
@@ -8148,6 +8308,16 @@ set-function-length@^1.2.1:
|
||||
gopd "^1.0.1"
|
||||
has-property-descriptors "^1.0.2"
|
||||
|
||||
set-function-name@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985"
|
||||
integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
|
||||
dependencies:
|
||||
define-data-property "^1.1.4"
|
||||
es-errors "^1.3.0"
|
||||
functions-have-names "^1.2.3"
|
||||
has-property-descriptors "^1.0.2"
|
||||
|
||||
sha@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/sha/-/sha-2.0.1.tgz#6030822fbd2c9823949f8f72ed6411ee5cf25aae"
|
||||
|
||||
@@ -48,7 +48,7 @@ func NewEnforcer(lo *logf.Logger) (*Enforcer, error) {
|
||||
return &Enforcer{enforcer: e, lo: lo}, nil
|
||||
}
|
||||
|
||||
// LoadPermissions adds the user's permissions to the Casbin enforcer if not already present
|
||||
// LoadPermissions adds the user's permissions to the Casbin enforcer if not already present.
|
||||
func (e *Enforcer) LoadPermissions(user umodels.User) error {
|
||||
for _, perm := range user.Permissions {
|
||||
parts := strings.Split(perm, ":")
|
||||
@@ -57,11 +57,8 @@ func (e *Enforcer) LoadPermissions(user umodels.User) error {
|
||||
}
|
||||
|
||||
userID, permObj, permAct := strconv.Itoa(user.ID), parts[0], parts[1]
|
||||
ok, err := e.enforcer.HasPolicy(userID, permObj, permAct)
|
||||
if err != nil || !ok {
|
||||
if _, err := e.enforcer.AddPolicy(userID, permObj, permAct); err != nil {
|
||||
return fmt.Errorf("failed to add policy: %v", err)
|
||||
}
|
||||
if _, err := e.enforcer.AddPolicy(userID, permObj, permAct); err != nil {
|
||||
return fmt.Errorf("failed to add policy: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -105,8 +102,8 @@ func (e *Enforcer) EnforceConversationAccess(user umodels.User, conversation cmo
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check for `read_assigned` permission
|
||||
allowed, err = e.enforcer.Enforce(strconv.Itoa(user.ID), "conversations", "read_assigned")
|
||||
// Check for `read_unassigned` permission
|
||||
allowed, err = e.enforcer.Enforce(strconv.Itoa(user.ID), "conversations", "read_unassigned")
|
||||
if err != nil {
|
||||
return false, envelope.NewError(envelope.GeneralError, "Error checking permissions", nil)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func (m *Manager) Run(ctx context.Context, dispatchConcurrency int, scanInterval
|
||||
}
|
||||
|
||||
// Render content in template.
|
||||
if err := m.RenderContentInTemplate(inb, message); err != nil {
|
||||
if err := m.RenderContentInTemplate(inb, &message); err != nil {
|
||||
m.lo.Error("error rendering content", "message_id", message.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func (m *Manager) MessageDispatchWorker(ctx context.Context) {
|
||||
}
|
||||
|
||||
// RenderContentInTemplate renders message content in the default template
|
||||
func (m *Manager) RenderContentInTemplate(inb inbox.Inbox, message models.Message) error {
|
||||
func (m *Manager) RenderContentInTemplate(inb inbox.Inbox, message *models.Message) error {
|
||||
var (
|
||||
channel = inb.Channel()
|
||||
err error
|
||||
|
||||
@@ -36,7 +36,6 @@ type Conversation struct {
|
||||
AssignedTeamID null.Int `db:"assigned_team_id" json:"assigned_team_id"`
|
||||
AssigneeLastSeenAt null.Time `db:"assignee_last_seen_at" json:"assignee_last_seen_at"`
|
||||
cmodels.Contact
|
||||
// Psuedo fields.
|
||||
Subject string `db:"subject" json:"subject"`
|
||||
UnreadMessageCount int `db:"unread_message_count" json:"unread_message_count"`
|
||||
InboxName string `db:"inbox_name" json:"inbox_name"`
|
||||
@@ -67,35 +66,33 @@ type NewConversationsStats struct {
|
||||
}
|
||||
|
||||
// Message represents a message in a conversation
|
||||
// TODO: Maybe diffentiate conversation message and a outgoing message.
|
||||
type Message struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
UUID string `db:"uuid" json:"uuid"`
|
||||
Type string `db:"type" json:"type"`
|
||||
Status string `db:"status" json:"status"`
|
||||
ConversationID int `db:"conversation_id" json:"conversation_id"`
|
||||
Content string `db:"content" json:"content"`
|
||||
ContentType string `db:"content_type" json:"content_type"`
|
||||
Private bool `db:"private" json:"private"`
|
||||
SourceID null.String `db:"source_id" json:"-"`
|
||||
SenderID int `db:"sender_id" json:"sender_id"`
|
||||
SenderType string `db:"sender_type" json:"sender_type"`
|
||||
InboxID int `db:"inbox_id" json:"-"`
|
||||
Meta string `db:"meta" json:"meta"`
|
||||
Attachments attachment.Attachments `db:"attachments" json:"attachments"`
|
||||
// Psuedo fields.
|
||||
ConversationUUID string `db:"conversation_uuid" json:"-"`
|
||||
From string `db:"from" json:"-"`
|
||||
To []string `db:"from" json:"-"`
|
||||
AltContent string `db:"alt_content" json:"-"`
|
||||
Subject string `db:"subject" json:"-"`
|
||||
Channel string `db:"channel" json:"-"`
|
||||
References []string `json:"-"`
|
||||
InReplyTo string `json:"-"`
|
||||
Headers textproto.MIMEHeader `json:"-"`
|
||||
Media []mmodels.Media `db:"-" json:"-"`
|
||||
ID int `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
UUID string `db:"uuid" json:"uuid"`
|
||||
Type string `db:"type" json:"type"`
|
||||
Status string `db:"status" json:"status"`
|
||||
ConversationID int `db:"conversation_id" json:"conversation_id"`
|
||||
Content string `db:"content" json:"content"`
|
||||
ContentType string `db:"content_type" json:"content_type"`
|
||||
Private bool `db:"private" json:"private"`
|
||||
SourceID null.String `db:"source_id" json:"-"`
|
||||
SenderID int `db:"sender_id" json:"sender_id"`
|
||||
SenderType string `db:"sender_type" json:"sender_type"`
|
||||
InboxID int `db:"inbox_id" json:"-"`
|
||||
Meta string `db:"meta" json:"meta"`
|
||||
Attachments attachment.Attachments `db:"attachments" json:"attachments"`
|
||||
ConversationUUID string `db:"conversation_uuid" json:"-"`
|
||||
From string `db:"from" json:"-"`
|
||||
To []string `db:"from" json:"-"`
|
||||
AltContent string `db:"alt_content" json:"-"`
|
||||
Subject string `db:"subject" json:"-"`
|
||||
Channel string `db:"channel" json:"-"`
|
||||
References []string `json:"-"`
|
||||
InReplyTo string `json:"-"`
|
||||
Headers textproto.MIMEHeader `json:"-"`
|
||||
Media []mmodels.Media `db:"-" json:"-"`
|
||||
}
|
||||
|
||||
// IncomingMessage links a message with the contact information and inbox id.
|
||||
|
||||
@@ -129,8 +129,18 @@ WHERE uuid = $1;
|
||||
-- name: update-conversation-status
|
||||
UPDATE conversations
|
||||
SET status_id = (SELECT id FROM status WHERE name = $2),
|
||||
resolved_at = CASE WHEN $2 = 'Resolved' THEN CURRENT_TIMESTAMP ELSE NULL END,
|
||||
closed_at = CASE WHEN $2 = 'Closed' THEN CURRENT_TIMESTAMP ELSE NULL END,
|
||||
resolved_at = CASE
|
||||
WHEN $2 = 'Resolved' THEN
|
||||
COALESCE(resolved_at, CURRENT_TIMESTAMP)
|
||||
WHEN $2 != 'Resolved' THEN
|
||||
resolved_at
|
||||
END,
|
||||
closed_at = CASE
|
||||
WHEN $2 = 'Closed' THEN
|
||||
COALESCE(closed_at, CURRENT_TIMESTAMP)
|
||||
ELSE
|
||||
closed_at
|
||||
END,
|
||||
updated_at = now()
|
||||
WHERE uuid = $1;
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ func (u *Manager) Create(user *models.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves a user by ID or UUID.
|
||||
// Get retrieves a user by ID.
|
||||
func (u *Manager) Get(id int) (models.User, error) {
|
||||
var user models.User
|
||||
if err := u.q.GetUser.Get(&user, id); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user