diff --git a/frontend/package.json b/frontend/package.json index 4f46436b..b899efda 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "serve": "vite preview" }, "dependencies": { + "@tiptap/extension-paragraph": "^2.4.0", "@twilio/voice-sdk": "^2.10.2", "@vueuse/core": "^10.3.0", "@vueuse/integrations": "^10.3.0", diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue index 3bf06991..8df55c2d 100644 --- a/frontend/src/components/Activities.vue +++ b/frontend/src/components/Activities.vue @@ -442,7 +442,7 @@ 'outgoing_call', ].includes(activity.activity_type), 'bg-white': ['added', 'removed', 'changed'].includes( - activity.activity_type + activity.activity_type, ), }" > @@ -528,7 +528,10 @@ {{ activity.data.bcc }} -
+
{ }, ] return actions.filter((action) => - action.condition ? action.condition() : true + action.condition ? action.condition() : true, ) }) @@ -1120,12 +1123,12 @@ const activities = computed(() => { } else if (props.title == 'Emails') { if (!all_activities.data?.versions) return [] activities = all_activities.data.versions.filter( - (activity) => activity.activity_type === 'communication' + (activity) => activity.activity_type === 'communication', ) } else if (props.title == 'Comments') { if (!all_activities.data?.versions) return [] activities = all_activities.data.versions.filter( - (activity) => activity.activity_type === 'comment' + (activity) => activity.activity_type === 'comment', ) } else if (props.title == 'Calls') { if (!all_activities.data?.calls) return [] @@ -1338,12 +1341,15 @@ function reply(email, reply_all = false) { editor.bccEmails = bcc } + let repliedMessage = `
${message}
` + editor.editor .chain() .clearContent() - .insertContent(message) + .insertContent('

.

') + .updateAttributes('paragraph', {class:'reply-to-content'}) + .insertContent(repliedMessage) .focus('all') - .setBlockquote() .insertContentAt(0, { type: 'paragraph' }) .focus('start') .run() diff --git a/frontend/src/components/EmailContent.vue b/frontend/src/components/EmailContent.vue index f68ea6b6..061dd7c1 100644 --- a/frontend/src/components/EmailContent.vue +++ b/frontend/src/components/EmailContent.vue @@ -6,7 +6,7 @@ style=" mask-image: linear-gradient( to bottom, - black calc(100% - 30px), + black calc(100% - 20px), transparent 100% ); " @@ -27,6 +27,90 @@ const files = import.meta.globEager('/src/index.css', { query: '?inline' }) const css = files['/src/index.css'].default const iframeRef = ref(null) +const _content = ref(props.content) + +const parser = new DOMParser() +const doc = parser.parseFromString(_content.value, 'text/html') + +const gmailReplyToContent = doc.querySelectorAll('div.gmail_quote') +const outlookReplyToContent = doc.querySelectorAll('div#appendonsend') +const replyToContent = doc.querySelectorAll('p.reply-to-content') + +if (gmailReplyToContent.length) { + _content.value = parseReplyToContent(doc, 'div.gmail_quote', true) +} else if (outlookReplyToContent.length) { + _content.value = parseReplyToContent(doc, 'div#appendonsend') +} else if (replyToContent.length) { + _content.value = parseReplyToContent(doc, 'p.reply-to-content') +} + +function parseReplyToContent(doc, selector, forGmail = false) { + function handleAllInstances(doc) { + const replyToContentElements = doc.querySelectorAll(selector) + if (replyToContentElements.length === 0) return + const replyToContentElement = replyToContentElements[0] + replaceReplyToContent(replyToContentElement, forGmail) + handleAllInstances(doc) + } + + handleAllInstances(doc) + + return doc.body.innerHTML +} + +function replaceReplyToContent(replyToContentElement, forGmail) { + if (!replyToContentElement) return + let randomId = Math.random().toString(36).substring(2, 7) + const wrapper = doc.createElement('div') + wrapper.classList.add('replied-content') + + const collapseLabel = doc.createElement('label') + collapseLabel.classList.add('collapse') + collapseLabel.setAttribute('for', randomId) + collapseLabel.innerHTML = '...' + wrapper.appendChild(collapseLabel) + + const collapseInput = doc.createElement('input') + collapseInput.setAttribute('id', randomId) + collapseInput.setAttribute('class', 'replyCollapser') + collapseInput.setAttribute('type', 'checkbox') + wrapper.appendChild(collapseInput) + + if (forGmail) { + const prevSibling = replyToContentElement.previousElementSibling + if (prevSibling && prevSibling.tagName === 'BR') { + prevSibling.remove() + } + let cloned = replyToContentElement.cloneNode(true) + cloned.classList.remove('gmail_quote') + wrapper.appendChild(cloned) + } else { + const allSiblings = Array.from(replyToContentElement.parentElement.children) + const replyToContentIndex = allSiblings.indexOf(replyToContentElement) + const followingSiblings = allSiblings.slice(replyToContentIndex + 1) + + if (followingSiblings.length === 0) return + + let clonedFollowingSiblings = followingSiblings.map((sibling) => + sibling.cloneNode(true), + ) + + const div = doc.createElement('div') + div.append(...clonedFollowingSiblings) + + wrapper.append(div) + + // Remove all siblings after the reply-to-content element + for (let i = replyToContentIndex + 1; i < allSiblings.length; i++) { + replyToContentElement.parentElement.removeChild(allSiblings[i]) + } + } + + replyToContentElement.parentElement.replaceChild( + wrapper, + replyToContentElement, + ) +} const htmlContent = ` @@ -35,6 +119,35 @@ const htmlContent = ` - + ` @@ -120,7 +233,18 @@ watch(iframeRef, (iframe) => { iframe.onload = () => { const emailContent = iframe.contentWindow.document.querySelector('.email-content') - iframe.style.height = emailContent.offsetHeight + 25 + 'px' + let parent = emailContent.closest('html') + + iframe.style.height = parent.offsetHeight + 'px' + + let replyCollapsers = emailContent.querySelectorAll('.replyCollapser') + if (replyCollapsers.length) { + replyCollapsers.forEach((replyCollapser) => { + replyCollapser.addEventListener('change', () => { + iframe.style.height = parent.offsetHeight + 'px' + }) + }) + } } } }) diff --git a/frontend/src/components/EmailEditor.vue b/frontend/src/components/EmailEditor.vue index d7f88179..2398aba5 100644 --- a/frontend/src/components/EmailEditor.vue +++ b/frontend/src/components/EmailEditor.vue @@ -1,12 +1,20 @@