feat: Comment Box

This commit is contained in:
Shariq Ansari 2024-01-25 14:33:40 +05:30
parent 669f26cdf2
commit 58aa8e5a13
3 changed files with 280 additions and 26 deletions

View File

@ -0,0 +1,169 @@
<template>
<TextEditor
ref="textEditor"
:editor-class="['prose-sm max-w-none', editable && 'min-h-[7rem]']"
:content="value"
@change="editable ? $emit('change', $event) : null"
:starterkit-options="{ heading: { levels: [2, 3, 4, 5, 6] } }"
:placeholder="placeholder"
:editable="editable"
>
<template v-slot:editor="{ editor }">
<EditorContent
:class="[
editable && 'mx-10 max-h-[50vh] overflow-y-auto border-t py-3',
]"
:editor="editor"
/>
</template>
<template v-slot:bottom>
<div v-if="editable" class="flex flex-col gap-2">
<div class="flex flex-wrap gap-2 px-10">
<AttachmentItem
v-for="a in attachments"
:key="a.file_url"
:label="a.file_name"
>
<template #suffix>
<FeatherIcon
class="h-3.5"
name="x"
@click.stop="removeAttachment(a)"
/>
</template>
</AttachmentItem>
</div>
<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"
:buttons="textEditorMenuButtons"
/>
<FileUploader
:upload-args="{
doctype: doctype,
docname: modelValue.name,
private: true,
}"
@success="(f) => attachments.push(f)"
>
<template #default="{ openFileSelector }">
<Button
theme="gray"
variant="ghost"
@click="openFileSelector()"
>
<template #icon>
<AttachmentIcon class="h-4" />
</template>
</Button>
</template>
</FileUploader>
</div>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}" label="Discard" />
<Button
variant="solid"
v-bind="submitButtonProps || {}"
label="Submit"
/>
</div>
</div>
</div>
</template>
</TextEditor>
</template>
<script setup>
import AttachmentIcon from '@/components/Icons/AttachmentIcon.vue'
import AttachmentItem from '@/components/AttachmentItem.vue'
import { TextEditorFixedMenu, TextEditor, FileUploader } from 'frappe-ui'
import { EditorContent } from '@tiptap/vue-3'
import { ref, computed, defineModel } from 'vue'
const props = defineProps({
value: {
type: String,
default: '',
},
placeholder: {
type: String,
default: null,
},
editable: {
type: Boolean,
default: true,
},
doctype: {
type: String,
default: 'CRM Lead',
},
editorProps: {
type: Object,
default: () => ({}),
},
submitButtonProps: {
type: Object,
default: () => ({}),
},
discardButtonProps: {
type: Object,
default: () => ({}),
},
})
const emit = defineEmits(['change'])
const modelValue = defineModel()
const attachments = defineModel('attachments')
const textEditor = ref(null)
const editor = computed(() => {
return textEditor.value.editor
})
function removeAttachment(attachment) {
attachments.value = attachments.value.filter((a) => a !== attachment)
}
defineExpose({ editor })
const textEditorMenuButtons = [
'Paragraph',
['Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6'],
'Separator',
'Bold',
'Italic',
'Separator',
'Bullet List',
'Numbered List',
'Separator',
'Align Left',
'Align Center',
'Align Right',
'FontColor',
'Separator',
'Image',
'Video',
'Link',
'Blockquote',
'Code',
'Horizontal Rule',
[
'InsertTable',
'AddColumnBefore',
'AddColumnAfter',
'DeleteColumn',
'AddRowBefore',
'AddRowAfter',
'DeleteRow',
'MergeCells',
'SplitCell',
'ToggleHeaderColumn',
'ToggleHeaderRow',
'ToggleHeaderCell',
'DeleteTable',
],
]
</script>

View File

