crm/frontend/src/components/Activities/EmailContent.vue

245 lines
6.3 KiB
Vue

<template>
<iframe
ref="iframeRef"
:srcdoc="htmlContent"
class="prose-f block h-10 max-h-[500px] w-full"
/>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
content: {
type: String,
required: true,
},
})
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 = `
<!DOCTYPE html>
<html>
<head>
<style>
${css}
.replied-content .collapse {
margin: 10px 0 10px 0;
visibility: visible;
cursor: pointer;
display: flex;
font-size: larger;
font-weight: 700;
height: 12px;
line-height: 0.1;
background: #e8eaed;
width: 23px;
justify-content: center;
border-radius: 5px;
}
.replied-content .collapse:hover {
background: #dadce0;
}
.replied-content .collapse + input {
display: none;
}
.replied-content .collapse + input + div {
display: none;
}
.replied-content .collapse + input:checked + div {
display: block;
}
.email-content {
word-break: break-word;
}
.email-content
:is(:where(table):not(:where([class~='not-prose'], [class~='not-prose']
*))) {
table-layout: auto;
}
.email-content
:where(table):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
width: unset;
table-layout: auto;
text-align: unset;
margin-top: unset;
margin-bottom: unset;
font-size: unset;
line-height: unset;
}
/* tr */
.email-content
:where(tbody tr):not(:where([class~='not-prose'], [class~='not-prose']
*)) {
border-bottom-width: 0;
border-bottom-color: transparent;
}
/* td */
.email-content
:is(:where(td):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
position: unset;
border-width: 0;
border-color: transparent;
padding: 0;
}
.email-content
:where(tbody td):not(:where([class~='not-prose'], [class~='not-prose']
*)) {
vertical-align: revert;
}
/* image */
.email-content
:is(:where(img):not(:where([class~='not-prose'], [class~='not-prose']
*))) {
border-width: 0;
}
.email-content
:where(img):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
margin: 0;
}
/* before & after */
.email-content
:where(blockquote
p:first-of-type):not(:where([class~='not-prose'], [class~='not-prose']
*))::before {
content: none;
}
.email-content
:where(blockquote
p:last-of-type):not(:where([class~='not-prose'], [class~='not-prose']
*))::after {
content: none;
}
</style>
</head>
<body>
<div ref="emailContentRef" class="email-content prose-f">${_content.value}</div>
</body>
</html>
`
watch(iframeRef, (iframe) => {
if (iframe) {
iframe.onload = () => {
const emailContent =
iframe.contentWindow.document.querySelector('.email-content')
let parent = emailContent.closest('html')
iframe.style.height = parent.offsetHeight + 1 + 'px'
let replyCollapsers = emailContent.querySelectorAll('.replyCollapser')
if (replyCollapsers.length) {
replyCollapsers.forEach((replyCollapser) => {
replyCollapser.addEventListener('change', () => {
iframe.style.height = parent.offsetHeight + 1 + 'px'
})
})
}
}
}
})
</script>