Merge branch 'develop' into feat/captain-editor-integration
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
"bot-outline": "M17.753 14a2.25 2.25 0 0 1 2.25 2.25v.905a3.75 3.75 0 0 1-1.307 2.846C17.13 21.345 14.89 22 12 22c-2.89 0-5.128-.656-6.691-2a3.75 3.75 0 0 1-1.306-2.843v-.908A2.25 2.25 0 0 1 6.253 14h11.5Zm0 1.5h-11.5a.75.75 0 0 0-.75.75v.908c0 .655.286 1.278.784 1.706C7.545 19.945 9.44 20.502 12 20.502c2.56 0 4.458-.557 5.719-1.64a2.25 2.25 0 0 0 .784-1.706v-.906a.75.75 0 0 0-.75-.75ZM11.898 2.008 12 2a.75.75 0 0 1 .743.648l.007.102V3.5h3.5a2.25 2.25 0 0 1 2.25 2.25v4.505a2.25 2.25 0 0 1-2.25 2.25h-8.5a2.25 2.25 0 0 1-2.25-2.25V5.75A2.25 2.25 0 0 1 7.75 3.5h3.5v-.749a.75.75 0 0 1 .648-.743L12 2l-.102.007ZM16.25 5h-8.5a.75.75 0 0 0-.75.75v4.505c0 .414.336.75.75.75h8.5a.75.75 0 0 0 .75-.75V5.75a.75.75 0 0 0-.75-.75Zm-6.5 1.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Zm4.492 0a1.25 1.25 0 1 1 0 2.499 1.25 1.25 0 0 1 0-2.499Z",
|
||||
"brand-facebook-outline": "M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z",
|
||||
"brand-instagram-outline": "M 8 3 C 5.243 3 3 5.243 3 8 L 3 16 C 3 18.757 5.243 21 8 21 L 16 21 C 18.757 21 21 18.757 21 16 L 21 8 C 21 5.243 18.757 3 16 3 L 8 3 z M 8 5 L 16 5 C 17.654 5 19 6.346 19 8 L 19 16 C 19 17.654 17.654 19 16 19 L 8 19 C 6.346 19 5 17.654 5 16 L 5 8 C 5 6.346 6.346 5 8 5 z M 17 6 A 1 1 0 0 0 16 7 A 1 1 0 0 0 17 8 A 1 1 0 0 0 18 7 A 1 1 0 0 0 17 6 z M 12 7 C 9.243 7 7 9.243 7 12 C 7 14.757 9.243 17 12 17 C 14.757 17 17 14.757 17 12 C 17 9.243 14.757 7 12 7 z M 12 9 C 13.654 9 15 10.346 15 12 C 15 13.654 13.654 15 12 15 C 10.346 15 9 13.654 9 12 C 9 10.346 10.346 9 12 9 z",
|
||||
"brand-tiktok-outline": "M 8 3 C 5.243 3 3 5.243 3 8 L 3 16 C 3 18.757 5.243 21 8 21 L 16 21 C 18.757 21 21 18.757 21 16 L 21 8 C 21 5.243 18.757 3 16 3 L 8 3 z M 8 5 L 16 5 C 17.654 5 19 6.346 19 8 L 19 16 C 19 17.654 17.654 19 16 19 L 8 19 C 6.346 19 5 17.654 5 16 L 5 8 C 5 6.346 6.346 5 8 5 z M 17 6 A 1 1 0 0 0 16 7 A 1 1 0 0 0 17 8 A 1 1 0 0 0 18 7 A 1 1 0 0 0 17 6 z M 12 7 C 9.243 7 7 9.243 7 12 C 7 14.757 9.243 17 12 17 C 14.757 17 17 14.757 17 12 C 17 9.243 14.757 7 12 7 z M 12 9 C 13.654 9 15 10.346 15 12 C 15 13.654 13.654 15 12 15 C 10.346 15 9 13.654 9 12 C 9 10.346 10.346 9 12 9 z",
|
||||
"brand-github-outline": "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385c.6.105.825-.255.825-.57c0-.285-.015-1.23-.015-2.235c-3.015.555-3.795-.735-4.035-1.41c-.135-.345-.72-1.41-1.23-1.695c-.42-.225-1.02-.78-.015-.795c.945-.015 1.62.87 1.845 1.23c1.08 1.815 2.805 1.305 3.495.99c.105-.78.42-1.305.765-1.605c-2.67-.3-5.46-1.335-5.46-5.925c0-1.305.465-2.385 1.23-3.225c-.12-.3-.54-1.53.12-3.18c0 0 1.005-.315 3.3 1.23c.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23c.66 1.65.24 2.88.12 3.18c.765.84 1.23 1.905 1.23 3.225c0 4.605-2.805 5.625-5.475 5.925c.435.375.81 1.095.81 2.22c0 1.605-.015 2.895-.015 3.3c0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12Z",
|
||||
"brand-line-outline": "M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314",
|
||||
"brand-linkedin-outline": "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { nextTick } from 'vue';
|
||||
import { useExpandableContent } from '../useExpandableContent';
|
||||
|
||||
// Mock VueUse composables
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
useToggle: vi.fn(initialValue => {
|
||||
let value = initialValue;
|
||||
const toggle = newValue => {
|
||||
value = newValue !== undefined ? newValue : !value;
|
||||
};
|
||||
return [{ value }, toggle];
|
||||
}),
|
||||
useResizeObserver: vi.fn((element, callback) => {
|
||||
// Store callback for manual triggering in tests
|
||||
if (element.value) {
|
||||
callback();
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useExpandableContent', () => {
|
||||
let originalGetComputedStyle;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock window.getComputedStyle
|
||||
originalGetComputedStyle = window.getComputedStyle;
|
||||
window.getComputedStyle = vi.fn(() => ({
|
||||
lineHeight: '20px',
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.getComputedStyle = originalGetComputedStyle;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns expected properties', () => {
|
||||
const result = useExpandableContent();
|
||||
|
||||
expect(result).toHaveProperty('contentElement');
|
||||
expect(result).toHaveProperty('isExpanded');
|
||||
expect(result).toHaveProperty('needsToggle');
|
||||
expect(result).toHaveProperty('toggleExpanded');
|
||||
expect(result).toHaveProperty('checkOverflow');
|
||||
});
|
||||
|
||||
it('initializes with default values', () => {
|
||||
const { isExpanded, needsToggle } = useExpandableContent();
|
||||
|
||||
expect(isExpanded.value).toBe(false);
|
||||
expect(needsToggle.value).toBe(false);
|
||||
});
|
||||
|
||||
it('checkOverflow sets needsToggle to true when content overflows', async () => {
|
||||
const { contentElement, needsToggle, checkOverflow } =
|
||||
useExpandableContent();
|
||||
|
||||
// Mock element with overflow
|
||||
contentElement.value = {
|
||||
scrollHeight: 100, // Much larger than 2 lines (40px)
|
||||
};
|
||||
|
||||
checkOverflow();
|
||||
await nextTick();
|
||||
|
||||
expect(needsToggle.value).toBe(true);
|
||||
});
|
||||
|
||||
it('checkOverflow sets needsToggle to false when content fits', async () => {
|
||||
const { contentElement, needsToggle, checkOverflow } =
|
||||
useExpandableContent();
|
||||
|
||||
// Mock element without overflow
|
||||
contentElement.value = {
|
||||
scrollHeight: 30, // Less than 2 lines (40px)
|
||||
};
|
||||
|
||||
checkOverflow();
|
||||
await nextTick();
|
||||
|
||||
expect(needsToggle.value).toBe(false);
|
||||
});
|
||||
|
||||
it('respects custom maxLines option', async () => {
|
||||
const { contentElement, needsToggle, checkOverflow } = useExpandableContent(
|
||||
{
|
||||
maxLines: 3,
|
||||
}
|
||||
);
|
||||
|
||||
// Mock element that fits in 3 lines but not 2
|
||||
contentElement.value = {
|
||||
scrollHeight: 50, // Fits in 3 lines (60px) but not 2 lines (40px)
|
||||
};
|
||||
|
||||
checkOverflow();
|
||||
await nextTick();
|
||||
|
||||
expect(needsToggle.value).toBe(false);
|
||||
});
|
||||
|
||||
it('uses defaultLineHeight when computed style is unavailable', async () => {
|
||||
window.getComputedStyle = vi.fn(() => ({
|
||||
lineHeight: 'normal', // Not a valid number
|
||||
}));
|
||||
|
||||
const { contentElement, needsToggle, checkOverflow } = useExpandableContent(
|
||||
{
|
||||
defaultLineHeight: 16,
|
||||
}
|
||||
);
|
||||
|
||||
// Mock element that overflows with 16px line height (32px max)
|
||||
contentElement.value = {
|
||||
scrollHeight: 40,
|
||||
};
|
||||
|
||||
checkOverflow();
|
||||
await nextTick();
|
||||
|
||||
expect(needsToggle.value).toBe(true);
|
||||
});
|
||||
|
||||
it('handles null contentElement gracefully', () => {
|
||||
const { checkOverflow } = useExpandableContent();
|
||||
|
||||
// Should not throw when contentElement is null
|
||||
expect(() => checkOverflow()).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ref, computed, nextTick, onMounted } from 'vue';
|
||||
import { useToggle, useResizeObserver } from '@vueuse/core';
|
||||
|
||||
/**
|
||||
* Composable for handling expandable content with "Read more / Read less" functionality.
|
||||
* Detects content overflow and provides toggle state for expansion.
|
||||
*
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {number} [options.maxLines=2] - Maximum number of lines before showing toggle
|
||||
* @param {number} [options.defaultLineHeight=20] - Fallback line height if computed style is unavailable
|
||||
* @param {boolean} [options.useResizeObserverForCheck=false] - Use ResizeObserver for continuous overflow checking
|
||||
* @returns {Object} - Composable state and methods
|
||||
*/
|
||||
export function useExpandableContent(options = {}) {
|
||||
const {
|
||||
maxLines = 2,
|
||||
defaultLineHeight = 20,
|
||||
useResizeObserverForCheck = false,
|
||||
} = options;
|
||||
|
||||
const contentElement = ref(null);
|
||||
const [isExpanded, toggleExpanded] = useToggle(false);
|
||||
const needsToggle = ref(false);
|
||||
|
||||
const showReadMore = computed(() => needsToggle.value && !isExpanded.value);
|
||||
const showReadLess = computed(() => needsToggle.value && isExpanded.value);
|
||||
|
||||
/**
|
||||
* Checks if content overflows the maximum allowed height
|
||||
* and updates needsToggle accordingly
|
||||
*/
|
||||
const checkOverflow = () => {
|
||||
if (!contentElement.value) return;
|
||||
|
||||
const element = contentElement.value;
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const lineHeight =
|
||||
parseFloat(computedStyle.lineHeight) || defaultLineHeight;
|
||||
const maxHeight = lineHeight * maxLines;
|
||||
|
||||
needsToggle.value = element.scrollHeight > maxHeight;
|
||||
};
|
||||
|
||||
// Setup overflow checking based on configuration
|
||||
if (useResizeObserverForCheck) {
|
||||
useResizeObserver(contentElement, checkOverflow);
|
||||
} else {
|
||||
onMounted(() => {
|
||||
nextTick(checkOverflow);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
contentElement,
|
||||
isExpanded,
|
||||
needsToggle,
|
||||
showReadMore,
|
||||
showReadLess,
|
||||
toggleExpanded,
|
||||
checkOverflow,
|
||||
};
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export const REPLY_POLICY = {
|
||||
'https://www.twilio.com/docs/whatsapp/tutorial/send-whatsapp-notification-messages-templates#sending-non-template-messages-within-a-24-hour-session',
|
||||
WHATSAPP_CLOUD:
|
||||
'https://business.whatsapp.com/policy#:~:text=You%20may%20reply%20to%20a,messages%20via%20approved%20Message%20Templates.',
|
||||
TIKTOK: 'https://business-api.tiktok.com/portal/docs?id=1832184236919810',
|
||||
};
|
||||
|
||||
export const CHANGELOG_API_URL = 'https://hub.2.chatwoot.com/changelogs';
|
||||
|
||||
@@ -164,4 +164,5 @@ export const TWILIO_CONTENT_TEMPLATE_TYPES = {
|
||||
TEXT: 'text',
|
||||
MEDIA: 'media',
|
||||
QUICK_REPLY: 'quick_reply',
|
||||
CALL_TO_ACTION: 'call_to_action',
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ export const MESSAGE_MAX_LENGTH = {
|
||||
FACEBOOK: 2000,
|
||||
// https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/messaging-api#send-a-text-message
|
||||
INSTAGRAM: 1000,
|
||||
// https://business-api.tiktok.com/portal/docs?id=1832184403754242
|
||||
TIKTOK: 6000,
|
||||
// https://www.twilio.com/docs/glossary/what-sms-character-limit
|
||||
TWILIO_SMS: 320,
|
||||
// https://help.twilio.com/articles/360033806753-Maximum-Message-Length-with-Twilio-Programmable-Messaging
|
||||
|
||||
@@ -14,6 +14,7 @@ export const INBOX_FEATURE_MAP = {
|
||||
INBOX_TYPES.TWITTER,
|
||||
INBOX_TYPES.WHATSAPP,
|
||||
INBOX_TYPES.TELEGRAM,
|
||||
INBOX_TYPES.TIKTOK,
|
||||
INBOX_TYPES.API,
|
||||
],
|
||||
[INBOX_FEATURES.REPLY_TO_OUTGOING]: [
|
||||
@@ -21,6 +22,7 @@ export const INBOX_FEATURE_MAP = {
|
||||
INBOX_TYPES.TWITTER,
|
||||
INBOX_TYPES.WHATSAPP,
|
||||
INBOX_TYPES.TELEGRAM,
|
||||
INBOX_TYPES.TIKTOK,
|
||||
INBOX_TYPES.API,
|
||||
],
|
||||
};
|
||||
@@ -115,6 +117,8 @@ export default {
|
||||
badgeKey = this.twilioBadge;
|
||||
} else if (this.isAWhatsAppChannel) {
|
||||
badgeKey = 'whatsapp';
|
||||
} else if (this.isATiktokChannel) {
|
||||
badgeKey = 'tiktok';
|
||||
}
|
||||
return badgeKey || this.channelType;
|
||||
},
|
||||
@@ -127,6 +131,9 @@ export default {
|
||||
isAnInstagramChannel() {
|
||||
return this.channelType === INBOX_TYPES.INSTAGRAM;
|
||||
},
|
||||
isATiktokChannel() {
|
||||
return this.channelType === INBOX_TYPES.TIKTOK;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
inboxHasFeature(feature) {
|
||||
|
||||
Reference in New Issue
Block a user