Skip to content

Commit e5e1aef

Browse files
committed
fix(chat): exclude contact cards from FilePreviewsWrapper
- contact card shares use the same 'file' key as regular files, but rendered with ContactCard.vue - they are not excluded from richParameters and rendered via NcRichText Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
1 parent 9645d6b commit e5e1aef

5 files changed

Lines changed: 59 additions & 6 deletions

File tree

src/components/MessagesList/MessagesGroup/Message/MessageItem.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import MessageQuote from '../../../MessageQuote.vue'
1818
import CallButton from '../../../TopBar/CallButton.vue'
1919
import MessageButtonsBar from './MessageButtonsBar/MessageButtonsBar.vue'
2020
import MessageItem from './MessageItem.vue'
21+
import ContactCard from './MessagePart/ContactCard.vue'
2122
import DeckCard from './MessagePart/DeckCard.vue'
2223
import DefaultParameter from './MessagePart/DefaultParameter.vue'
2324
import FilePreview from './MessagePart/FilePreview.vue'
@@ -408,6 +409,26 @@ describe('MessageItem.vue', () => {
408409
expect(wrapper.findComponent(NcRichText).exists()).toBe(false)
409410
})
410411

412+
test('renders contact card (text/vcard) via NcRichText', () => {
413+
const params = {
414+
file: {
415+
id: '123',
416+
type: 'file',
417+
mimetype: 'text/vcard',
418+
name: 'John Doe.vcf',
419+
link: 'https://example.com/John%20Doe.vcf',
420+
},
421+
}
422+
messageProps.message.message = '{file}'
423+
messageProps.message.messageParameters = params
424+
store.dispatch('processMessage', { token: TOKEN, message: messageProps.message })
425+
const wrapper = mountMessage(messageProps)
426+
427+
expect(wrapper.findComponent(NcRichText).exists()).toBe(true)
428+
expect(wrapper.findComponent(ContactCard).exists()).toBe(true)
429+
expect(wrapper.findComponent(FilePreviewsWrapper).exists()).toBe(false)
430+
})
431+
411432
test('renders deck cards', () => {
412433
const params = {
413434
actor: {

src/components/MessagesList/MessagesGroup/Message/MessageItem.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ import { hasTalkFeature } from '../../../../services/CapabilitiesManager.ts'
113113
import { EventBus } from '../../../../services/EventBus.ts'
114114
import { useActorStore } from '../../../../stores/actor.ts'
115115
import { useChatExtrasStore } from '../../../../stores/chatExtras.ts'
116+
import { isFilePreviewParameter } from '../../../../utils/message.ts'
116117
117118
const LocationCard = defineAsyncComponent(() => import('./MessagePart/LocationCard.vue'))
118119
@@ -229,7 +230,7 @@ export default {
229230
token: this.message.token,
230231
},
231232
}
232-
} else if (type === 'file' && mimetype !== 'text/vcard') {
233+
} else if (isFilePreviewParameter(p, this.message.messageParameters[p])) {
233234
// File previews are rendered by FilePreviewsWrapper, skip from richParameters
234235
return
235236
} else if (type === SHARED_ITEM.OBJECT_TYPE.DECK_CARD) {

src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreviewsWrapper.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import type { ChatMessage } from '../../../../../types/index.ts'
99
import { computed } from 'vue'
1010
import FilePreview from './FilePreview.vue'
1111
import { getItemTypeFromMessage } from '../../../../../utils/getItemTypeFromMessage.ts'
12-
import { getFileKeys } from '../../../../../utils/message.ts'
12+
import { getFilePreviewKeys } from '../../../../../utils/message.ts'
1313
1414
const props = defineProps<{
1515
message: ChatMessage
1616
}>()
1717
18-
const fileKeys = computed(() => getFileKeys(props.message))
18+
const fileKeys = computed(() => getFilePreviewKeys(props.message))
1919
2020
/**
2121
* Get referenceId of a file parameter to look up a local preview (if available).

src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@
6666
<MessageQuote v-if="showQuote" :message="message.parent" />
6767

6868
<!-- File previews, rendered as a block on top of the (optional) caption -->
69-
<FilePreviewsWrapper v-if="isFileShare" :message="message" />
69+
<FilePreviewsWrapper v-if="hasFilePreviews" :message="message" />
7070

7171
<!-- Message content / text -->
7272
<NcRichText
73-
v-if="!isFileShareWithoutCaption"
73+
v-if="!isFileShareWithoutCaption || !hasFilePreviews"
7474
:text="renderedMessage"
7575
:arguments="richParameters"
7676
:class="{ 'single-emoji': isSingleEmoji }"
@@ -218,6 +218,7 @@ import { useChatExtrasStore } from '../../../../../stores/chatExtras.ts'
218218
import { usePollsStore } from '../../../../../stores/polls.ts'
219219
import { useUploadStore } from '../../../../../stores/upload.ts'
220220
import { formatDateTime } from '../../../../../utils/formattedTime.ts'
221+
import { getFileKeys, getFilePreviewKeys, isFilePreviewParameter } from '../../../../../utils/message.ts'
221222
import { parseMentions, parseSpecialSymbols } from '../../../../../utils/textParse.ts'
222223
223224
// Regular expression to check for Unicode emojis in message text
@@ -327,13 +328,22 @@ export default {
327328
},
328329
329330
computed: {
331+
hasFilePreviews() {
332+
return getFilePreviewKeys(this.message).length > 0
333+
},
334+
330335
showQuote() {
331336
return !!this.message.parent && this.message.parent.id !== this.threadId
332337
},
333338
334339
renderedMessage() {
335340
// File previews are rendered separately, as a block on top of this text (see FilePreviewsWrapper)
336-
if (this.isLocationMessageWithName) {
341+
if (this.isFileShare && !this.isFileShareWithoutCaption) {
342+
// Contact cards with mimetype 'text/vcard' are rendered here.
343+
// In case of caption present, placeholder should be put before it.
344+
const vcardKey = getFileKeys(this.message).find((key) => !isFilePreviewParameter(key, this.message.messageParameters[key]))
345+
return vcardKey ? `{${vcardKey}}\n\n${this.message.message}` : this.message.message
346+
} else if (this.isLocationMessageWithName) {
337347
return this.message.message + '\n\n' + this.message.messageParameters.object.name
338348
} else {
339349
return this.message.message

src/utils/message.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,27 @@ export function getFileKeys(message: Pick<ChatMessage, 'messageParameters'>) {
197197
return Object.keys(message.messageParameters ?? {}).filter((key) => FILE_KEY_REGEX.test(key))
198198
}
199199

200+
/**
201+
* Checks whether a message parameter should be rendered as a preview in a FilePreviewsWrapper
202+
* (Contact cards with mimetype 'text/vcard' are rendered separately)
203+
*
204+
* @param key key of the message parameter ('file', 'file-1', …)
205+
* @param parameter the message parameter itself
206+
* @param parameter.mimetype the parameter's mimetype, if any
207+
*/
208+
export function isFilePreviewParameter(key: string, parameter: { mimetype?: string }): boolean {
209+
return FILE_KEY_REGEX.test(key) && parameter.mimetype !== 'text/vcard'
210+
}
211+
212+
/**
213+
* Returns keys of files shared with the message to be rendered with a FilePreviewsWrapper
214+
*
215+
* @param message Chat message (or an object with messageParameters)
216+
*/
217+
export function getFilePreviewKeys(message: Pick<ChatMessage, 'messageParameters'>) {
218+
return getFileKeys(message).filter((key) => isFilePreviewParameter(key, message.messageParameters[key]))
219+
}
220+
200221
/**
201222
* Returns whether the given message shares a file (has a rich object parameter with a key starting with 'file').
202223
*

0 commit comments

Comments
 (0)