chore: Replace multiple mixins to simgle use composables
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user