75ff60a04d
Signed-off-by: Sebastian <krupinski01@gmail.com>
294 lines
7.2 KiB
Vue
294 lines
7.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch, onBeforeUnmount } from 'vue'
|
|
import { storeToRefs } from 'pinia'
|
|
import { useEditor } from '@tiptap/vue-3'
|
|
import StarterKit from '@tiptap/starter-kit'
|
|
import Link from '@tiptap/extension-link'
|
|
import Underline from '@tiptap/extension-underline'
|
|
import TextAlign from '@tiptap/extension-text-align'
|
|
import Placeholder from '@tiptap/extension-placeholder'
|
|
import { EntityObject } from '@MailManager/models/entity'
|
|
import type { CollectionObject, MessageAddressObject } from '@MailManager/models'
|
|
import { useMailOperationsStore } from '@/stores/mailOperationsStore'
|
|
import { useMailCompositionStore } from '@/stores/mailCompositionStore'
|
|
import { ComposerMode } from '@/types/composer'
|
|
import ComposerToolbar from '@/components/composer/ComposerToolbar.vue'
|
|
import ComposerSender from '@/components/composer/ComposerSender.vue'
|
|
import ComposerRecipients from '@/components/composer/ComposerRecipients.vue'
|
|
import ComposerAttachments from '@/components/composer/ComposerAttachments.vue'
|
|
import ComposerEditor from '@/components/composer/ComposerEditor.vue'
|
|
|
|
// Props
|
|
interface Props {
|
|
mode: ComposerMode
|
|
source?: EntityObject | MessageAddressObject | null
|
|
folder?: CollectionObject | null
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
// Emits
|
|
const emit = defineEmits<{
|
|
close: []
|
|
}>()
|
|
|
|
const mailOperationsStore = useMailOperationsStore()
|
|
const compositionStore = useMailCompositionStore()
|
|
const {
|
|
composerSending: sending,
|
|
} = storeToRefs(mailOperationsStore)
|
|
|
|
const {
|
|
activeDraft,
|
|
saving,
|
|
stageStatus,
|
|
} = storeToRefs(compositionStore)
|
|
|
|
// State
|
|
const showCc = ref(false)
|
|
const showBcc = ref(false)
|
|
const applyingDraftToEditor = ref(false)
|
|
|
|
const sender = computed({
|
|
get: () => activeDraft.value?.sender ?? null,
|
|
set: value => {
|
|
if (value) {
|
|
compositionStore.updateSender(value)
|
|
}
|
|
},
|
|
})
|
|
|
|
const to = computed({
|
|
get: () => activeDraft.value?.message.to ?? [],
|
|
set: value => compositionStore.updateRecipients('to', value),
|
|
})
|
|
|
|
const cc = computed({
|
|
get: () => activeDraft.value?.message.cc ?? [],
|
|
set: value => compositionStore.updateRecipients('cc', value),
|
|
})
|
|
|
|
const bcc = computed({
|
|
get: () => activeDraft.value?.message.bcc ?? [],
|
|
set: value => compositionStore.updateRecipients('bcc', value),
|
|
})
|
|
|
|
const subject = computed({
|
|
get: () => activeDraft.value?.message.subject ?? '',
|
|
set: value => compositionStore.updateSubject(value),
|
|
})
|
|
|
|
const attachments = computed(() => activeDraft.value?.attachments ?? {})
|
|
|
|
// Initialize Tiptap editor
|
|
const editor = useEditor({
|
|
extensions: [
|
|
StarterKit,
|
|
Link.configure({
|
|
openOnClick: false,
|
|
}),
|
|
Underline,
|
|
TextAlign.configure({
|
|
types: ['heading', 'paragraph'],
|
|
}),
|
|
Placeholder.configure({
|
|
placeholder: 'Write your message...',
|
|
}),
|
|
],
|
|
content: '',
|
|
editorProps: {
|
|
attributes: {
|
|
class: 'tiptap-editor pa-4',
|
|
},
|
|
},
|
|
})
|
|
|
|
watch(
|
|
[() => props.mode, () => props.source, () => editor.value],
|
|
([, , currentEditor]) => {
|
|
if (!currentEditor) {
|
|
return
|
|
}
|
|
|
|
compositionStore.openDraft(props.mode, props.source)
|
|
showCc.value = (activeDraft.value?.message.cc.length ?? 0) > 0
|
|
showBcc.value = (activeDraft.value?.message.bcc.length ?? 0) > 0
|
|
|
|
applyingDraftToEditor.value = true
|
|
currentEditor.commands.setContent(activeDraft.value?.message.body.html || '')
|
|
applyingDraftToEditor.value = false
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
// Computed
|
|
const canSend = computed(() => {
|
|
return to.value.length > 0 && subject.value.trim().length > 0
|
|
})
|
|
|
|
// Watch editor content changes
|
|
watch(editor, currentEditor => {
|
|
if (!currentEditor) {
|
|
return
|
|
}
|
|
|
|
const handleUpdate = () => {
|
|
if (applyingDraftToEditor.value) {
|
|
return
|
|
}
|
|
|
|
compositionStore.updateBody({
|
|
html: currentEditor.getHTML(),
|
|
text: currentEditor.getText(),
|
|
})
|
|
}
|
|
|
|
currentEditor.on('update', handleUpdate)
|
|
|
|
return () => {
|
|
currentEditor.off('update', handleUpdate)
|
|
}
|
|
})
|
|
|
|
// Cleanup
|
|
onBeforeUnmount(() => {
|
|
void compositionStore.flushSave()
|
|
editor.value?.destroy()
|
|
})
|
|
|
|
// Handlers
|
|
const handleClose = async () => {
|
|
await compositionStore.closeDraft()
|
|
emit('close')
|
|
}
|
|
|
|
const handleSend = async () => {
|
|
await compositionStore.sendDraft()
|
|
}
|
|
|
|
const handleAttach = async (files: File[]) => {
|
|
await compositionStore.addAttachments(files)
|
|
}
|
|
|
|
const handleDetach = async (identifier: string) => {
|
|
await compositionStore.removeAttachment(identifier)
|
|
}
|
|
|
|
const toggleCc = () => {
|
|
showCc.value = !showCc.value
|
|
}
|
|
|
|
const toggleBcc = () => {
|
|
showBcc.value = !showBcc.value
|
|
}
|
|
|
|
// Toolbar actions
|
|
const toggleBold = () => editor.value?.chain().focus().toggleBold().run()
|
|
const toggleItalic = () => editor.value?.chain().focus().toggleItalic().run()
|
|
const toggleUnderline = () => editor.value?.chain().focus().toggleUnderline().run()
|
|
const toggleBulletList = () => editor.value?.chain().focus().toggleBulletList().run()
|
|
const toggleOrderedList = () => editor.value?.chain().focus().toggleOrderedList().run()
|
|
const setLink = () => {
|
|
const url = window.prompt('Enter URL:')
|
|
if (url) {
|
|
editor.value?.chain().focus().setLink({ href: url }).run()
|
|
}
|
|
}
|
|
const removeLink = () => editor.value?.chain().focus().unsetLink().run()
|
|
const toggleLink = () => {
|
|
if (isActive('link')) {
|
|
removeLink()
|
|
return
|
|
}
|
|
|
|
setLink()
|
|
}
|
|
const isActive = (name: string, attrs?: any) => {
|
|
return editor.value?.isActive(name, attrs) || false
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="message-composer">
|
|
<ComposerToolbar
|
|
:mode="mode"
|
|
:status="stageStatus"
|
|
:can-send="canSend"
|
|
:sending="sending"
|
|
@close="handleClose"
|
|
@send="handleSend"
|
|
/>
|
|
|
|
<div class="composer-content">
|
|
<ComposerSender
|
|
v-model="sender"
|
|
:options="compositionStore.senderIdentities"
|
|
/>
|
|
|
|
<ComposerRecipients
|
|
:to="to"
|
|
:cc="cc"
|
|
:bcc="bcc"
|
|
:subject="subject"
|
|
:show-cc="showCc"
|
|
:show-bcc="showBcc"
|
|
@update:to="to = $event"
|
|
@update:cc="cc = $event"
|
|
@update:bcc="bcc = $event"
|
|
@update:subject="subject = $event"
|
|
@toggle:cc="toggleCc"
|
|
@toggle:bcc="toggleBcc"
|
|
/>
|
|
|
|
<v-divider />
|
|
|
|
<ComposerAttachments
|
|
v-if="Object.keys(attachments).length > 0"
|
|
:attachments="attachments"
|
|
@remove="handleDetach"
|
|
/>
|
|
|
|
<v-divider v-if="Object.keys(attachments).length > 0" />
|
|
|
|
<ComposerEditor
|
|
:editor="editor"
|
|
:is-bold-active="isActive('bold')"
|
|
:is-italic-active="isActive('italic')"
|
|
:is-underline-active="isActive('underline')"
|
|
:is-bullet-list-active="isActive('bulletList')"
|
|
:is-ordered-list-active="isActive('orderedList')"
|
|
:is-link-active="isActive('link')"
|
|
@bold="toggleBold"
|
|
@italic="toggleItalic"
|
|
@underline="toggleUnderline"
|
|
@bullet-list="toggleBulletList"
|
|
@ordered-list="toggleOrderedList"
|
|
@link="toggleLink"
|
|
@attach="handleAttach"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.message-composer {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.composer-toolbar {
|
|
flex-shrink: 0;
|
|
border-bottom: 1px solid rgb(var(--v-border-color));
|
|
}
|
|
|
|
.composer-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
</style>
|