mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-11-02 21:13:47 +00:00
Compare commits
18 Commits
fix/post-p
...
v0.7.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
389c4e3dd3 | ||
|
|
9a119e6dc3 | ||
|
|
ee178d383d | ||
|
|
fc4db676d9 | ||
|
|
70cb3d0f80 | ||
|
|
c9920c3377 | ||
|
|
6d62c3a4ba | ||
|
|
d9b5fb8f0f | ||
|
|
3de320f1fb | ||
|
|
be977dcff2 | ||
|
|
5e19f13e18 | ||
|
|
ccc5940dd9 | ||
|
|
4203b82e90 | ||
|
|
ba07e224c2 | ||
|
|
3fff65150f | ||
|
|
c4fcf6bd91 | ||
|
|
5ea1b9e84c | ||
|
|
5b522888bc |
@@ -15,7 +15,7 @@ Visit [libredesk.io](https://libredesk.io) for more info. Check out the [**Live
|
||||
## Features
|
||||
|
||||
- **Multi Shared Inbox**
|
||||
Libredesk supports multiple shares inboxes, letting you manage conversations across teams effortlessly.
|
||||
Libredesk supports multiple shared inboxes, letting you manage conversations across teams effortlessly.
|
||||
- **Granular Permissions**
|
||||
Create custom roles with granular permissions for teams and individual agents.
|
||||
- **Smart Automation**
|
||||
@@ -85,6 +85,11 @@ __________________
|
||||
## Developers
|
||||
If you are interested in contributing, refer to the [developer setup](https://libredesk.io/docs/developer-setup/). The backend is written in Go and the frontend is Vue js 3 with Shadcn for UI components.
|
||||
|
||||
## Development Status
|
||||
|
||||
Libredesk is under active development.
|
||||
Track roadmap and progress on the GitHub Project Board: [https://github.com/users/abhinavxd/projects/1](https://github.com/users/abhinavxd/projects/1)
|
||||
|
||||
|
||||
## Translators
|
||||
You can help translate Libredesk into your language on [Crowdin](https://crowdin.com/project/libredesk).
|
||||
|
||||
@@ -744,7 +744,7 @@ func handleCreateConversation(r *fastglue.Request) error {
|
||||
}
|
||||
|
||||
// Send reply to the created conversation.
|
||||
if err := app.conversation.SendReply(media, req.InboxID, auser.ID /**sender_id**/, conversationUUID, req.Content, to, nil /**cc**/, nil /**bcc**/, map[string]any{} /**meta**/); err != nil {
|
||||
if _, err := app.conversation.SendReply(media, req.InboxID, auser.ID /**sender_id**/, conversationUUID, req.Content, to, nil /**cc**/, nil /**bcc**/, map[string]any{} /**meta**/); err != nil {
|
||||
// Delete the conversation if reply fails.
|
||||
if err := app.conversation.DeleteConversation(conversationUUID); err != nil {
|
||||
app.lo.Error("error deleting conversation", "error", err)
|
||||
|
||||
@@ -162,13 +162,15 @@ func handleSendMessage(r *fastglue.Request) error {
|
||||
}
|
||||
|
||||
if req.Private {
|
||||
if err := app.conversation.SendPrivateNote(media, user.ID, cuuid, req.Message); err != nil {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
} else {
|
||||
if err := app.conversation.SendReply(media, conv.InboxID, user.ID, cuuid, req.Message, req.To, req.CC, req.BCC, map[string]any{} /**meta**/); err != nil {
|
||||
message, err := app.conversation.SendPrivateNote(media, user.ID, cuuid, req.Message)
|
||||
if err != nil {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
return r.SendEnvelope(message)
|
||||
}
|
||||
return r.SendEnvelope(true)
|
||||
message, err := app.conversation.SendReply(media, conv.InboxID, user.ID, cuuid, req.Message, req.To, req.CC, req.BCC, map[string]any{} /**meta**/)
|
||||
if err != nil {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
return r.SendEnvelope(message)
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ Triggered when an existing message is updated.
|
||||
|
||||
## Delivery and Retries
|
||||
|
||||
- Webhooks are delivered with a 10-second timeout
|
||||
- Webhooks requests timeout can be configured in the `config.toml` file
|
||||
- Failed deliveries are not automatically retried
|
||||
- Webhook delivery runs in a background worker pool for better performance
|
||||
- If the webhook queue is full (configurable in config.toml file), new events may be dropped
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@internationalized/date": "^3.5.5",
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
@@ -40,7 +42,7 @@
|
||||
"axios": "^1.8.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"codeflask": "^1.4.1",
|
||||
"codemirror": "^6.0.2",
|
||||
"date-fns": "^3.6.0",
|
||||
"lucide-vue-next": "^0.378.0",
|
||||
"mitt": "^3.0.1",
|
||||
|
||||
210
frontend/pnpm-lock.yaml
generated
210
frontend/pnpm-lock.yaml
generated
@@ -8,6 +8,12 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@codemirror/lang-html':
|
||||
specifier: ^6.4.9
|
||||
version: 6.4.9
|
||||
'@codemirror/theme-one-dark':
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3
|
||||
'@formkit/auto-animate':
|
||||
specifier: ^0.8.2
|
||||
version: 0.8.2
|
||||
@@ -74,9 +80,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
codeflask:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
codemirror:
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2
|
||||
date-fns:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
@@ -234,6 +240,39 @@ packages:
|
||||
'@bassist/utils@0.4.0':
|
||||
resolution: {integrity: sha512-aoFTl0jUjm8/tDZodP41wnEkvB+C5O9NFCuYN/ztL6jSUSsuBkXq90/1ifBm1XhV/zySHgLYlU1+tgo3XtQ+nA==}
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
|
||||
|
||||
'@codemirror/commands@6.8.1':
|
||||
resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==}
|
||||
|
||||
'@codemirror/lang-css@6.3.1':
|
||||
resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
|
||||
|
||||
'@codemirror/lang-html@6.4.9':
|
||||
resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==}
|
||||
|
||||
'@codemirror/lang-javascript@6.2.4':
|
||||
resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
|
||||
|
||||
'@codemirror/language@6.11.1':
|
||||
resolution: {integrity: sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==}
|
||||
|
||||
'@codemirror/lint@6.8.5':
|
||||
resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==}
|
||||
|
||||
'@codemirror/search@6.5.11':
|
||||
resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==}
|
||||
|
||||
'@codemirror/state@6.5.2':
|
||||
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
|
||||
|
||||
'@codemirror/theme-one-dark@6.1.3':
|
||||
resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
|
||||
|
||||
'@codemirror/view@6.37.2':
|
||||
resolution: {integrity: sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==}
|
||||
|
||||
'@colors/colors@1.5.0':
|
||||
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
@@ -508,6 +547,24 @@ packages:
|
||||
'@juggle/resize-observer@3.4.0':
|
||||
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
||||
|
||||
'@lezer/common@1.2.3':
|
||||
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
|
||||
|
||||
'@lezer/css@1.2.1':
|
||||
resolution: {integrity: sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==}
|
||||
|
||||
'@lezer/highlight@1.2.1':
|
||||
resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
|
||||
|
||||
'@lezer/html@1.3.10':
|
||||
resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==}
|
||||
|
||||
'@lezer/javascript@1.5.1':
|
||||
resolution: {integrity: sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==}
|
||||
|
||||
'@lezer/lr@1.4.2':
|
||||
resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
|
||||
|
||||
'@mapbox/geojson-rewind@0.5.2':
|
||||
resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==}
|
||||
hasBin: true
|
||||
@@ -535,6 +592,9 @@ packages:
|
||||
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1109,9 +1169,6 @@ packages:
|
||||
'@types/pbf@3.0.5':
|
||||
resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==}
|
||||
|
||||
'@types/prismjs@1.26.5':
|
||||
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
||||
|
||||
'@types/sinonjs__fake-timers@8.1.1':
|
||||
resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
|
||||
|
||||
@@ -1532,8 +1589,8 @@ packages:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
codeflask@1.4.1:
|
||||
resolution: {integrity: sha512-4vb2IbE/iwvP0Uubhd2ixVeysm3KNC2pl7SoDaisxq1juhZzvap3qbaX7B2CtpQVvv5V9sjcQK8hO0eTcY0V9Q==}
|
||||
codemirror@6.0.2:
|
||||
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
@@ -2780,10 +2837,6 @@ packages:
|
||||
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
prismjs@1.29.0:
|
||||
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
process@0.11.10:
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
@@ -3093,6 +3146,9 @@ packages:
|
||||
striptags@3.2.0:
|
||||
resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==}
|
||||
|
||||
style-mod@4.1.2:
|
||||
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
|
||||
|
||||
stylis@4.2.0:
|
||||
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
||||
|
||||
@@ -3550,6 +3606,89 @@ snapshots:
|
||||
dependencies:
|
||||
'@withtypes/mime': 0.1.2
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.11.1
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@codemirror/commands@6.8.1':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.11.1
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@codemirror/lang-css@6.3.1':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/language': 6.11.1
|
||||
'@codemirror/state': 6.5.2
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/css': 1.2.1
|
||||
|
||||
'@codemirror/lang-html@6.4.9':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/lang-css': 6.3.1
|
||||
'@codemirror/lang-javascript': 6.2.4
|
||||
'@codemirror/language': 6.11.1
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/css': 1.2.1
|
||||
'@lezer/html': 1.3.10
|
||||
|
||||
'@codemirror/lang-javascript@6.2.4':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/language': 6.11.1
|
||||
'@codemirror/lint': 6.8.5
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/javascript': 1.5.1
|
||||
|
||||
'@codemirror/language@6.11.1':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
style-mod: 4.1.2
|
||||
|
||||
'@codemirror/lint@6.8.5':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/search@6.5.11':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/state@6.5.2':
|
||||
dependencies:
|
||||
'@marijn/find-cluster-break': 1.0.2
|
||||
|
||||
'@codemirror/theme-one-dark@6.1.3':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.11.1
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
'@lezer/highlight': 1.2.1
|
||||
|
||||
'@codemirror/view@6.37.2':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
crelt: 1.0.6
|
||||
style-mod: 4.1.2
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@colors/colors@1.5.0':
|
||||
optional: true
|
||||
|
||||
@@ -3815,6 +3954,34 @@ snapshots:
|
||||
|
||||
'@juggle/resize-observer@3.4.0': {}
|
||||
|
||||
'@lezer/common@1.2.3': {}
|
||||
|
||||
'@lezer/css@1.2.1':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@lezer/highlight@1.2.1':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@lezer/html@1.3.10':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@lezer/javascript@1.5.1':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@lezer/lr@1.4.2':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@mapbox/geojson-rewind@0.5.2':
|
||||
dependencies:
|
||||
get-stream: 6.0.1
|
||||
@@ -3836,6 +4003,8 @@ snapshots:
|
||||
|
||||
'@mapbox/whoots-js@3.1.0': {}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -4378,8 +4547,6 @@ snapshots:
|
||||
|
||||
'@types/pbf@3.0.5': {}
|
||||
|
||||
'@types/prismjs@1.26.5': {}
|
||||
|
||||
'@types/sinonjs__fake-timers@8.1.1': {}
|
||||
|
||||
'@types/sizzle@2.3.9': {}
|
||||
@@ -4906,10 +5073,15 @@ snapshots:
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
codeflask@1.4.1:
|
||||
codemirror@6.0.2:
|
||||
dependencies:
|
||||
'@types/prismjs': 1.26.5
|
||||
prismjs: 1.29.0
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/commands': 6.8.1
|
||||
'@codemirror/language': 6.11.1
|
||||
'@codemirror/lint': 6.8.5
|
||||
'@codemirror/search': 6.5.11
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.2
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
@@ -6200,8 +6372,6 @@ snapshots:
|
||||
|
||||
pretty-bytes@5.6.0: {}
|
||||
|
||||
prismjs@1.29.0: {}
|
||||
|
||||
process@0.11.10: {}
|
||||
|
||||
prosemirror-changeset@2.2.1:
|
||||
@@ -6601,6 +6771,8 @@ snapshots:
|
||||
|
||||
striptags@3.2.0: {}
|
||||
|
||||
style-mod@4.1.2: {}
|
||||
|
||||
stylis@4.2.0: {}
|
||||
|
||||
stylus@0.57.0:
|
||||
|
||||
@@ -207,10 +207,6 @@
|
||||
}
|
||||
// End Scrollbar
|
||||
|
||||
.code-editor {
|
||||
@apply rounded border shadow h-[65vh] min-h-[250px] w-full relative;
|
||||
}
|
||||
|
||||
.show-quoted-text {
|
||||
blockquote {
|
||||
@apply block;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div ref="codeEditor" id="code-editor" class="code-editor" />
|
||||
<div ref="codeEditor" @click="editorView?.focus()" class="w-full h-[28rem] border rounded-md" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, nextTick } from 'vue'
|
||||
import CodeFlask from 'codeflask'
|
||||
import { ref, onMounted, watch, nextTick, useTemplateRef } from 'vue'
|
||||
import { EditorView, basicSetup } from 'codemirror'
|
||||
import { html } from '@codemirror/lang-html'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
@@ -13,45 +16,38 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const codeEditor = ref(null)
|
||||
const data = ref('')
|
||||
const flask = ref(null)
|
||||
let editorView = null
|
||||
const codeEditor = useTemplateRef('codeEditor')
|
||||
|
||||
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)
|
||||
const isDark = useColorMode().value === 'dark'
|
||||
|
||||
flask.value = new CodeFlask(el.shadowRoot.getElementById('area'), {
|
||||
language: props.language,
|
||||
lineNumbers: false,
|
||||
styleParent: el.shadowRoot,
|
||||
readonly: props.disabled
|
||||
editorView = new EditorView({
|
||||
doc: body,
|
||||
extensions: [
|
||||
basicSetup,
|
||||
html(),
|
||||
...(isDark ? [oneDark] : []),
|
||||
EditorView.editable.of(!props.disabled),
|
||||
EditorView.theme({
|
||||
'&': { height: '100%' },
|
||||
'.cm-editor': { height: '100%' },
|
||||
'.cm-scroller': { overflow: 'auto' }
|
||||
}),
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (!update.docChanged) return
|
||||
const v = update.state.doc.toString()
|
||||
emit('update:modelValue', v)
|
||||
data.value = v
|
||||
|
||||
})
|
||||
],
|
||||
parent: codeEditor.value
|
||||
})
|
||||
|
||||
flask.value.onUpdate((v) => {
|
||||
emit('update:modelValue', v)
|
||||
data.value = v
|
||||
})
|
||||
|
||||
flask.value.updateCode(body)
|
||||
|
||||
nextTick(() => {
|
||||
document.querySelector('code-flask').shadowRoot.querySelector('textarea').focus()
|
||||
editorView?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,7 +57,9 @@ onMounted(() => {
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal !== data.value) {
|
||||
flask.value.updateCode(newVal)
|
||||
editorView?.dispatch({
|
||||
changes: { from: 0, to: editorView.state.doc.length, insert: newVal }
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
accountNavItems,
|
||||
contactNavItems
|
||||
} from '@/constants/navigation'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -43,14 +43,17 @@ import { useStorage } from '@vueuse/core'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useConversationStore } from '@/stores/conversation'
|
||||
|
||||
defineProps({
|
||||
userTeams: { type: Array, default: () => [] },
|
||||
userViews: { type: Array, default: () => [] }
|
||||
})
|
||||
const userStore = useUserStore()
|
||||
const conversationStore = useConversationStore()
|
||||
const settingsStore = useAppSettingsStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits(['createView', 'editView', 'deleteView', 'createConversation'])
|
||||
|
||||
@@ -74,6 +77,58 @@ const deleteView = (view) => {
|
||||
emit('deleteView', view)
|
||||
}
|
||||
|
||||
// Navigation methods with conversation retention
|
||||
const navigateToInbox = (type) => {
|
||||
if (conversationStore.hasConversationOpen && conversationStore.conversation.data?.uuid) {
|
||||
router.push({
|
||||
name: 'inbox-conversation',
|
||||
params: {
|
||||
type,
|
||||
uuid: conversationStore.conversation.data.uuid
|
||||
}
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
name: 'inbox',
|
||||
params: { type }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToTeamInbox = (teamID) => {
|
||||
if (conversationStore.hasConversationOpen && conversationStore.conversation.data?.uuid) {
|
||||
router.push({
|
||||
name: 'team-inbox-conversation',
|
||||
params: {
|
||||
teamID,
|
||||
uuid: conversationStore.conversation.data.uuid
|
||||
}
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
name: 'team-inbox',
|
||||
params: { teamID }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToViewInbox = (viewID) => {
|
||||
if (conversationStore.hasConversationOpen && conversationStore.conversation.data?.uuid) {
|
||||
router.push({
|
||||
name: 'view-inbox-conversation',
|
||||
params: {
|
||||
viewID,
|
||||
uuid: conversationStore.conversation.data.uuid
|
||||
}
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
name: 'view-inbox',
|
||||
params: { viewID }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const filteredAdminNavItems = computed(() => filterNavItems(adminNavItems, userStore.can))
|
||||
const filteredReportsNavItems = computed(() => filterNavItems(reportsNavItems, userStore.can))
|
||||
const filteredContactsNavItems = computed(() => filterNavItems(contactNavItems, userStore.can))
|
||||
@@ -322,32 +377,32 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/assigned')">
|
||||
<router-link :to="{ name: 'inbox', params: { type: 'assigned' } }">
|
||||
<a href="#" @click.prevent="navigateToInbox('assigned')">
|
||||
<User />
|
||||
<span>{{ t('globals.terms.myInbox') }}</span>
|
||||
</router-link>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/unassigned')">
|
||||
<router-link :to="{ name: 'inbox', params: { type: 'unassigned' } }">
|
||||
<a href="#" @click.prevent="navigateToInbox('unassigned')">
|
||||
<CircleDashed />
|
||||
<span>
|
||||
{{ t('globals.terms.unassigned') }}
|
||||
</span>
|
||||
</router-link>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild :isActive="isActiveParent('/inboxes/all')">
|
||||
<router-link :to="{ name: 'inbox', params: { type: 'all' } }">
|
||||
<a href="#" @click.prevent="navigateToInbox('all')">
|
||||
<List />
|
||||
<span>
|
||||
{{ t('globals.messages.all') }}
|
||||
</span>
|
||||
</router-link>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
@@ -380,9 +435,9 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
||||
:is-active="route.params.teamID == team.id"
|
||||
asChild
|
||||
>
|
||||
<router-link :to="{ name: 'team-inbox', params: { teamID: team.id } }">
|
||||
<a href="#" @click.prevent="navigateToTeamInbox(team.id)">
|
||||
{{ team.emoji }}<span>{{ team.name }}</span>
|
||||
</router-link>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
@@ -423,7 +478,7 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
||||
:isActive="route.params.viewID == view.id"
|
||||
asChild
|
||||
>
|
||||
<router-link :to="{ name: 'view-inbox', params: { viewID: view.id } }">
|
||||
<a href="#" @click.prevent="navigateToViewInbox(view.id)">
|
||||
<span class="break-words w-32 truncate">{{ view.name }}</span>
|
||||
<SidebarMenuAction :showOnHover="true" class="mr-3">
|
||||
<DropdownMenu>
|
||||
@@ -440,7 +495,7 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuAction>
|
||||
</router-link>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuSubItem>
|
||||
</SidebarMenuSub>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
:checked="!!selectedDays[day]"
|
||||
@update:checked="handleDayToggle(day, $event)"
|
||||
/>
|
||||
<Label :for="day" class="font-medium text-gray-800">{{ day }}</Label>
|
||||
<Label :for="day" class="font-medium">{{ day }}</Label>
|
||||
</div>
|
||||
<div class="flex space-x-2 items-center">
|
||||
<div class="flex flex-col items-start">
|
||||
@@ -156,7 +156,7 @@
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button :disabled="!holidayName || !holidayDate" @click="saveHoliday">
|
||||
{{ t('globals.messages.saveChanges') }}
|
||||
{{ t('globals.messages.add') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const createColumns = (t) => [
|
||||
},
|
||||
cell: function ({ row }) {
|
||||
const url = row.getValue('url')
|
||||
return h('div', { class: 'text-center font-mono text-sm max-w-sm truncate' }, url)
|
||||
return h('div', { class: 'text-center font-mono mt-1 max-w-sm truncate' }, url)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -66,11 +66,11 @@
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<span class="text-muted-foreground text-xs mt-1">
|
||||
{{ format(message.updated_at, 'h:mm a') }}
|
||||
{{ formatMessageTimestamp(message.created_at) }}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{{ format(message.updated_at, "MMMM dd, yyyy 'at' HH:mm") }}
|
||||
{{ formatFullTimestamp(message.created_at) }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -79,12 +79,12 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { format } from 'date-fns'
|
||||
import { useConversationStore } from '@/stores/conversation'
|
||||
import { Lock, RotateCcw, Check } from 'lucide-vue-next'
|
||||
import { revertCIDToImageSrc } from '@/utils/strings'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
import { formatMessageTimestamp, formatFullTimestamp } from '@/utils/datetime'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import MessageAttachmentPreview from '@/features/conversation/message/attachment/MessageAttachmentPreview.vue'
|
||||
import MessageEnvelope from './MessageEnvelope.vue'
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<Letter
|
||||
:html="sanitizedMessageContent"
|
||||
:allowedSchemas="['cid', 'https', 'http', 'mailto']"
|
||||
class="mb-1 native-html"
|
||||
class="mb-1 native-html break-all"
|
||||
:class="{ 'mb-3': message.attachments.length > 0 }"
|
||||
/>
|
||||
|
||||
@@ -60,12 +60,12 @@
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<span class="text-muted-foreground text-xs mt-1">
|
||||
{{ format(message.updated_at, 'h:mm a') }}
|
||||
{{ formatMessageTimestamp(message.created_at) }}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
{{ format(message.updated_at, "MMMM dd, yyyy 'at' HH:mm") }}
|
||||
{{ formatFullTimestamp(message.created_at) }}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -75,11 +75,11 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { format } from 'date-fns'
|
||||
import { useConversationStore } from '@/stores/conversation'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Letter } from 'vue-letter'
|
||||
import { formatMessageTimestamp, formatFullTimestamp } from '@/utils/datetime'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import MessageAttachmentPreview from '@/features/conversation/message/attachment/MessageAttachmentPreview.vue'
|
||||
|
||||
@@ -71,14 +71,16 @@ const routes = [
|
||||
path: '',
|
||||
name: 'team-inbox',
|
||||
component: () => import('@/views/inbox/InboxView.vue'),
|
||||
meta: { title: 'Team inbox' }
|
||||
},
|
||||
{
|
||||
path: 'conversation/:uuid',
|
||||
name: 'team-inbox-conversation',
|
||||
component: () => import('@/views/conversation/ConversationDetailView.vue'),
|
||||
props: true,
|
||||
meta: { title: 'Team inbox', hidePageHeader: true }
|
||||
meta: { title: 'Team inbox' },
|
||||
children: [
|
||||
{
|
||||
path: 'conversation/:uuid',
|
||||
name: 'team-inbox-conversation',
|
||||
component: () => import('@/views/conversation/ConversationDetailView.vue'),
|
||||
props: true,
|
||||
meta: { title: 'Team inbox', hidePageHeader: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -93,14 +95,16 @@ const routes = [
|
||||
path: '',
|
||||
name: 'view-inbox',
|
||||
component: () => import('@/views/inbox/InboxView.vue'),
|
||||
meta: { title: 'View inbox' }
|
||||
},
|
||||
{
|
||||
path: 'conversation/:uuid',
|
||||
name: 'view-inbox-conversation',
|
||||
component: () => import('@/views/conversation/ConversationDetailView.vue'),
|
||||
props: true,
|
||||
meta: { title: 'View inbox', hidePageHeader: true }
|
||||
meta: { title: 'View inbox' },
|
||||
children: [
|
||||
{
|
||||
path: 'conversation/:uuid',
|
||||
name: 'view-inbox-conversation',
|
||||
component: () => import('@/views/conversation/ConversationDetailView.vue'),
|
||||
props: true,
|
||||
meta: { title: 'View inbox', hidePageHeader: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -378,9 +378,6 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
if (conversations.listType !== listType || conversations.teamID !== teamID || conversations.viewID !== viewID) {
|
||||
resetConversations()
|
||||
}
|
||||
if (conversations.listType !== listType) {
|
||||
resetCurrentConversation()
|
||||
}
|
||||
if (listType) conversations.listType = listType
|
||||
if (teamID) conversations.teamID = teamID
|
||||
if (viewID) conversations.viewID = viewID
|
||||
|
||||
@@ -25,4 +25,12 @@ export const formatDuration = (seconds, showSeconds = true) => {
|
||||
const mins = Math.floor((totalSeconds % 3600) / 60)
|
||||
const secs = totalSeconds % 60
|
||||
return `${hours}h ${mins}m ${showSeconds ? `${secs}s` : ''}`
|
||||
}
|
||||
|
||||
export const formatMessageTimestamp = (time) => {
|
||||
return format(time, 'd MMM, hh:mm a')
|
||||
}
|
||||
|
||||
export const formatFullTimestamp = (time) => {
|
||||
return format(time, 'd MMM yyyy, hh:mm a')
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch, onMounted, onUnmounted } from 'vue'
|
||||
import { watch, onMounted } from 'vue'
|
||||
import { useConversationStore } from '@/stores/conversation'
|
||||
import Conversation from '@/features/conversation/Conversation.vue'
|
||||
import ConversationSideBarWrapper from '@/features/conversation/sidebar/ConversationSideBarWrapper.vue'
|
||||
@@ -37,10 +37,6 @@ onMounted(() => {
|
||||
if (props.uuid) fetchConversation(props.uuid)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
conversationStore.resetCurrentConversation()
|
||||
})
|
||||
|
||||
// Watcher for UUID changes
|
||||
watch(
|
||||
() => props.uuid,
|
||||
|
||||
@@ -25,7 +25,6 @@ onMounted(() => {
|
||||
if (!conversationStore.getListStatus) {
|
||||
conversationStore.setListStatus(CONVERSATION_DEFAULT_STATUSES.OPEN, false)
|
||||
}
|
||||
conversationStore.resetCurrentConversation()
|
||||
conversationStore.fetchConversationsList(true, type.value)
|
||||
}
|
||||
// Fetch team list.
|
||||
@@ -34,7 +33,6 @@ onMounted(() => {
|
||||
if (!conversationStore.getListStatus) {
|
||||
conversationStore.setListStatus(CONVERSATION_DEFAULT_STATUSES.OPEN, false)
|
||||
}
|
||||
conversationStore.resetCurrentConversation()
|
||||
conversationStore.fetchConversationsList(
|
||||
true,
|
||||
CONVERSATION_LIST_TYPE.TEAM_UNASSIGNED,
|
||||
@@ -45,7 +43,6 @@ onMounted(() => {
|
||||
if (viewID.value) {
|
||||
// Empty out list status as views are already filtered.
|
||||
conversationStore.setListStatus('', false)
|
||||
conversationStore.resetCurrentConversation()
|
||||
conversationStore.fetchConversationsList(true, CONVERSATION_LIST_TYPE.VIEW, 0, [], viewID.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -29,6 +29,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 600,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"globals.terms.setting": "Setting | Settings",
|
||||
"globals.terms.template": "Template | Templates",
|
||||
"globals.terms.rule": "Rule | Rules",
|
||||
"globals.terms.businessHour": "Business Hour | Business Hours",
|
||||
"globals.terms.businessHour": "Business hour | Business hours",
|
||||
"globals.terms.priority": "Priority | Priorities",
|
||||
"globals.terms.status": "Status | Statuses",
|
||||
"globals.terms.secret": "Secret | Secrets",
|
||||
@@ -533,7 +533,7 @@
|
||||
"admin.automation.event.message.incoming": "Incoming message",
|
||||
"admin.automation.invalid": "Make sure you have atleast one action and one rule and their values are not empty.",
|
||||
"admin.notification.restartApp": "Settings updated successfully, Please restart the app for changes to take effect.",
|
||||
"admin.template.outgoingEmailTemplates": "Outgoing Email Templates",
|
||||
"admin.template.outgoingEmailTemplates": "Outgoing email templates",
|
||||
"admin.template.emailNotificationTemplates": "Email notification templates",
|
||||
"admin.template.makeSureTemplateHasContent": "Make sure the template has {content} only once.",
|
||||
"admin.template.onlyOneDefaultOutgoingTemplate": "You can have only one default outgoing email template.",
|
||||
|
||||
@@ -331,7 +331,7 @@ func (e *Engine) handleNewConversation(conversation cmodels.Conversation) {
|
||||
e.lo.Debug("handling new conversation for automation rule evaluation", "uuid", conversation.UUID)
|
||||
rules := e.filterRulesByType(models.RuleTypeNewConversation, "")
|
||||
if len(rules) == 0 {
|
||||
e.lo.Warn("no rules to evaluate for new conversation rule evaluation", "uuid", conversation.UUID)
|
||||
e.lo.Info("no rules to evaluate for new conversation rule evaluation", "uuid", conversation.UUID)
|
||||
return
|
||||
}
|
||||
e.evalConversationRules(rules, conversation)
|
||||
@@ -342,7 +342,7 @@ func (e *Engine) handleUpdateConversation(conversation cmodels.Conversation, eve
|
||||
e.lo.Debug("handling update conversation for automation rule evaluation", "uuid", conversation.UUID, "event_type", eventType)
|
||||
rules := e.filterRulesByType(models.RuleTypeConversationUpdate, eventType)
|
||||
if len(rules) == 0 {
|
||||
e.lo.Warn("no rules to evaluate for conversation update", "uuid", conversation.UUID, "event_type", eventType)
|
||||
e.lo.Info("no rules to evaluate for conversation update", "uuid", conversation.UUID, "event_type", eventType)
|
||||
return
|
||||
}
|
||||
e.evalConversationRules(rules, conversation)
|
||||
@@ -359,7 +359,7 @@ func (e *Engine) handleTimeTrigger() {
|
||||
}
|
||||
rules := e.filterRulesByType(models.RuleTypeTimeTrigger, "")
|
||||
if len(rules) == 0 {
|
||||
e.lo.Warn("no rules to evaluate for time trigger")
|
||||
e.lo.Info("no rules to evaluate for time trigger")
|
||||
return
|
||||
}
|
||||
e.lo.Info("fetched conversations for evaluating time triggers", "conversations_count", len(conversations), "rules_count", len(rules))
|
||||
|
||||
@@ -920,14 +920,17 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
|
||||
}
|
||||
return m.UpdateConversationStatus(conv.UUID, statusID, "", "", user)
|
||||
case amodels.ActionSendPrivateNote:
|
||||
return m.SendPrivateNote([]mmodels.Media{}, user.ID, conv.UUID, action.Value[0])
|
||||
_, err := m.SendPrivateNote([]mmodels.Media{}, user.ID, conv.UUID, action.Value[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("sending private note: %w", err)
|
||||
}
|
||||
case amodels.ActionReply:
|
||||
// Make recipient list.
|
||||
to, cc, bcc, err := m.makeRecipients(conv.ID, conv.Contact.Email.String, conv.InboxMail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("making recipients for reply action: %w", err)
|
||||
}
|
||||
return m.SendReply(
|
||||
_, err = m.SendReply(
|
||||
[]mmodels.Media{},
|
||||
conv.InboxID,
|
||||
user.ID,
|
||||
@@ -938,6 +941,9 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
|
||||
bcc,
|
||||
map[string]any{}, /**meta**/
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sending reply: %w", err)
|
||||
}
|
||||
case amodels.ActionSetSLA:
|
||||
slaID, err := strconv.Atoi(action.Value[0])
|
||||
if err != nil {
|
||||
@@ -951,6 +957,7 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
|
||||
default:
|
||||
return fmt.Errorf("unknown action: %s", action.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveConversationAssignee removes the assignee from the conversation.
|
||||
@@ -991,10 +998,16 @@ func (m *Manager) SendCSATReply(actorUserID int, conversation models.Conversatio
|
||||
// Make recipient list.
|
||||
to, cc, bcc, err := m.makeRecipients(conversation.ID, conversation.Contact.Email.String, conversation.InboxMail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("making recipients for CSAT reply: %w", err)
|
||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.csat}"), nil)
|
||||
}
|
||||
|
||||
return m.SendReply(nil /**media**/, conversation.InboxID, actorUserID, conversation.UUID, message, to, cc, bcc, meta)
|
||||
// Send CSAT reply.
|
||||
_, err = m.SendReply(nil /**media**/, conversation.InboxID, actorUserID, conversation.UUID, message, to, cc, bcc, meta)
|
||||
if err != nil {
|
||||
m.lo.Error("error sending CSAT reply", "conversation_uuid", conversation.UUID, "error", err)
|
||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.csat}"), nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteConversation deletes a conversation.
|
||||
|
||||
@@ -356,8 +356,7 @@ func (m *Manager) MarkMessageAsPending(uuid string) error {
|
||||
}
|
||||
|
||||
// SendPrivateNote inserts a private message in a conversation.
|
||||
func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversationUUID, content string) error {
|
||||
// Insert Message.
|
||||
func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversationUUID, content string) (models.Message, error) {
|
||||
message := models.Message{
|
||||
ConversationUUID: conversationUUID,
|
||||
SenderID: senderID,
|
||||
@@ -369,18 +368,25 @@ func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversat
|
||||
Private: true,
|
||||
Media: media,
|
||||
}
|
||||
return m.InsertMessage(&message)
|
||||
if err := m.InsertMessage(&message); err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// SendReply inserts a reply message in a conversation.
|
||||
func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conversationUUID, content string, to, cc, bcc []string, meta map[string]interface{}) error {
|
||||
// Save to, cc and bcc in meta.
|
||||
func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conversationUUID, content string, to, cc, bcc []string, meta map[string]interface{}) (models.Message, error) {
|
||||
var (
|
||||
message = models.Message{}
|
||||
)
|
||||
|
||||
// Clear empty fields in to, cc, bcc.
|
||||
to = stringutil.RemoveEmpty(to)
|
||||
cc = stringutil.RemoveEmpty(cc)
|
||||
bcc = stringutil.RemoveEmpty(bcc)
|
||||
|
||||
if len(to) == 0 {
|
||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.empty", "name", "`to`"), nil)
|
||||
return message, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.empty", "name", "`to`"), nil)
|
||||
}
|
||||
meta["to"] = to
|
||||
|
||||
@@ -393,22 +399,22 @@ func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conver
|
||||
|
||||
metaJSON, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorMarshalling", "name", "{globals.terms.meta}"), nil)
|
||||
return message, envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorMarshalling", "name", "{globals.terms.meta}"), nil)
|
||||
}
|
||||
|
||||
// Generage unique source ID i.e. message-id for email.
|
||||
inbox, err := m.inboxStore.GetDBRecord(inboxID)
|
||||
if err != nil {
|
||||
return err
|
||||
return message, err
|
||||
}
|
||||
sourceID, err := stringutil.GenerateEmailMessageID(conversationUUID, inbox.From)
|
||||
if err != nil {
|
||||
m.lo.Error("error generating source message id", "error", err)
|
||||
return envelope.NewError(envelope.GeneralError, m.i18n.T("conversation.errorGeneratingMessageID"), nil)
|
||||
return message, envelope.NewError(envelope.GeneralError, m.i18n.T("conversation.errorGeneratingMessageID"), nil)
|
||||
}
|
||||
|
||||
// Insert Message.
|
||||
message := models.Message{
|
||||
message = models.Message{
|
||||
ConversationUUID: conversationUUID,
|
||||
SenderID: senderID,
|
||||
Type: models.MessageOutgoing,
|
||||
@@ -421,16 +427,17 @@ func (m *Manager) SendReply(media []mmodels.Media, inboxID, senderID int, conver
|
||||
Meta: metaJSON,
|
||||
SourceID: null.StringFrom(sourceID),
|
||||
}
|
||||
return m.InsertMessage(&message)
|
||||
if err := m.InsertMessage(&message); err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// InsertMessage inserts a message and attaches the media to the message.
|
||||
func (m *Manager) InsertMessage(message *models.Message) error {
|
||||
// Private message is always sent.
|
||||
if message.Private {
|
||||
message.Status = models.MessageStatusSent
|
||||
}
|
||||
|
||||
if len(message.Meta) == 0 || string(message.Meta) == "null" {
|
||||
message.Meta = json.RawMessage(`{}`)
|
||||
}
|
||||
@@ -438,14 +445,16 @@ func (m *Manager) InsertMessage(message *models.Message) error {
|
||||
// Convert HTML content to text for search.
|
||||
message.TextContent = stringutil.HTML2Text(message.Content)
|
||||
|
||||
// Insert Message.
|
||||
if err := m.q.InsertMessage.QueryRow(message.Type, message.Status, message.ConversationID, message.ConversationUUID, message.Content, message.TextContent, message.SenderID, message.SenderType,
|
||||
message.Private, message.ContentType, message.SourceID, message.Meta).Scan(&message.ID, &message.UUID, &message.CreatedAt); err != nil {
|
||||
// Insert and scan the message into the struct.
|
||||
if err := m.q.InsertMessage.Get(message,
|
||||
message.Type, message.Status, message.ConversationID, message.ConversationUUID,
|
||||
message.Content, message.TextContent, message.SenderID, message.SenderType,
|
||||
message.Private, message.ContentType, message.SourceID, message.Meta); err != nil {
|
||||
m.lo.Error("error inserting message in db", "error", err)
|
||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorInserting", "name", "{globals.terms.message}"), nil)
|
||||
}
|
||||
|
||||
// Attach message to the media.
|
||||
// Attach just inserted message to the media.
|
||||
for _, media := range message.Media {
|
||||
m.mediaStore.Attach(media.ID, mmodels.ModelMessages, message.ID)
|
||||
}
|
||||
@@ -465,14 +474,20 @@ func (m *Manager) InsertMessage(message *models.Message) error {
|
||||
// Broadcast new message.
|
||||
m.BroadcastNewMessage(message)
|
||||
|
||||
// Refetch message and send webhook event for message created.
|
||||
updatedMessage, err := m.GetMessage(message.UUID)
|
||||
if err != nil {
|
||||
m.lo.Error("error fetching updated message for webhook event", "uuid", message.UUID, "error", err)
|
||||
} else {
|
||||
m.webhookStore.TriggerEvent(wmodels.EventMessageCreated, updatedMessage)
|
||||
// Refetch message if this message has media attachments, as media gets linked after inserting the message.
|
||||
if len(message.Media) > 0 {
|
||||
refetchedMessage, err := m.GetMessage(message.UUID)
|
||||
if err != nil {
|
||||
m.lo.Error("error fetching message after insert", "error", err)
|
||||
} else {
|
||||
// Replace the message in the struct with the refetched message.
|
||||
*message = refetchedMessage
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger webhook for new message created.
|
||||
m.webhookStore.TriggerEvent(wmodels.EventMessageCreated, message)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -530,7 +545,7 @@ func (m *Manager) InsertConversationActivity(activityType, conversationUUID, new
|
||||
content, err := m.getMessageActivityContent(activityType, newValue, actor.FullName())
|
||||
if err != nil {
|
||||
m.lo.Error("error could not generate activity content", "error", err)
|
||||
return err
|
||||
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorGenerating", "name", "{globals.terms.activityMessage}"), nil)
|
||||
}
|
||||
|
||||
message := models.Message{
|
||||
|
||||
@@ -509,9 +509,9 @@ inserted_msg AS (
|
||||
$1, $2, (SELECT id FROM conversation_id),
|
||||
$5, $6, $7, $8, $9, $10, $11, $12
|
||||
)
|
||||
RETURNING id, uuid, created_at, conversation_id
|
||||
RETURNING *
|
||||
)
|
||||
SELECT id, uuid, created_at FROM inserted_msg;
|
||||
SELECT * FROM inserted_msg;
|
||||
|
||||
-- name: message-exists-by-source-id
|
||||
SELECT conversation_id
|
||||
|
||||
@@ -89,7 +89,7 @@ func New(opts Opts) (*Manager, error) {
|
||||
db: opts.DB,
|
||||
deliveryQueue: make(chan DeliveryTask, opts.QueueSize),
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Timeout: opts.Timeout,
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 3 * time.Second,
|
||||
|
||||
Reference in New Issue
Block a user