@ -4,21 +4,26 @@
<Button
ref="sendEmailRef"
variant="ghost"
:class="[showCommunicationBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
:class="[showEmailBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
label="Reply"
@click="showCommunicationBox = !showCommunicationBox"
@click="toggleEmailBox()"
>
<template #prefix>
<EmailIcon class="h-4" />
</template>
</Button>
<!-- <Button variant="ghost" label="Comment">
<Button
variant="ghost"
label="Comment"
:class="[showCommentBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
@click="toggleCommentBox()"
>
<template #prefix>
<CommentIcon class="h-4" />
</template>
</Button> -->
</Button>
</div>
<div v-if="showCommunicationBox" class="flex gap-1.5">
<div v-if="showEmailBox" class="flex gap-1.5">
<Button
label="CC"
@click="toggleCC()"
@ -32,9 +37,9 @@
</div>
</div>
<div
v-show="showCommunicationBox"
@keydown.ctrl.enter.capture.stop="submitComment"
@keydown.meta.enter.capture.stop="submitComment"
v-show="showEmailBox"
@keydown.ctrl.enter.capture.stop="submitEmail"
@keydown.meta.enter.capture.stop="submitEmail"
>
<EmailEditor
ref="newEmailEditor"
@ -42,16 +47,16 @@
@change="onNewEmailChange"
:submitButtonProps="{
variant: 'solid',
onClick: submitComment,
onClick: submitEmail,
disabled: emailEmpty,
}"
:discardButtonProps="{
onClick: () => {
showCommunicationBox = false
showEmailBox = false
newEmail = ''
},
}"
:editable="showCommunicationBox"
:editable="showEmailBox"
v-model="doc.data"
v-model:attachments="attachments"
:doctype="doctype"
@ -59,10 +64,35 @@
placeholder="Add a reply..."
/>
</div>
<div v-show="showCommentBox">
<CommentBox
ref="newCommentEditor"
:value="newComment"
@change="onNewCommentChange"
:submitButtonProps="{
variant: 'solid',
onClick: submitComment,
disabled: commentEmpty,
}"
:discardButtonProps="{
onClick: () => {
showCommentBox = false
newComment = ''
},
}"
:editable="showCommentBox"
v-model="doc.data"
v-model:attachments="attachments"
:doctype="doctype"
placeholder="Add a comment..."
/>
</div>
</template>
<script setup>
import EmailEditor from '@/components/EmailEditor.vue'
import CommentBox from '@/components/CommentBox.vue'
import CommentIcon from '@/components/Icons/CommentIcon.vue'
import EmailIcon from '@/components/Icons/EmailIcon.vue'
import { usersStore } from '@/stores/users'
import { useStorage } from '@vueuse/core'
@ -83,9 +113,12 @@ const emit = defineEmits(['scroll'])
const { getUser } = usersStore()
const showCommunicationBox = ref(false)
const showEmailBox = ref(false)
const showCommentBox = ref(false)
const newEmail = useStorage('emailBoxContent', '')
const newComment = useStorage('commentBoxContent', '')
const newEmailEditor = ref(null)
const newCommentEditor = ref(null)
const sendEmailRef = ref(null)
const attachments = ref([])
@ -100,7 +133,7 @@ const subject = computed(() => {
})
watch(
() => showCommunicationBox.value,
() => showEmailBox.value,
(value) => {
if (value) {
newEmailEditor.value.editor.commands.focus()
@ -108,6 +141,19 @@ watch(
}
)
watch(
() => showCommentBox.value,
(value) => {
if (value) {
newCommentEditor.value.editor.commands.focus()
}
}
)
const commentEmpty = computed(() => {
return !newComment.value || newComment.value === '<p></p>'
})
const emailEmpty = computed(() => {
return !newEmail.value || newEmail.value === '<p></p>'
})
@ -116,6 +162,10 @@ const onNewEmailChange = (value) => {
newEmail.value = value
}
const onNewCommentChange = (value) => {
newComment.value = value
}
async function sendMail() {
let recipients = newEmailEditor.value.toEmails
let subject = newEmailEditor.value.subject
@ -136,28 +186,63 @@ async function sendMail() {
})
}
async function submitComment() {
async function sendComment() {
await call("frappe.desk.form.utils.add_comment", {
reference_doctype: props.doctype,
reference_name: doc.value.data.name,
content: newComment.value,
comment_email: getUser().name,
comment_by: getUser()?.full_name || undefined,
})
}
async function submitEmail() {
if (emailEmpty.value) return
showCommunicationBox.value = false
showEmailBox.value = false
await sendMail()
newEmail.value = ''
reload.value = true
emit('scroll')
}
async function submitComment() {
if (commentEmpty.value) return
showCommentBox.value = false
await sendComment()
newComment.value = ''
reload.value = true
emit('scroll')
}
function toggleCC() {
newEmailEditor.value.cc = !newEmailEditor.value.cc
newEmailEditor.value.cc && nextTick(() => {
newEmailEditor.value.ccInput.setFocus()
})
newEmailEditor.value.cc &&
nextTick(() => {
newEmailEditor.value.ccInput.setFocus()
})
}
function toggleBCC() {
newEmailEditor.value.bcc = !newEmailEditor.value.bcc
newEmailEditor.value.bcc && nextTick(() => {
newEmailEditor.value.bccInput.setFocus()
})
newEmailEditor.value.bcc &&
nextTick(() => {
newEmailEditor.value.bccInput.setFocus()
})
}
defineExpose({ show: showCommunicationBox, editor: newEmailEditor })
function toggleEmailBox() {
if (showCommentBox.value) {
showCommentBox.value = false
}
showEmailBox.value = !showEmailBox.value
}
function toggleCommentBox() {
if (showEmailBox.value) {
showEmailBox.value = false
}
showCommentBox.value = !showCommentBox.value
}
defineExpose({ show: showEmailBox, editor: newEmailEditor })
</script>

View File

@ -18,7 +18,7 @@
</div>
<div
class="mx-10 flex items-center gap-2 border-t py-2.5"
:class="[cc || bcc ? '' : 'border-b']"
:class="[cc || bcc ? 'border-b' : '']"
>
<span class="text-xs text-gray-500">TO:</span>
<MultiselectInput
@ -31,7 +31,7 @@
<div
v-if="cc"
class="mx-10 flex items-center gap-2 py-2.5"
:class="bcc ? '' : 'border-b'"
:class="bcc ? 'border-b' : ''"
>
<span class="text-xs text-gray-500">CC:</span>
<MultiselectInput
@ -42,7 +42,7 @@
: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">
<div v-if="bcc" class="mx-10 flex items-center gap-2 py-2.5">
<span class="text-xs text-gray-500">BCC:</span>
<MultiselectInput
ref="bccInput"
@ -55,7 +55,7 @@
</template>
<template v-slot:editor="{ editor }">
<EditorContent
:class="[editable && 'mx-10 max-h-[50vh] overflow-y-auto py-3']"
:class="[editable && 'mx-10 max-h-[50vh] overflow-y-auto py-3 border-t']"
:editor="editor"
/>
</template>