chore: Replace multiple mixins to simgle use composables

This commit is contained in:
Fayaz Ahmed
2024-08-05 15:41:45 +05:30
parent b4b308336f
commit 0bd62cf33f
10 changed files with 488 additions and 0 deletions
@@ -0,0 +1,116 @@
import { computed } from 'vue';
import { utcToZonedTime } from 'date-fns-tz';
import { isTimeAfter } from 'shared/helpers/DateHelper';
/**
* Composable for handling availability-related computations.
* @param {Object} i18n - Vue i18n instance for translations.
* @returns {Object} An object containing computed properties and methods for availability management.
*/
export function useAvailability(i18n) {
const channelConfig = computed(() => window.chatwootWebChannel);
const replyTime = computed(() => window.chatwootWebChannel.replyTime);
const replyTimeStatus = computed(() => {
switch (replyTime.value) {
case 'in_a_few_minutes':
return i18n.t('REPLY_TIME.IN_A_FEW_MINUTES');
case 'in_a_few_hours':
return i18n.t('REPLY_TIME.IN_A_FEW_HOURS');
case 'in_a_day':
return i18n.t('REPLY_TIME.IN_A_DAY');
default:
return i18n.t('REPLY_TIME.IN_A_FEW_HOURS');
}
});
const replyWaitMessage = computed(() => {
const { workingHoursEnabled } = channelConfig.value;
if (workingHoursEnabled) {
return isOnline.value
? replyTimeStatus.value
: `${i18n.t('REPLY_TIME.BACK_IN')} ${timeLeftToBackInOnline.value}`;
}
return isOnline.value
? replyTimeStatus.value
: i18n.t('TEAM_AVAILABILITY.OFFLINE');
});
const outOfOfficeMessage = computed(
() => channelConfig.value.outOfOfficeMessage
);
const currentDayAvailability = computed(() => {
const { utcOffset } = channelConfig.value;
const dayOfTheWeek = getDateWithOffset(utcOffset).getDay();
const [workingHourConfig = {}] = channelConfig.value.workingHours.filter(
workingHour => workingHour.day_of_week === dayOfTheWeek
);
return {
closedAllDay: workingHourConfig.closed_all_day,
openHour: workingHourConfig.open_hour,
openMinute: workingHourConfig.open_minutes,
closeHour: workingHourConfig.close_hour,
closeMinute: workingHourConfig.close_minutes,
openAllDay: workingHourConfig.open_all_day,
};
});
const isInBetweenTheWorkingHours = computed(() => {
const {
openHour,
openMinute,
closeHour,
closeMinute,
closedAllDay,
openAllDay,
} = currentDayAvailability.value;
if (openAllDay) {
return true;
}
if (closedAllDay) {
return false;
}
const { utcOffset } = channelConfig.value;
const today = getDateWithOffset(utcOffset);
const currentHours = today.getHours();
const currentMinutes = today.getMinutes();
const isAfterStartTime = isTimeAfter(
currentHours,
currentMinutes,
openHour,
openMinute
);
const isBeforeEndTime = isTimeAfter(
closeHour,
closeMinute,
currentHours,
currentMinutes
);
return isAfterStartTime && isBeforeEndTime;
});
const isInBusinessHours = computed(() => {
const { workingHoursEnabled } = channelConfig.value;
return workingHoursEnabled ? isInBetweenTheWorkingHours.value : true;
});
const getDateWithOffset = utcOffset => {
return utcToZonedTime(new Date().toISOString(), utcOffset);
};
return {
channelConfig,
replyTime,
replyTimeStatus,
replyWaitMessage,
outOfOfficeMessage,
currentDayAvailability,
isInBetweenTheWorkingHours,
isInBusinessHours,
getDateWithOffset,
};
}
@@ -0,0 +1,32 @@
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { CAMPAIGN_TYPES } from '../constants/campaign';
/**
* Composable for handling campaign-related computations.
* @returns {Object} An object containing computed properties for campaign types.
*/
export function useCampaign() {
const route = useRoute();
const campaignType = computed(() => {
const campaignTypeMap = {
ongoing_campaigns: CAMPAIGN_TYPES.ONGOING,
one_off: CAMPAIGN_TYPES.ONE_OFF,
};
return campaignTypeMap[route.name];
});
const isOngoingType = computed(
() => campaignType.value === CAMPAIGN_TYPES.ONGOING
);
const isOneOffType = computed(
() => campaignType.value === CAMPAIGN_TYPES.ONE_OFF
);
return {
campaignType,
isOngoingType,
isOneOffType,
};
}
@@ -0,0 +1,35 @@
import { computed } from 'vue';
import { useStore } from 'vuex';
/**
* Composable for handling dark mode preferences and utility functions.
* @returns {Object} An object containing computed properties and methods for dark mode management.
*/
export function useDarkMode() {
const store = useStore();
const darkMode = computed(() => store.getters['appConfig/darkMode']);
const prefersDarkMode = computed(() => {
const isOSOnDarkMode =
darkMode.value === 'auto' &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
return isOSOnDarkMode || darkMode.value === 'dark';
});
const dm = (light, dark) => {
if (darkMode.value === 'light') {
return light;
}
if (darkMode.value === 'dark') {
return dark;
}
return `${light} ${dark}`;
};
return {
darkMode,
prefersDarkMode,
dm,
};
}
@@ -0,0 +1,16 @@
import { computed } from 'vue';
/**
* Composable for handling hook-related computations.
* @param {Object} integration - The integration object containing hook information.
* @returns {Object} An object containing computed properties for hook types and connections.
*/
export function useHook(integration) {
const isHookTypeInbox = computed(() => integration.hook_type === 'inbox');
const hasConnectedHooks = computed(() => !!integration.hooks.length);
return {
isHookTypeInbox,
hasConnectedHooks,
};
}
@@ -0,0 +1,67 @@
import { onMounted, onBeforeUnmount } from 'vue';
import { isActiveElementTypeable, isEscape } from '../helpers/KeyboardHelpers';
import { createKeybindingsHandler } from 'tinykeys';
// Store that stores the handler globally, and only gets reset on reload
const taggedHandlers = [];
/**
* Composable for handling keyboard event listeners.
* @returns {Object} An object containing methods for managing keyboard event listeners.
*/
export function useKeyboardEventListener() {
let handlerIndex = -1;
const wrapEventsInKeybindingsHandler = events => {
const wrappedEvents = {};
Object.keys(events).forEach(eventName => {
wrappedEvents[eventName] = keydownWrapper(events[eventName]);
});
return wrappedEvents;
};
const keydownWrapper = handler => {
return e => {
const actionToPerform =
typeof handler === 'function' ? handler : handler.action;
const allowOnFocusedInput =
typeof handler === 'function' ? false : handler.allowOnFocusedInput;
const isTypeable = isActiveElementTypeable(e);
if (isTypeable) {
if (isEscape(e)) {
e.target.blur();
}
if (!allowOnFocusedInput) return;
}
actionToPerform(e);
};
};
const addEventHandler = events => {
const wrappedEvents = wrapEventsInKeybindingsHandler(events);
const keydownHandler = createKeybindingsHandler(wrappedEvents);
handlerIndex = taggedHandlers.push(keydownHandler) - 1;
document.addEventListener('keydown', keydownHandler);
};
const removeEventHandler = () => {
if (handlerIndex !== -1) {
const handlerToRemove = taggedHandlers[handlerIndex];
document.removeEventListener('keydown', handlerToRemove);
handlerIndex = -1;
}
};
onBeforeUnmount(() => {
removeEventHandler();
});
return {
addEventHandler,
removeEventHandler,
};
}
@@ -0,0 +1,89 @@
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { useKeyboardEventListener } from './useKeyboardEventListener';
/**
* Composable for handling keyboard-based mention selection.
* @param {Object} options - Configuration options.
* @param {Array} options.items - The list of items to select from.
* @param {Function} options.onSelect - Callback function when an item is selected.
* @param {Function} options.adjustScroll - Function to adjust scroll position.
* @returns {Object} An object containing methods and state for mention selection.
*/
export function useMentionSelectionKeyboard({ items, onSelect, adjustScroll }) {
const selectedIndex = ref(0);
const moveSelectionUp = () => {
if (!selectedIndex.value) {
selectedIndex.value = items.length - 1;
} else {
selectedIndex.value -= 1;
}
adjustScroll();
};
const moveSelectionDown = () => {
if (selectedIndex.value === items.length - 1) {
selectedIndex.value = 0;
} else {
selectedIndex.value += 1;
}
adjustScroll();
};
const getKeyboardEvents = () => ({
ArrowUp: {
action: e => {
moveSelectionUp();
e.preventDefault();
},
allowOnFocusedInput: true,
},
'Control+KeyP': {
action: e => {
moveSelectionUp();
e.preventDefault();
},
allowOnFocusedInput: true,
},
ArrowDown: {
action: e => {
moveSelectionDown();
e.preventDefault();
},
allowOnFocusedInput: true,
},
'Control+KeyN': {
action: e => {
moveSelectionDown();
e.preventDefault();
},
allowOnFocusedInput: true,
},
Enter: {
action: e => {
onSelect();
e.preventDefault();
},
allowOnFocusedInput: true,
},
});
const { addEventHandler, removeEventHandler } = useKeyboardEventListener();
onMounted(() => {
const events = getKeyboardEvents();
if (events) {
addEventHandler(events);
}
});
onBeforeUnmount(() => {
removeEventHandler();
});
return {
selectedIndex,
moveSelectionUp,
moveSelectionDown,
};
}
@@ -0,0 +1,22 @@
import { computed } from 'vue';
/**
* Composable for handling message-related computations.
* @param {Object} message - The message object to be processed.
* @returns {Object} An object containing computed properties for message content and attachments.
*/
export function useMessage(message) {
const messageContentAttributes = computed(() => {
const { content_attributes: attribute = {} } = message;
return attribute;
});
const hasAttachments = computed(() => {
return !!(message.attachments && message.attachments.length > 0);
});
return {
messageContentAttributes,
hasAttachments,
};
}
@@ -0,0 +1,48 @@
import MessageFormatter from '../helpers/MessageFormatter';
/**
* Composable for handling message formatting operations.
* @returns {Object} An object containing methods for formatting and manipulating messages.
*/
export function useMessageFormatter() {
const formatMessage = (message, isATweet, isAPrivateNote) => {
const messageFormatter = new MessageFormatter(
message,
isATweet,
isAPrivateNote
);
return messageFormatter.formattedMessage;
};
const getPlainText = (message, isATweet) => {
const messageFormatter = new MessageFormatter(message, isATweet);
return messageFormatter.plainText;
};
const truncateMessage = (description = '') => {
if (description.length < 100) {
return description;
}
return `${description.slice(0, 97)}...`;
};
const highlightContent = (
content = '',
searchTerm = '',
highlightClass = ''
) => {
const plainTextContent = getPlainText(content);
const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return plainTextContent.replace(
new RegExp(`(${escapedSearchTerm})`, 'ig'),
`<span class="${highlightClass}">$1</span>`
);
};
return {
formatMessage,
getPlainText,
truncateMessage,
highlightContent,
};
}
@@ -0,0 +1,36 @@
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from 'vuex';
import { frontendURL } from 'dashboard/helper/URLHelper';
import allLocales from 'shared/constants/locales.js';
/**
* Composable for handling portal-related computations and methods.
* @returns {Object} An object containing computed properties and methods for portal management.
*/
export function usePortal() {
const route = useRoute();
const store = useStore();
const accountId = computed(() => store.getters.getCurrentAccountId);
const portalSlug = computed(() => route.params.portalSlug);
const locale = computed(() => route.params.locale);
const articleUrl = id => {
return frontendURL(
`accounts/${accountId.value}/portals/${portalSlug.value}/${locale.value}/articles/${id}`
);
};
const localeName = code => {
return allLocales[code];
};
return {
accountId,
portalSlug,
locale,
articleUrl,
localeName,
};
}
@@ -0,0 +1,27 @@
import { useRouter, useRoute } from 'vue-router';
/**
* Composable for handling router-related operations.
* @returns {Object} An object containing methods for router manipulation.
*/
export function useRouterHelper() {
const router = useRouter();
const route = useRoute();
/**
* Replaces the current route with a new one if it's different.
* @param {string} name - The name of the route to replace with.
* @param {Object} params - The params to pass to the new route.
* @returns {Promise|undefined} A promise that resolves when the navigation is complete, or undefined if no navigation occurs.
*/
const replaceRoute = async (name, params = {}) => {
if (route.name !== name) {
return router.replace({ name, params });
}
return undefined;
};
return {
replaceRoute,
};
}