chore: Clean up

This commit is contained in:
iamsivin
2025-05-13 16:43:45 +05:30
parent 32158da454
commit 12ace33254
7 changed files with 61 additions and 327 deletions
@@ -1,28 +0,0 @@
<!--
* Preserves RTL/LTR context when teleporting content
* Ensures direction-specific classes (ltr:tailwind-class, rtl:tailwind-class) work correctly
* when content is teleported outside the app's container with [dir] attribute
-->
<script setup>
import { computed } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
defineProps({
to: {
type: String,
default: 'body',
},
});
const isRTL = useMapGetter('accounts/isRTL');
const contentDirection = computed(() => (isRTL.value ? 'rtl' : 'ltr'));
</script>
<template>
<Teleport :to="to">
<div :dir="contentDirection">
<slot />
</div>
</Teleport>
</template>
@@ -1,12 +1,5 @@
<script setup>
import {
onMounted,
computed,
ref,
toRefs,
useTemplateRef,
onUnmounted,
} from 'vue';
import { onMounted, computed, ref, toRefs, useTemplateRef } from 'vue';
import { useTimeoutFn } from '@vueuse/core';
import { provideMessageContext } from './provider.js';
import { useTrack } from 'dashboard/composables';
@@ -26,7 +19,6 @@ import {
MESSAGE_STATUS,
CONTENT_TYPES,
} from './constants';
import { useScrollLock } from '@vueuse/core';
import Avatar from 'next/avatar/Avatar.vue';
@@ -138,8 +130,6 @@ const props = defineProps({
sourceId: { type: String, default: '' }, // eslint-disable-line vue/no-unused-properties
});
const conversationPanelElement = document.querySelector('.conversation-panel');
const conversationPanelScrollLock = useScrollLock(conversationPanelElement);
const contextMenuPosition = ref({});
const showBackgroundHighlight = ref(false);
const showContextMenu = ref(false);
@@ -378,9 +368,6 @@ const shouldRenderMessage = computed(() => {
});
function openContextMenu(e) {
// Lock scroll to prevent scrolling when context menu is open
conversationPanelScrollLock.value = true;
// Close forward modal, when opening context menu
emailBubbleRef.value?.closeForwardModal();
@@ -403,8 +390,6 @@ function openContextMenu(e) {
}
function closeContextMenu() {
// Unlock scroll when context menu is closed
conversationPanelScrollLock.value = false;
showContextMenu.value = false;
contextMenuPosition.value = { x: null, y: null };
}
@@ -467,9 +452,6 @@ const openForwardModal = ({ x, y }) => {
};
onMounted(setupHighlightTimer);
onUnmounted(() => {
conversationPanelScrollLock.value = false;
});
provideMessageContext({
...toRefs(props),
@@ -1,16 +1,8 @@
<script setup>
import {
computed,
useTemplateRef,
onUnmounted,
ref,
onMounted,
reactive,
} from 'vue';
import { computed, useTemplateRef, ref, onMounted, reactive } from 'vue';
import { Letter } from 'vue-letter';
import { allowedCssProperties } from 'lettersanitizer';
import { useWindowSize, useToggle } from '@vueuse/core';
import { useScrollLock } from '@vueuse/core';
import { useToggle } from '@vueuse/core';
import Icon from 'next/icon/Icon.vue';
import { EmailQuoteExtractor } from './removeReply.js';
@@ -20,13 +12,12 @@ import AttachmentChips from 'next/message/chips/AttachmentChips.vue';
import EmailMeta from './EmailMeta.vue';
import TranslationToggle from 'dashboard/components-next/message/TranslationToggle.vue';
import ForwardMessageForm from 'dashboard/components-next/message/forwardMessage/ForwardMessage.vue';
import CustomTeleport from 'dashboard/components-next/CustomTeleport.vue';
import ContextMenu from 'dashboard/components/ui/ContextMenu.vue';
import { useMessageContext } from '../../provider.js';
import { useInbox } from 'dashboard/composables/useInbox';
import { MESSAGE_TYPES } from 'next/message/constants.js';
import { useTranslations } from 'dashboard/composables/useTranslations';
import { calculatePosition } from 'dashboard/helper/position.js';
const { id, content, contentAttributes, attachments, messageType } =
useMessageContext();
@@ -43,9 +34,6 @@ const contentContainer = useTemplateRef('contentContainer');
const [showForwardMessageModal, toggleForwardModal] = useToggle();
const forwardFormPosition = reactive({ top: 0, right: 0 });
const conversationPanelElement = document.querySelector('.conversation-panel');
const conversationPanelScrollLock = useScrollLock(conversationPanelElement);
const { width: windowWidth, height: windowHeight } = useWindowSize();
onMounted(() => {
isExpandable.value = contentContainer.value?.scrollHeight > 400;
@@ -120,45 +108,16 @@ const handleSeeOriginal = () => {
const closeForwardModal = () => {
toggleForwardModal(false);
conversationPanelScrollLock.value = false;
forwardFormPosition.x = 0;
forwardFormPosition.y = 0;
};
const openForwardModal = ({ x, y }) => {
// Lock conversation panel scroll
conversationPanelScrollLock.value = true;
// Form dimensions
const [formWidth, formHeight] = [672, 500];
const { value: winWidth } = windowWidth;
const { value: winHeight } = windowHeight;
// Position calculation
if (x && y) {
// Calculate position based on click location of context menu forward button
const { left, top } = calculatePosition(
x,
y,
formWidth,
formHeight,
winWidth,
winHeight
);
forwardFormPosition.top = top;
forwardFormPosition.right = winWidth - left - formWidth;
} else {
// Center the form when no trigger event
forwardFormPosition.top = Math.max(0, (winHeight - formHeight) / 2);
forwardFormPosition.right = Math.max(0, (winWidth - formWidth) / 2);
}
forwardFormPosition.x = x;
forwardFormPosition.y = y;
toggleForwardModal(true);
};
onUnmounted(() => {
conversationPanelScrollLock.value = false;
});
defineExpose({
openForwardModal,
closeForwardModal,
@@ -266,21 +225,22 @@ defineExpose({
<AttachmentChips :attachments="attachments" class="gap-1" />
</section>
<CustomTeleport v-if="showForwardMessageModal">
<ContextMenu
v-if="showForwardMessageModal"
:x="forwardFormPosition.x"
:y="forwardFormPosition.y"
:lock-scroll-element="conversationPanelElement"
@close="closeForwardModal"
>
<ForwardMessageForm
:message="contentAttributes?.email"
:content="content"
:inbox="inbox"
:attachments="attachments"
:message-id="id"
class="fixed z-50 skip-context-menu"
:style="{
top: `${forwardFormPosition.top}px`,
right: `${forwardFormPosition.right}px`,
}"
@close="closeForwardModal"
/>
</CustomTeleport>
</ContextMenu>
</BaseBubble>
</template>
@@ -1,22 +1,49 @@
<script setup>
import { computed, onMounted, nextTick, useTemplateRef } from 'vue';
import { useWindowSize, useElementBounding } from '@vueuse/core';
import { calculatePosition } from 'dashboard/helper/position.js';
import {
computed,
onMounted,
nextTick,
onUnmounted,
useTemplateRef,
} from 'vue';
import { useWindowSize, useElementBounding, useScrollLock } from '@vueuse/core';
import TeleportWithDirection from 'dashboard/components-next/TeleportWithDirection.vue';
const props = defineProps({
x: { type: Number, default: 0 },
y: { type: Number, default: 0 },
lockScrollElement: { type: [HTMLElement, null], default: null },
});
const emit = defineEmits(['close']);
const menuRef = useTemplateRef('menuRef');
const scrollLockElement = computed(() => props.lockScrollElement);
const isLocked = useScrollLock(scrollLockElement);
const { width: windowWidth, height: windowHeight } = useWindowSize();
const { width: menuWidth, height: menuHeight } = useElementBounding(menuRef);
const calculatePosition = (x, y, menuW, menuH, windowW, windowH) => {
const PADDING = 16;
// Initial position
let left = x;
let top = y;
// Boundary checks
const isOverflowingRight = left + menuW > windowW - PADDING;
const isOverflowingBottom = top + menuH > windowH - PADDING;
// Adjust position if overflowing
if (isOverflowingRight) left = windowW - menuW - PADDING;
if (isOverflowingBottom) top = windowH - menuH - PADDING;
return {
left: Math.max(PADDING, left),
top: Math.max(PADDING, top),
};
};
const position = computed(() => {
if (!menuRef.value) return { top: `${props.y}px`, left: `${props.x}px` };
@@ -36,8 +63,18 @@ const position = computed(() => {
});
onMounted(() => {
isLocked.value = true;
nextTick(() => menuRef.value?.focus());
});
const handleClose = () => {
isLocked.value = false;
emit('close');
};
onUnmounted(() => {
isLocked.value = false;
});
</script>
<template>
@@ -47,7 +84,7 @@ onMounted(() => {
class="fixed outline-none z-[9999] cursor-pointer"
:style="position"
tabindex="0"
@blur="emit('close')"
@blur="handleClose"
>
<slot />
</div>
@@ -1,38 +0,0 @@
/**
* Calculates the optimal position for a popup element to ensure it remains within viewport bounds
*
* @param {number} x - The initial x-coordinate (left position) for the element
* @param {number} y - The initial y-coordinate (top position) for the element
* @param {number} menuW - The width of the element to position
* @param {number} menuH - The height of the element to position
* @param {number} windowW - The width of the viewport
* @param {number} windowH - The height of the viewport
* @param {number} [padding=16] - Minimum padding from viewport edges (in pixels)
* @returns {Object} An object containing the adjusted { left, top } coordinates
*/
export const calculatePosition = (
x,
y,
menuW,
menuH,
windowW,
windowH,
padding = 16
) => {
// Initial position
let left = x;
let top = y;
// Boundary checks
const isOverflowingRight = left + menuW > windowW - padding;
const isOverflowingBottom = top + menuH > windowH - padding;
// Adjust position if overflowing
if (isOverflowingRight) left = windowW - menuW - padding;
if (isOverflowingBottom) top = windowH - menuH - padding;
return {
left: Math.max(padding, left),
top: Math.max(padding, top),
};
};
@@ -1,183 +0,0 @@
import { calculatePosition } from '../position';
describe('calculatePosition', () => {
const WINDOW_WIDTH = 1024;
const WINDOW_HEIGHT = 768;
const MENU_WIDTH = 400;
const MENU_HEIGHT = 300;
const DEFAULT_PADDING = 16;
test('returns the original position when element fits within viewport', () => {
const x = 100;
const y = 100;
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
expect(result).toEqual({ left: x, top: y });
});
test('adjusts position when overflowing right edge', () => {
const x = WINDOW_WIDTH - 100; // Too far right
const y = 100;
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
const expectedLeft = WINDOW_WIDTH - MENU_WIDTH - DEFAULT_PADDING;
expect(result).toEqual({ left: expectedLeft, top: y });
});
test('adjusts position when overflowing bottom edge', () => {
const x = 100;
const y = WINDOW_HEIGHT - 100; // Too far down
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
const expectedTop = WINDOW_HEIGHT - MENU_HEIGHT - DEFAULT_PADDING;
expect(result).toEqual({ left: x, top: expectedTop });
});
test('adjusts position when overflowing both right and bottom edges', () => {
const x = WINDOW_WIDTH - 100; // Too far right
const y = WINDOW_HEIGHT - 100; // Too far down
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
const expectedLeft = WINDOW_WIDTH - MENU_WIDTH - DEFAULT_PADDING;
const expectedTop = WINDOW_HEIGHT - MENU_HEIGHT - DEFAULT_PADDING;
expect(result).toEqual({ left: expectedLeft, top: expectedTop });
});
test('ensures minimum padding from left edge', () => {
const x = -50; // Position would be off-screen to the left
const y = 100;
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
expect(result).toEqual({ left: DEFAULT_PADDING, top: y });
});
test('ensures minimum padding from top edge', () => {
const x = 100;
const y = -50; // Position would be off-screen at the top
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
expect(result).toEqual({ left: x, top: DEFAULT_PADDING });
});
test('handles case when element is larger than viewport width', () => {
const x = 100;
const y = 100;
const largeWidth = WINDOW_WIDTH + 200; // Wider than window
const result = calculatePosition(
x,
y,
largeWidth,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
// Should position at minimum padding
expect(result).toEqual({ left: DEFAULT_PADDING, top: y });
});
test('handles case when element is larger than viewport height', () => {
const x = 100;
const y = 100;
const largeHeight = WINDOW_HEIGHT + 200; // Taller than window
const result = calculatePosition(
x,
y,
MENU_WIDTH,
largeHeight,
WINDOW_WIDTH,
WINDOW_HEIGHT
);
// Should position at minimum padding
expect(result).toEqual({ left: x, top: DEFAULT_PADDING });
});
test('accepts custom padding value', () => {
const x = WINDOW_WIDTH - 100;
const y = WINDOW_HEIGHT - 100;
const customPadding = 32;
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT,
customPadding
);
const expectedLeft = WINDOW_WIDTH - MENU_WIDTH - customPadding;
const expectedTop = WINDOW_HEIGHT - MENU_HEIGHT - customPadding;
expect(result).toEqual({ left: expectedLeft, top: expectedTop });
});
test('ensures custom minimum padding from edges', () => {
const x = -50;
const y = -50;
const customPadding = 32;
const result = calculatePosition(
x,
y,
MENU_WIDTH,
MENU_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT,
customPadding
);
expect(result).toEqual({ left: customPadding, top: customPadding });
});
});
@@ -62,6 +62,9 @@ export default {
getAccount: 'accounts/getAccount',
currentAccountId: 'getCurrentAccountId',
}),
conversationPanelElement() {
return document.querySelector('.conversation-panel');
},
plainTextContent() {
return this.getPlainText(this.messageContent);
},
@@ -197,6 +200,7 @@ export default {
v-if="isOpen && !isCannedResponseModalOpen"
:x="contextMenuPosition.x"
:y="contextMenuPosition.y"
:lock-scroll-element="conversationPanelElement"
@close="handleClose"
>
<div class="menu-container">