Skip to content

Commit

Permalink
Merge pull request #43 from shariquerik/cc-bcc-reply-all
Browse files Browse the repository at this point in the history
feat: CC, BCC & ReplyAll
  • Loading branch information
shariquerik authored Dec 26, 2023
2 parents fe320bd + 35c1d84 commit 9681995
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 41 deletions.
46 changes: 41 additions & 5 deletions frontend/src/components/Activities.vue
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@
<div
class="cursor-pointer rounded-md bg-gray-50 p-3 text-base leading-6 transition-all duration-300 ease-in-out"
>
<div class="mb-3 flex items-center justify-between gap-2">
<div class="mb-1 flex items-center justify-between gap-2">
<div class="flex items-center gap-2">
<UserAvatar :user="activity.data.sender" size="md" />
<span>{{ activity.data.sender_full_name }}</span>
Expand All @@ -370,12 +370,29 @@
<Button
variant="ghost"
class="text-gray-700"
@click="reply(activity.data.content)"
@click="reply(activity.data)"
>
<ReplyIcon class="h-4 w-4" />
</Button>
<Button
variant="ghost"
class="text-gray-700"
@click="reply(activity.data, true)"
>
<ReplyAllIcon class="h-4 w-4" />
</Button>
</div>
</div>
<div class="mb-3 text-sm leading-5 text-gray-600">
<span class="mr-1">TO:</span>
<span>{{ activity.data.recipients }}</span>
<span v-if="activity.data.cc">, </span>
<span v-if="activity.data.cc" class="mr-1">CC:</span>
<span v-if="activity.data.cc">{{ activity.data.cc }}</span>
<span v-if="activity.data.bcc">, </span>
<span v-if="activity.data.bcc" class="mr-1">BCC:</span>
<span v-if="activity.data.bcc">{{ activity.data.bcc }}</span>
</div>
<span class="prose-f" v-html="activity.data.content" />
<div class="flex flex-wrap gap-2">
<AttachmentItem
Expand Down Expand Up @@ -679,6 +696,7 @@ import EmailAtIcon from '@/components/Icons/EmailAtIcon.vue'
import InboundCallIcon from '@/components/Icons/InboundCallIcon.vue'
import OutboundCallIcon from '@/components/Icons/OutboundCallIcon.vue'
import ReplyIcon from '@/components/Icons/ReplyIcon.vue'
import ReplyAllIcon from '@/components/Icons/ReplyAllIcon.vue'
import AttachmentItem from '@/components/AttachmentItem.vue'
import CommunicationArea from '@/components/CommunicationArea.vue'
import NoteModal from '@/components/Modals/NoteModal.vue'
Expand Down Expand Up @@ -997,10 +1015,28 @@ function updateTaskStatus(status, task) {
}
// Email
function reply(message) {
function reply(email, reply_all = false) {
emailBox.value.show = true
let editor = emailBox.value.editor.editor
editor
let editor = emailBox.value.editor
let message = email.content
let recipients = email.recipients.split(',').map((r) => r.trim())
editor.toEmails = recipients
editor.cc = editor.bcc = false
editor.ccEmails = []
editor.bccEmails = []
if (reply_all) {
let cc = email.cc?.split(',').map((r) => r.trim())
let bcc = email.bcc?.split(',').map((r) => r.trim())
editor.cc = cc ? true : false
editor.bcc = bcc ? true : false
editor.ccEmails = cc
editor.bccEmails = bcc
}
editor.editor
.chain()
.clearContent()
.insertContent(message)
Expand Down
59 changes: 38 additions & 21 deletions frontend/src/components/CommunicationArea.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
<template>
<div class="flex gap-1.5 border-t px-10 py-2.5">
<Button
ref="sendEmailRef"
variant="ghost"
:class="[showCommunicationBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
label="Reply"
@click="showCommunicationBox = !showCommunicationBox"
>
<template #prefix>
<EmailIcon class="h-4" />
</template>
</Button>
<!-- <Button variant="ghost" label="Comment">
<template #prefix>
<CommentIcon class="h-4" />
</template>
</Button> -->
<div class="flex justify-between gap-3 border-t px-10 py-2.5">
<div class="flex gap-1.5">
<Button
ref="sendEmailRef"
variant="ghost"
:class="[showCommunicationBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
label="Reply"
@click="showCommunicationBox = !showCommunicationBox"
>
<template #prefix>
<EmailIcon class="h-4" />
</template>
</Button>
<!-- <Button variant="ghost" label="Comment">
<template #prefix>
<CommentIcon class="h-4" />
</template>
</Button> -->
</div>
<div v-if="showCommunicationBox" class="flex gap-1.5">
<Button
label="CC"
@click="newEmailEditor.cc = !newEmailEditor.cc"
:class="[newEmailEditor.cc ? 'bg-gray-300 hover:bg-gray-200' : '']"
/>
<Button
label="BCC"
@click="newEmailEditor.bcc = !newEmailEditor.bcc"
:class="[newEmailEditor.bcc ? 'bg-gray-300 hover:bg-gray-200' : '']"
/>
</div>
</div>
<div
v-show="showCommunicationBox"
Expand Down Expand Up @@ -71,7 +85,7 @@ const showCommunicationBox = ref(false)
const newEmail = ref('')
const newEmailEditor = ref(null)
const sendEmailRef = ref(null)
const attachments = ref([]);
const attachments = ref([])
watch(
() => showCommunicationBox.value,
Expand All @@ -91,11 +105,14 @@ const onNewEmailChange = (value) => {
}
async function sendMail() {
let recipients = newEmailEditor.value.toEmails
let cc = newEmailEditor.value.ccEmails
let bcc = newEmailEditor.value.bccEmails
await call('frappe.core.doctype.communication.email.make', {
recipients: doc.value.data.email,
recipients: recipients.join(', '),
attachments: attachments.value.map((x) => x.name),
cc: '',
bcc: '',
cc: cc.join(', '),
bcc: bcc.join(', '),
subject: 'Email from Agent',
content: newEmail.value,
doctype: props.doctype,
Expand Down
88 changes: 88 additions & 0 deletions frontend/src/components/Controls/MultiselectInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<template>
<div>
<div class="flex flex-wrap gap-1">
<Button
v-for="value in values"
:label="value"
theme="gray"
variant="subtle"
class="rounded-full"
>
<template #suffix>
<FeatherIcon
class="h-3.5"
name="x"
@click.stop="removeValue(value)"
/>
</template>
</Button>
<TextInput
class="min-w-20 flex-1 border-none bg-white hover:bg-white focus:border-none focus:shadow-none focus-visible:ring-0"
v-model="currentValue"
@keydown.enter.capture.stop="addValue"
@keydown.tab.capture.stop="addValue"
@keydown.delete.capture.stop="removeLastValue"
@keydown.meta.delete.capture.stop="removeAllValue"
/>
</div>
<ErrorMessage class="mt-2 pl-2" v-if="error" :message="error" />
</div>
</template>

<script setup>
import { Button, ErrorMessage, FeatherIcon, TextInput } from 'frappe-ui'
import { ref, defineModel } from 'vue'
const props = defineProps({
validate: {
type: Function,
default: null,
},
errorMessage: {
type: Function,
default: (value) => `${value} is an Invalid value`,
},
})
const values = defineModel()
const currentValue = ref('')
const error = ref(null)
const addValue = () => {
error.value = null
if (currentValue.value) {
const splitValues = currentValue.value.split(',')
splitValues.forEach((value) => {
value = value.trim()
if (value) {
// check if value is not already in the values array
if (!values.value.includes(value)) {
// check if value is valid
if (value && props.validate && !props.validate(value)) {
error.value = props.errorMessage(value)
return
}
// add value to values array
values.value.push(value)
currentValue.value = currentValue.value.replace(value, '')
}
}
})
!error.value && (currentValue.value = '')
}
}
const removeValue = (value) => {
values.value = values.value.filter((v) => v !== value)
}
const removeAllValue = () => {
values.value = []
}
const removeLastValue = () => {
if (!currentValue.value) {
values.value.pop()
}
}
</script>
67 changes: 52 additions & 15 deletions frontend/src/components/EmailEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,39 @@
:editable="editable"
>
<template #top>
<div class="mx-10 border-b border-t py-2.5">
<div
class="mx-10 flex items-center gap-2 border-t py-2.5"
:class="[cc || bcc ? '' : 'border-b']"
>
<span class="text-xs text-gray-500">TO:</span>
<span
v-if="modelValue.email"
class="ml-2 cursor-pointer rounded-md bg-gray-100 px-2 py-1 text-sm text-gray-800"
>
{{ modelValue.email }}
</span>
<MultiselectInput
class="flex-1"
v-model="toEmails"
:validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`"
/>
</div>
<div
v-if="cc"
class="mx-10 flex items-center gap-2 py-2.5"
:class="bcc ? '' : 'border-b'"
>
<span class="text-xs text-gray-500">CC:</span>
<MultiselectInput
class="flex-1"
v-model="ccEmails"
:validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`"
/>
</div>
<div v-if="bcc" class="mx-10 flex items-center gap-2 border-b py-2.5">
<span class="text-xs text-gray-500">BCC:</span>
<MultiselectInput
class="flex-1"
v-model="bccEmails"
:validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`"
/>
</div>
</template>
<template v-slot:editor="{ editor }">
Expand All @@ -42,10 +67,12 @@
</template>
</AttachmentItem>
</div>
<div class="flex justify-between border-t px-10 py-2.5">
<div class="flex items-center">
<div
class="flex justify-between gap-2 overflow-hidden border-t px-10 py-2.5"
>
<div class="flex items-center overflow-x-auto">
<TextEditorFixedMenu
class="-ml-1 overflow-x-auto"
class="-ml-1"
:buttons="textEditorMenuButtons"
/>
<FileUploader
Expand All @@ -70,10 +97,12 @@
</FileUploader>
</div>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}"> Discard </Button>
<Button variant="solid" v-bind="submitButtonProps || {}">
Submit
</Button>
<Button v-bind="discardButtonProps || {}" label="Discard" />
<Button
variant="solid"
v-bind="submitButtonProps || {}"
label="Submit"
/>
</div>
</div>
</div>
Expand All @@ -84,12 +113,14 @@
<script setup>
import AttachmentIcon from '@/components/Icons/AttachmentIcon.vue'
import AttachmentItem from '@/components/AttachmentItem.vue'
import MultiselectInput from '@/components/Controls/MultiselectInput.vue'
import {
TextEditorFixedMenu,
TextEditor,
FileUploader,
FeatherIcon,
} from 'frappe-ui'
import { validateEmail } from '@/utils'
import { EditorContent } from '@tiptap/vue-3'
import { ref, computed, defineModel } from 'vue'
Expand Down Expand Up @@ -129,6 +160,12 @@ const modelValue = defineModel()
const attachments = defineModel('attachments')
const textEditor = ref(null)
const cc = ref(false)
const bcc = ref(false)
const toEmails = ref(modelValue.value.email ? [modelValue.value.email] : [])
const ccEmails = ref([])
const bccEmails = ref([])
const editor = computed(() => {
return textEditor.value.editor
Expand All @@ -138,7 +175,7 @@ function removeAttachment(attachment) {
attachments.value = attachments.value.filter((a) => a !== attachment)
}
defineExpose({ editor })
defineExpose({ editor, cc, bcc, toEmails, ccEmails, bccEmails })
const textEditorMenuButtons = [
'Paragraph',
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/Icons/ReplyAllIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.85355 3.14645C5.65829 2.95118 5.34171 2.95118 5.14645 3.14645L1.14645 7.14645C0.951184 7.34171 0.951184 7.65829 1.14645 7.85355L5.14645 11.8536C5.34171 12.0488 5.65829 12.0488 5.85355 11.8536C6.04882 11.6583 6.04882 11.3417 5.85355 11.1464L2.20711 7.5L5.85355 3.85355C6.04882 3.65829 6.04882 3.34171 5.85355 3.14645ZM10 8C11.933 8 13.5 9.567 13.5 11.5V12C13.5 12.2761 13.7239 12.5 14 12.5C14.2761 12.5 14.5 12.2761 14.5 12V11.5C14.5 9.01472 12.4853 7 10 7H6.6728L9.81924 3.85355C10.0145 3.65829 10.0145 3.34171 9.81924 3.14645C9.62398 2.95118 9.3074 2.95118 9.11214 3.14645L5.11214 7.14645C4.91688 7.34171 4.91688 7.65829 5.11214 7.85355L9.11214 11.8536C9.3074 12.0488 9.62398 12.0488 9.81924 11.8536C10.0145 11.6583 10.0145 11.3417 9.81924 11.1464L6.6728 8H10Z"
fill="currentColor"
/>
</svg>
</template>
6 changes: 6 additions & 0 deletions frontend/src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,9 @@ export function formatNumberIntoCurrency(value) {
export function startCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}

export function validateEmail(email) {
let regExp =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return regExp.test(email)
}

0 comments on commit 9681995

Please sign in to comment.