fix: Fix the issue with context menu for right click on images and videos (#11114)

This pull request includes changes to various components adding a
`skip-context-menu` class to ensure the system context menu opens
instead of the Chatwoot message one

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Shivam Mishra
2025-03-20 08:39:44 +05:30
committed by GitHub
parent 3a4249da11
commit 57a571ea87
6 changed files with 187 additions and 180 deletions

View File

@@ -13,6 +13,9 @@ const attachment = computed(() => {
<template> <template>
<BaseBubble class="bg-transparent" data-bubble-name="audio"> <BaseBubble class="bg-transparent" data-bubble-name="audio">
<AudioChip :attachment="attachment" class="p-2 text-n-slate-12" /> <AudioChip
:attachment="attachment"
class="p-2 text-n-slate-12 skip-context-menu"
/>
</BaseBubble> </BaseBubble>
</template> </template>

View File

@@ -56,6 +56,7 @@ const downloadAttachment = async () => {
</div> </div>
<div v-else class="relative group rounded-lg overflow-hidden"> <div v-else class="relative group rounded-lg overflow-hidden">
<img <img
class="skip-context-menu"
:src="attachment.dataUrl" :src="attachment.dataUrl"
:width="attachment.width" :width="attachment.width"
:height="attachment.height" :height="attachment.height"
@@ -63,8 +64,9 @@ const downloadAttachment = async () => {
@error="handleError" @error="handleError"
/> />
<div <div
class="inset-0 p-2 absolute bg-gradient-to-tl from-n-slate-12/30 dark:from-n-slate-1/50 via-transparent to-transparent hidden group-hover:flex items-end justify-end gap-1.5" class="inset-0 p-2 pointer-events-none absolute bg-gradient-to-tl from-n-slate-12/30 dark:from-n-slate-1/50 via-transparent to-transparent hidden group-hover:flex"
> />
<div class="absolute right-2 bottom-2 hidden group-hover:flex gap-2">
<Button xs solid slate icon="i-lucide-expand" class="opacity-60" /> <Button xs solid slate icon="i-lucide-expand" class="opacity-60" />
<Button <Button
xs xs

View File

@@ -41,13 +41,13 @@ const onVideoLoadError = () => {
<div v-if="content" v-dompurify-html="formattedContent" class="mb-2" /> <div v-if="content" v-dompurify-html="formattedContent" class="mb-2" />
<img <img
v-if="!hasImgStoryError" v-if="!hasImgStoryError"
class="rounded-lg max-w-80" class="rounded-lg max-w-80 skip-context-menu"
:src="attachment.dataUrl" :src="attachment.dataUrl"
@error="onImageLoadError" @error="onImageLoadError"
/> />
<video <video
v-else-if="!hasVideoStoryError" v-else-if="!hasVideoStoryError"
class="rounded-lg max-w-80" class="rounded-lg max-w-80 skip-context-menu"
controls controls
:src="attachment.dataUrl" :src="attachment.dataUrl"
@error="onVideoLoadError" @error="onVideoLoadError"

View File

@@ -35,13 +35,13 @@ const isReel = computed(() => {
<div class="relative group rounded-lg overflow-hidden"> <div class="relative group rounded-lg overflow-hidden">
<div <div
v-if="isReel" v-if="isReel"
class="absolute p-2 flex items-start justify-end right-0" class="absolute p-2 flex items-start justify-end right-0 pointer-events-none"
> >
<Icon icon="i-lucide-instagram" class="text-white shadow-lg" /> <Icon icon="i-lucide-instagram" class="text-white shadow-lg" />
</div> </div>
<video <video
controls controls
class="rounded-lg" class="rounded-lg skip-context-menu"
:src="attachment.dataUrl" :src="attachment.dataUrl"
:class="{ :class="{
'max-w-48': isReel, 'max-w-48': isReel,

View File

@@ -36,7 +36,7 @@ const handleError = () => {
</div> </div>
<img <img
v-else v-else
class="object-cover w-full h-full" class="object-cover w-full h-full skip-context-menu"
:src="attachment.dataUrl" :src="attachment.dataUrl"
@error="handleError" @error="handleError"
/> />

View File

@@ -166,188 +166,190 @@ onMounted(() => {
</script> </script>
<template> <template>
<woot-modal <Teleport to="body">
v-model:show="show" <woot-modal
full-width v-model:show="show"
:show-close-button="false" full-width
:on-close="onClose" :show-close-button="false"
> :on-close="onClose"
<div
class="bg-n-background flex flex-col h-[inherit] w-[inherit] overflow-hidden select-none"
@click="onClose"
> >
<header <div
class="z-10 flex items-center justify-between w-full h-16 px-6 py-2 bg-n-background border-b border-n-weak" class="bg-n-background flex flex-col h-[inherit] w-[inherit] overflow-hidden select-none"
@click.stop @click="onClose"
> >
<div <header
v-if="senderDetails" class="z-10 flex items-center justify-between w-full h-16 px-6 py-2 bg-n-background border-b border-n-weak"
class="flex items-center min-w-[15rem] shrink-0" @click.stop
> >
<Thumbnail
v-if="senderDetails.avatar"
:username="senderDetails.name"
:src="senderDetails.avatar"
class="flex-shrink-0"
/>
<div class="flex flex-col ml-2 rtl:ml-0 rtl:mr-2 overflow-hidden">
<h3 class="text-base leading-5 m-0 font-medium">
<span
class="overflow-hidden text-n-slate-12 whitespace-nowrap text-ellipsis"
>
{{ senderDetails.name }}
</span>
</h3>
<span
class="text-xs text-n-slate-11 whitespace-nowrap text-ellipsis"
>
{{ readableTime }}
</span>
</div>
</div>
<div
class="flex-1 mx-2 px-2 truncate text-sm font-medium text-center text-n-slate-12"
>
<span v-dompurify-html="fileNameFromDataUrl" class="truncate" />
</div>
<div class="flex items-center gap-2 ml-2 shrink-0">
<NextButton
v-if="isImage"
icon="i-lucide-zoom-in"
slate
ghost
@click="onZoom(0.1)"
/>
<NextButton
v-if="isImage"
icon="i-lucide-zoom-out"
slate
ghost
@click="onZoom(-0.1)"
/>
<NextButton
v-if="isImage"
icon="i-lucide-rotate-ccw"
slate
ghost
@click="onRotate('counter-clockwise')"
/>
<NextButton
v-if="isImage"
icon="i-lucide-rotate-cw"
slate
ghost
@click="onRotate('clockwise')"
/>
<NextButton
icon="i-lucide-download"
slate
ghost
:is-loading="isDownloading"
:disabled="isDownloading"
@click="onClickDownload"
/>
<NextButton icon="i-lucide-x" slate ghost @click="onClose" />
</div>
</header>
<main class="flex items-stretch flex-1 h-full overflow-hidden">
<div class="flex items-center justify-center w-16 shrink-0">
<NextButton
v-if="hasMoreThanOneAttachment"
icon="i-lucide-chevron-left"
class="z-10"
blue
faded
lg
:disabled="activeImageIndex === 0"
@click.stop="
onClickChangeAttachment(
allAttachments[activeImageIndex - 1],
activeImageIndex - 1
)
"
/>
</div>
<div class="flex-1 flex items-center justify-center overflow-hidden">
<div <div
v-if="isImage" v-if="senderDetails"
:style="imageWrapperStyle" class="flex items-center min-w-[15rem] shrink-0"
class="flex items-center justify-center origin-center"
:class="{
// Adjust dimensions when rotated 90/270 degrees to maintain visibility
// and prevent image from overflowing container in different aspect ratios
'w-[calc(100dvh-8rem)] h-[calc(100dvw-7rem)]':
activeImageRotation % 180 !== 0,
'size-full': activeImageRotation % 180 === 0,
}"
> >
<img <Thumbnail
ref="imageRef" v-if="senderDetails.avatar"
:key="activeAttachment.message_id" :username="senderDetails.name"
:src="activeAttachment.data_url" :src="senderDetails.avatar"
:style="imageStyle" class="flex-shrink-0"
class="max-h-full max-w-full object-contain duration-100 ease-in-out transform select-none" />
@click.stop <div class="flex flex-col ml-2 rtl:ml-0 rtl:mr-2 overflow-hidden">
@dblclick.stop="onDoubleClickZoomImage" <h3 class="text-base leading-5 m-0 font-medium">
@wheel.prevent.stop="onWheelImageZoom" <span
@mousemove="onMouseMove" class="overflow-hidden text-n-slate-12 whitespace-nowrap text-ellipsis"
@mouseleave="onMouseLeave" >
{{ senderDetails.name }}
</span>
</h3>
<span
class="text-xs text-n-slate-11 whitespace-nowrap text-ellipsis"
>
{{ readableTime }}
</span>
</div>
</div>
<div
class="flex-1 mx-2 px-2 truncate text-sm font-medium text-center text-n-slate-12"
>
<span v-dompurify-html="fileNameFromDataUrl" class="truncate" />
</div>
<div class="flex items-center gap-2 ml-2 shrink-0">
<NextButton
v-if="isImage"
icon="i-lucide-zoom-in"
slate
ghost
@click="onZoom(0.1)"
/>
<NextButton
v-if="isImage"
icon="i-lucide-zoom-out"
slate
ghost
@click="onZoom(-0.1)"
/>
<NextButton
v-if="isImage"
icon="i-lucide-rotate-ccw"
slate
ghost
@click="onRotate('counter-clockwise')"
/>
<NextButton
v-if="isImage"
icon="i-lucide-rotate-cw"
slate
ghost
@click="onRotate('clockwise')"
/>
<NextButton
icon="i-lucide-download"
slate
ghost
:is-loading="isDownloading"
:disabled="isDownloading"
@click="onClickDownload"
/>
<NextButton icon="i-lucide-x" slate ghost @click="onClose" />
</div>
</header>
<main class="flex items-stretch flex-1 h-full overflow-hidden">
<div class="flex items-center justify-center w-16 shrink-0">
<NextButton
v-if="hasMoreThanOneAttachment"
icon="i-lucide-chevron-left"
class="z-10"
blue
faded
lg
:disabled="activeImageIndex === 0"
@click.stop="
onClickChangeAttachment(
allAttachments[activeImageIndex - 1],
activeImageIndex - 1
)
"
/> />
</div> </div>
<video <div class="flex-1 flex items-center justify-center overflow-hidden">
v-if="isVideo" <div
:key="activeAttachment.message_id" v-if="isImage"
:src="activeAttachment.data_url" :style="imageWrapperStyle"
controls class="flex items-center justify-center origin-center"
playsInline :class="{
class="max-h-full max-w-full object-contain" // Adjust dimensions when rotated 90/270 degrees to maintain visibility
@click.stop // and prevent image from overflowing container in different aspect ratios
/> 'w-[calc(100dvh-8rem)] h-[calc(100dvw-7rem)]':
activeImageRotation % 180 !== 0,
'size-full': activeImageRotation % 180 === 0,
}"
>
<img
ref="imageRef"
:key="activeAttachment.message_id"
:src="activeAttachment.data_url"
:style="imageStyle"
class="max-h-full max-w-full object-contain duration-100 ease-in-out transform select-none"
@click.stop
@dblclick.stop="onDoubleClickZoomImage"
@wheel.prevent.stop="onWheelImageZoom"
@mousemove="onMouseMove"
@mouseleave="onMouseLeave"
/>
</div>
<audio <video
v-if="isAudio" v-if="isVideo"
:key="activeAttachment.message_id" :key="activeAttachment.message_id"
controls :src="activeAttachment.data_url"
class="w-full max-w-md" controls
@click.stop playsInline
> class="max-h-full max-w-full object-contain"
<source :src="`${activeAttachment.data_url}?t=${Date.now()}`" /> @click.stop
</audio> />
</div>
<div class="flex items-center justify-center w-16 shrink-0"> <audio
<NextButton v-if="isAudio"
v-if="hasMoreThanOneAttachment" :key="activeAttachment.message_id"
icon="i-lucide-chevron-right" controls
class="z-10" class="w-full max-w-md"
blue @click.stop
faded >
lg <source :src="`${activeAttachment.data_url}?t=${Date.now()}`" />
:disabled="activeImageIndex === allAttachments.length - 1" </audio>
@click.stop=" </div>
onClickChangeAttachment(
allAttachments[activeImageIndex + 1],
activeImageIndex + 1
)
"
/>
</div>
</main>
<footer <div class="flex items-center justify-center w-16 shrink-0">
class="z-10 flex items-center justify-center h-12 border-t border-n-weak" <NextButton
> v-if="hasMoreThanOneAttachment"
<div icon="i-lucide-chevron-right"
class="rounded-md flex items-center justify-center px-3 py-1 bg-n-slate-3 text-n-slate-12 text-sm font-medium" class="z-10"
blue
faded
lg
:disabled="activeImageIndex === allAttachments.length - 1"
@click.stop="
onClickChangeAttachment(
allAttachments[activeImageIndex + 1],
activeImageIndex + 1
)
"
/>
</div>
</main>
<footer
class="z-10 flex items-center justify-center h-12 border-t border-n-weak"
> >
{{ `${activeImageIndex + 1} / ${allAttachments.length}` }} <div
</div> class="rounded-md flex items-center justify-center px-3 py-1 bg-n-slate-3 text-n-slate-12 text-sm font-medium"
</footer> >
</div> {{ `${activeImageIndex + 1} / ${allAttachments.length}` }}
</woot-modal> </div>
</footer>
</div>
</woot-modal>
</Teleport>
</template> </template>