mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-24 03:16:20 +00:00
feat: emoji picker
- fix: Fatal issued during inbox reload instead return errors. - rename functions in inbox pkg to reflect purpose. - rename page titles on router. - remove iconify.
This commit is contained in:
+1
-1
@@ -567,7 +567,7 @@ func calculateSLA(app *App, conversation *cmodels.Conversation) error {
|
||||
|
||||
// sendCSATSurvey sends a CSAT survey if enabled on the inbox.
|
||||
func sendCSATSurvey(app *App, conversation cmodels.Conversation, user umodels.User) error {
|
||||
inbox, err := app.inbox.GetByID(conversation.InboxID)
|
||||
inbox, err := app.inbox.GetDBRecord(conversation.InboxID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ func handleGetInbox(r *fastglue.Request) error {
|
||||
app = r.Context.(*App)
|
||||
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
||||
)
|
||||
inbox, err := app.inbox.GetByID(id)
|
||||
inbox, err := app.inbox.GetDBRecord(id)
|
||||
if err != nil {
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error fetching inbox", nil, envelope.GeneralError)
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func handleUpdateInbox(r *fastglue.Request) error {
|
||||
}
|
||||
|
||||
if err := reloadInboxes(app); err != nil {
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error reloading inboxes", nil, envelope.GeneralError)
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error reloading inboxes, Please restart the app if the issue persists", nil, envelope.GeneralError)
|
||||
}
|
||||
|
||||
return r.SendEnvelope(inbox)
|
||||
|
||||
+4
-5
@@ -471,11 +471,11 @@ func initEmailInbox(inboxRecord imodels.Inbox, store inbox.MessageStore) (inbox.
|
||||
|
||||
// Load JSON data into Koanf.
|
||||
if err := ko.Load(rawbytes.Provider([]byte(inboxRecord.Config)), kjson.Parser()); err != nil {
|
||||
log.Fatalf("error loading config: %v", err)
|
||||
return nil, fmt.Errorf("loading config: %w", err)
|
||||
}
|
||||
|
||||
if err := ko.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "json"}); err != nil {
|
||||
log.Fatalf("error unmarshalling `%s` %s config: %v", inboxRecord.Channel, inboxRecord.Name, err)
|
||||
return nil, fmt.Errorf("unmarshalling `%s` %s config: %w", inboxRecord.Channel, inboxRecord.Name, err)
|
||||
}
|
||||
|
||||
if len(config.SMTP) == 0 {
|
||||
@@ -499,11 +499,10 @@ func initEmailInbox(inboxRecord imodels.Inbox, store inbox.MessageStore) (inbox.
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: initalizing `%s` inbox: `%s` error : %v", inboxRecord.Channel, inboxRecord.Name, err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("initializing `%s` inbox: `%s` error : %w", inboxRecord.Channel, inboxRecord.Name, err)
|
||||
}
|
||||
|
||||
log.Printf("`%s` inbox successfully initalized. %d smtp servers. %d imap clients.", inboxRecord.Name, len(config.SMTP), len(config.IMAP))
|
||||
log.Printf("`%s` inbox successfully initialized. %d SMTP servers. %d IMAP clients.", inboxRecord.Name, len(config.SMTP), len(config.IMAP))
|
||||
|
||||
return inbox, nil
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@
|
||||
"vue-picture-cropper": "^0.7.0",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-sonner": "^1.3.0",
|
||||
"vue3-emoji-picker": "^1.1.8",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
|
||||
@@ -2,9 +2,9 @@ import * as z from 'zod'
|
||||
import { isGoDuration } from '@/utils/strings'
|
||||
|
||||
export const formSchema = z.object({
|
||||
name2: z.string().describe('Name').default(''),
|
||||
name: z.string().describe('Name').default(''),
|
||||
from: z.string().describe('From address').default(''),
|
||||
csat_enabled: z.boolean().describe('Enable CSAT').default(false),
|
||||
csat_enabled: z.boolean().describe('Enable CSAT'),
|
||||
imap: z
|
||||
.object({
|
||||
host: z.string().describe('Host').default('imap.gmail.com'),
|
||||
|
||||
@@ -64,10 +64,11 @@
|
||||
<!-- Attachments preview -->
|
||||
<AttachmentsPreview :attachments="attachments" :onDelete="handleOnFileDelete"></AttachmentsPreview>
|
||||
|
||||
|
||||
<!-- Bottom menu bar -->
|
||||
<ReplyBoxBottomMenuBar :handleFileUpload="handleFileUpload" :handleInlineImageUpload="handleInlineImageUpload"
|
||||
:isBold="isBold" :isItalic="isItalic" @toggleBold="toggleBold" @toggleItalic="toggleItalic" :hasText="hasText"
|
||||
:handleSend="handleSend">
|
||||
:handleSend="handleSend" @emojiSelect="handleEmojiSelect">
|
||||
</ReplyBoxBottomMenuBar>
|
||||
</div>
|
||||
|
||||
@@ -92,9 +93,7 @@ import AttachmentsPreview from '@/components/attachment/AttachmentsPreview.vue'
|
||||
import ReplyBoxBottomMenuBar from '@/components/conversation/ReplyBoxMenuBar.vue'
|
||||
|
||||
const conversationStore = useConversationStore()
|
||||
|
||||
const emitter = useEmitter()
|
||||
|
||||
let editorInstance = ref(null)
|
||||
const isEditorFullscreen = ref(false)
|
||||
const cursorPosition = ref(0)
|
||||
@@ -330,4 +329,8 @@ const selectCannedResponse = (content) => {
|
||||
filteredCannedResponses.value = []
|
||||
selectedResponseIndex.value = -1
|
||||
}
|
||||
|
||||
const handleEmojiSelect = (emoji) => {
|
||||
editorInstance.value.chain().focus().insertContent(emoji).run()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
<template>
|
||||
<div class="flex justify-between items-center border-t h-14 px-2">
|
||||
<div class="flex justify-between items-center border-t h-14 px-2 relative">
|
||||
<EmojiPicker v-if="isEmojiPickerVisible" ref="emojiPickerRef" :native="true" @select="onSelectEmoji"
|
||||
class="absolute bottom-14 left-14" />
|
||||
<div class="flex justify-items-start gap-2">
|
||||
<!-- File inputs -->
|
||||
<input type="file" class="hidden" ref="attachmentInput" multiple @change="handleFileUpload" />
|
||||
<input type="file" class="hidden" ref="inlineImageInput" accept="image/*" @change="handleInlineImageUpload" />
|
||||
<!-- Editor buttons -->
|
||||
<!-- <Toggle class="px-2 py-2 border-0" variant="outline" @click="toggleBold" :pressed="isBold">
|
||||
<Bold class="h-4 w-4" />
|
||||
</Toggle>
|
||||
<Toggle class="px-2 py-2 border-0" variant="outline" @click="toggleItalic" :pressed="isItalic">
|
||||
<Italic class="h-4 w-4" />
|
||||
</Toggle> -->
|
||||
<Toggle class="px-2 py-2 border-0" variant="outline" @click="triggerFileUpload" :pressed="false">
|
||||
<Paperclip class="h-4 w-4" />
|
||||
</Toggle>
|
||||
<!-- <Toggle class="px-2 py-2 border-0" variant="outline" @click="triggerInlineImage" :pressed="false">
|
||||
<Image class="h-4 w-4" />
|
||||
</Toggle> -->
|
||||
<Toggle class="px-2 py-2 border-0" variant="outline" @click="toggleEmojiPicker" :pressed="isEmojiPickerVisible">
|
||||
<Smile class="h-4 w-4" />
|
||||
</Toggle>
|
||||
</div>
|
||||
<Button class="h-8 w-6 px-8" @click="handleSend" :disabled="!hasText">Send</Button>
|
||||
</div>
|
||||
@@ -24,13 +20,19 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Toggle } from '@/components/ui/toggle'
|
||||
import { Paperclip } from 'lucide-vue-next'
|
||||
import { Paperclip, Smile } from 'lucide-vue-next'
|
||||
import EmojiPicker from 'vue3-emoji-picker'
|
||||
import 'vue3-emoji-picker/css'
|
||||
|
||||
const attachmentInput = ref(null)
|
||||
const inlineImageInput = ref(null)
|
||||
// const emit = defineEmits(['toggleBold', 'toggleItalic'])
|
||||
const isEmojiPickerVisible = ref(false)
|
||||
const emojiPickerRef = ref(null)
|
||||
const emit = defineEmits(['toggleBold', 'toggleItalic', 'emojiSelect'])
|
||||
|
||||
defineProps({
|
||||
isBold: Boolean,
|
||||
isItalic: Boolean,
|
||||
@@ -40,19 +42,19 @@ defineProps({
|
||||
handleInlineImageUpload: Function
|
||||
})
|
||||
|
||||
// const toggleBold = () => {
|
||||
// emit('toggleBold')
|
||||
// }
|
||||
|
||||
// const toggleItalic = () => {
|
||||
// emit('toggleItalic')
|
||||
// }
|
||||
onClickOutside(emojiPickerRef, () => {
|
||||
isEmojiPickerVisible.value = false
|
||||
})
|
||||
|
||||
const triggerFileUpload = () => {
|
||||
attachmentInput.value.click()
|
||||
}
|
||||
|
||||
// const triggerInlineImage = () => {
|
||||
// inlineImageInput.value.click()
|
||||
// }
|
||||
const toggleEmojiPicker = () => {
|
||||
isEmojiPickerVisible.value = !isEmojiPickerVisible.value
|
||||
}
|
||||
|
||||
function onSelectEmoji (emoji) {
|
||||
emit('emojiSelect', emoji.i)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -338,7 +338,7 @@ const hasConversationOpen = computed(() => {
|
||||
<template>
|
||||
<div class="flex flex-row justify-between h-full">
|
||||
<div class="flex-1">
|
||||
<SidebarProvider :open="open" @update:open="($event) => emit('update:open', $event)">
|
||||
<SidebarProvider :open="open" @update:open="($event) => emit('update:open', $event)" style="--sidebar-width: 17rem;">
|
||||
<!-- Flex Container that holds all the sidebar components -->
|
||||
<Sidebar collapsible="icon" class="overflow-hidden [&>[data-sidebar=sidebar]]:flex-row !border-r-0">
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ const routes = [
|
||||
{
|
||||
path: 'teams',
|
||||
component: () => import('@/components/admin/team/teams/Teams.vue'),
|
||||
meta: { title: 'Teams Management' },
|
||||
meta: { title: 'Teams' },
|
||||
children: [
|
||||
|
||||
{
|
||||
@@ -288,7 +288,7 @@ const routes = [
|
||||
{
|
||||
path: 'general',
|
||||
component: () => import('@/components/admin/general/General.vue'),
|
||||
meta: { title: 'General Settings' }
|
||||
meta: { title: 'General' }
|
||||
},
|
||||
{
|
||||
path: 'templates',
|
||||
|
||||
+26
-24
@@ -747,18 +747,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
|
||||
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
||||
|
||||
"@iconify/types@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57"
|
||||
integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
|
||||
|
||||
"@iconify/vue@^4.1.2":
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@iconify/vue/-/vue-4.1.2.tgz#b76135785ca366b29bf0736eee9cfefc1c2ef79e"
|
||||
integrity sha512-CQnYqLiQD5LOAaXhBrmj1mdL2/NCJvwcC4jtW2Z8ukhThiFkLDkutarTOV2trfc9EXqUqRs0KqXOL9pZ/IyysA==
|
||||
dependencies:
|
||||
"@iconify/types" "^2.0.0"
|
||||
|
||||
"@internationalized/date@^3.5.4":
|
||||
version "3.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.4.tgz#49ba11634fd4350b7a9308e297032267b4063c44"
|
||||
@@ -1105,7 +1093,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
|
||||
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
|
||||
|
||||
"@popperjs/core@^2.9.0":
|
||||
"@popperjs/core@^2.11.0", "@popperjs/core@^2.9.0":
|
||||
version "2.11.8"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
@@ -5181,6 +5169,11 @@ iconv-lite@0.6, iconv-lite@^0.6.2:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
idb@^7.1.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b"
|
||||
integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
|
||||
|
||||
ieee754@^1.1.12, ieee754@^1.1.13:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
@@ -9628,6 +9621,26 @@ vue-sonner@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-sonner/-/vue-sonner-1.3.0.tgz#da8ab9be995dfea781d57a6ac52d170d02473d86"
|
||||
integrity sha512-jAodBy4Mri8rQjVZGQAPs4ZYymc1ywPiwfa81qU0fFl+Suk7U8NaOxIDdI1oBGLeQJqRZi/oxNIuhCLqsBmOwg==
|
||||
|
||||
vue3-emoji-picker@^1.1.8:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/vue3-emoji-picker/-/vue3-emoji-picker-1.1.8.tgz#70f1b56e9adf4b282d122b7c3a6ce8dc0ca3f72d"
|
||||
integrity sha512-k9tVHeQEBVLzVCLYAkFaI1nib3FJFQwdPhWD5khJkhks3ktg3g12z5wPGOSDpIuSLNtelRGvq1qdmZuJu5khfA==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.11.0"
|
||||
idb "^7.1.0"
|
||||
vue "^3.2.23"
|
||||
|
||||
vue@^3.2.23, vue@^3.5.13:
|
||||
version "3.5.13"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
|
||||
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.5.13"
|
||||
"@vue/compiler-sfc" "3.5.13"
|
||||
"@vue/runtime-dom" "3.5.13"
|
||||
"@vue/server-renderer" "3.5.13"
|
||||
"@vue/shared" "3.5.13"
|
||||
|
||||
vue@^3.4.37:
|
||||
version "3.4.37"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.37.tgz#64ce0eeb8de16a29fb74e504777ee8c0c1cf229e"
|
||||
@@ -9639,17 +9652,6 @@ vue@^3.4.37:
|
||||
"@vue/server-renderer" "3.4.37"
|
||||
"@vue/shared" "3.4.37"
|
||||
|
||||
vue@^3.5.13:
|
||||
version "3.5.13"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
|
||||
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.5.13"
|
||||
"@vue/compiler-sfc" "3.5.13"
|
||||
"@vue/runtime-dom" "3.5.13"
|
||||
"@vue/server-renderer" "3.5.13"
|
||||
"@vue/shared" "3.5.13"
|
||||
|
||||
w3c-keyname@^2.2.0:
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
||||
|
||||
@@ -82,7 +82,7 @@ type Manager struct {
|
||||
|
||||
// Prepared queries.
|
||||
type queries struct {
|
||||
GetByID *sqlx.Stmt `query:"get-by-id"`
|
||||
GetInbox *sqlx.Stmt `query:"get-inbox"`
|
||||
GetActive *sqlx.Stmt `query:"get-active-inboxes"`
|
||||
GetAll *sqlx.Stmt `query:"get-all-inboxes"`
|
||||
Update *sqlx.Stmt `query:"update"`
|
||||
@@ -119,11 +119,10 @@ func (m *Manager) Register(i Inbox) {
|
||||
m.inboxes[i.Identifier()] = i
|
||||
}
|
||||
|
||||
// Get returns the inbox with the given ID.
|
||||
// Get retrieves the initialized inbox instance with the specified ID from memory.
|
||||
func (m *Manager) Get(id int) (Inbox, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
i, ok := m.inboxes[id]
|
||||
if !ok {
|
||||
return nil, ErrInboxNotFound
|
||||
@@ -131,10 +130,10 @@ func (m *Manager) Get(id int) (Inbox, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// GetByID returns an inbox from the DB by the given ID.
|
||||
func (m *Manager) GetByID(id int) (imodels.Inbox, error) {
|
||||
// GetDBRecord returns the inbox record from the DB.
|
||||
func (m *Manager) GetDBRecord(id int) (imodels.Inbox, error) {
|
||||
var inbox imodels.Inbox
|
||||
if err := m.queries.GetByID.Get(&inbox, id); err != nil {
|
||||
if err := m.queries.GetInbox.Get(&inbox, id); err != nil {
|
||||
m.lo.Error("error fetching inbox", "error", err)
|
||||
return inbox, envelope.NewError(envelope.GeneralError, "Error fetching inbox", nil)
|
||||
}
|
||||
@@ -195,7 +194,7 @@ func (m *Manager) InitInboxes(initFn initFn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload reloads inboxes with the provided initialization function.
|
||||
// Reload hot reloads the inboxes with the given init function.
|
||||
func (m *Manager) Reload(ctx context.Context, initFn initFn) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@@ -248,7 +247,7 @@ func (m *Manager) Reload(ctx context.Context, initFn initFn) error {
|
||||
|
||||
// Update updates an inbox in the DB.
|
||||
func (m *Manager) Update(id int, inbox imodels.Inbox) error {
|
||||
current, err := m.GetByID(id)
|
||||
current, err := m.GetDBRecord(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ INSERT INTO inboxes
|
||||
(channel, config, "name", "from")
|
||||
VALUES($1, $2, $3, $4);
|
||||
|
||||
-- name: get-by-id
|
||||
-- name: get-inbox
|
||||
SELECT * from inboxes where id = $1 and deleted_at is NULL;
|
||||
|
||||
-- name: update
|
||||
|
||||
Reference in New Issue
Block a user