mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-22 18:43:33 +00:00
hide outlook/hotmail quoted text correctly and fix html email whitespace
This commit is contained in:
@@ -77,7 +77,7 @@
|
||||
:html="sanitizedContent"
|
||||
:allowedSchemas="['cid', 'https', 'http', 'mailto']"
|
||||
:allowed-css-properties="extendedCssProperties"
|
||||
class="mb-1 native-html whitespace-pre-wrap break-words"
|
||||
class="mb-1 native-html break-words"
|
||||
:class="{ 'mb-3': message.attachments.length > 0 }"
|
||||
/>
|
||||
</div>
|
||||
@@ -205,6 +205,7 @@ import BubbleAttachmentPreview from '@main/features/conversation/message/attachm
|
||||
import MessageEnvelope from './MessageEnvelope.vue'
|
||||
import CSATResponseDisplay from './CSATResponseDisplay.vue'
|
||||
import api from '@main/api'
|
||||
import { containsQuoteMarkers } from '@shared-ui/utils/quotedContent.js'
|
||||
|
||||
const extendedCssProperties = [...allowedCssProperties, 'transform', 'transform-origin']
|
||||
|
||||
@@ -305,7 +306,7 @@ const retryMessage = (msg) => {
|
||||
|
||||
const showQuotedText = ref(false)
|
||||
const hasQuotedContent = computed(
|
||||
() => !isOutgoing.value && sanitizedContent.value.includes('<blockquote')
|
||||
() => !isOutgoing.value && containsQuoteMarkers(sanitizedContent.value)
|
||||
)
|
||||
const toggleQuote = () => {
|
||||
showQuotedText.value = !showQuotedText.value
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { describe, test, expect } from 'vitest'
|
||||
import { containsQuoteMarkers } from '@shared-ui/utils/quotedContent.js'
|
||||
|
||||
describe('containsQuoteMarkers', () => {
|
||||
test('returns false for null', () => {
|
||||
expect(containsQuoteMarkers(null)).toBe(false)
|
||||
})
|
||||
|
||||
test('returns false for undefined', () => {
|
||||
expect(containsQuoteMarkers(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
test('returns false for empty string', () => {
|
||||
expect(containsQuoteMarkers('')).toBe(false)
|
||||
})
|
||||
|
||||
test('returns false for plain HTML with no quote', () => {
|
||||
expect(containsQuoteMarkers('<p>Hello world</p><div>thanks</div>')).toBe(false)
|
||||
})
|
||||
|
||||
test('detects <blockquote> (Gmail/Apple/Thunderbird)', () => {
|
||||
expect(containsQuoteMarkers('<blockquote>previous</blockquote>')).toBe(true)
|
||||
})
|
||||
|
||||
test('detects <blockquote> with attributes', () => {
|
||||
expect(containsQuoteMarkers('<blockquote type="cite" class="gmail_quote">x</blockquote>'))
|
||||
.toBe(true)
|
||||
})
|
||||
|
||||
test('detects Outlook Web appendonsend marker', () => {
|
||||
expect(containsQuoteMarkers('<div id="appendonsend"></div>')).toBe(true)
|
||||
})
|
||||
|
||||
test('detects Outlook divRplyFwdMsg marker', () => {
|
||||
expect(containsQuoteMarkers('<div id="divRplyFwdMsg" dir="ltr">From:</div>')).toBe(true)
|
||||
})
|
||||
|
||||
test('detects Outlook for Mac OLK_SRC_BODY_SECTION marker', () => {
|
||||
expect(containsQuoteMarkers('<div id="OLK_SRC_BODY_SECTION">body</div>')).toBe(true)
|
||||
})
|
||||
|
||||
test('detects legacy Outlook OutlookMessageHeader class', () => {
|
||||
expect(containsQuoteMarkers('<div class="OutlookMessageHeader">x</div>')).toBe(true)
|
||||
})
|
||||
|
||||
test('detects real Hotmail reply payload', () => {
|
||||
const html = `<div class="elementToProof">Quoted reply!</div>
|
||||
<div id="appendonsend"></div>
|
||||
<hr style="display:inline-block;width:98%" tabindex="-1">
|
||||
<div id="divRplyFwdMsg" dir="ltr"><font face="Calibri, sans-serif">From: Libredesk
|
||||
Sent: 17 May 2026 18:12</font></div>
|
||||
<div>Original message body</div>`
|
||||
expect(containsQuoteMarkers(html)).toBe(true)
|
||||
})
|
||||
|
||||
test('does not match marker word in plain prose', () => {
|
||||
expect(containsQuoteMarkers('I love the appendonsend feature in Outlook')).toBe(false)
|
||||
})
|
||||
|
||||
test('does not match if id uses single quotes (edge case - Outlook always emits double)', () => {
|
||||
expect(containsQuoteMarkers(`<div id='appendonsend'></div>`)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -160,6 +160,7 @@ import MessageAttachment from './MessageAttachment.vue'
|
||||
import CSATMessageBubble from './CSATMessageBubble.vue'
|
||||
import { TypingIndicator } from '@shared-ui/components/TypingIndicator'
|
||||
import { Spinner } from '@shared-ui/components/ui/spinner'
|
||||
import { containsQuoteMarkers } from '@shared-ui/utils/quotedContent.js'
|
||||
|
||||
const extendedCssProperties = [...allowedCssProperties, 'transform', 'transform-origin']
|
||||
|
||||
@@ -187,9 +188,7 @@ const getMessageTime = (timestamp) => {
|
||||
return useRelativeTime(new Date(timestamp)).value
|
||||
}
|
||||
|
||||
const hasQuotedContent = (content) => {
|
||||
return content && content.includes('<blockquote')
|
||||
}
|
||||
const hasQuotedContent = (content) => containsQuoteMarkers(content)
|
||||
|
||||
const isQuotedTextVisible = (messageUuid) => {
|
||||
return quotedTextState.value[messageUuid] || false
|
||||
|
||||
@@ -296,7 +296,21 @@
|
||||
}
|
||||
|
||||
.hide-quoted-text {
|
||||
blockquote {
|
||||
// Selectors below use attribute-suffix matching because vue-letter
|
||||
// prefixes every id/class with a random "msg_XYZ_" at render time, so
|
||||
// raw "#appendonsend" would never match.
|
||||
// Outlook/Hotmail emit a marker with the quoted history as trailing
|
||||
// siblings (no wrapping element), so we hide the marker AND everything
|
||||
// after it. Outlook always top-posts, so trailing siblings are quote.
|
||||
blockquote,
|
||||
[id$="_divRplyFwdMsg"],
|
||||
[id$="_divRplyFwdMsg"] ~ *,
|
||||
[id$="_appendonsend"],
|
||||
[id$="_appendonsend"] ~ *,
|
||||
[id$="_OLK_SRC_BODY_SECTION"],
|
||||
[id$="_OLK_SRC_BODY_SECTION"] ~ *,
|
||||
[class*="_OutlookMessageHeader"],
|
||||
[class*="_OutlookMessageHeader"] ~ * {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Runs against the RAW HTML stored on the message - before vue-letter
|
||||
// renders. vue-letter prefixes every id/class with a random "msg_XYZ_" at
|
||||
// render time, so any CSS that needs to match the rendered DOM must use
|
||||
// attribute-suffix selectors (see .hide-quoted-text in main.scss).
|
||||
//
|
||||
// Scope: <blockquote> covers Gmail/Apple/Thunderbird. The id/class markers
|
||||
// below cover the Microsoft variants (Outlook desktop/web/mac, Hotmail).
|
||||
export const QUOTE_MARKERS = [
|
||||
'<blockquote',
|
||||
'id="divRplyFwdMsg"',
|
||||
'id="appendonsend"',
|
||||
'id="OLK_SRC_BODY_SECTION"',
|
||||
'class="OutlookMessageHeader"'
|
||||
]
|
||||
|
||||
export const containsQuoteMarkers = (html) => {
|
||||
if (!html) return false
|
||||
return QUOTE_MARKERS.some((marker) => html.includes(marker))
|
||||
}
|
||||
Reference in New Issue
Block a user