@capillarytech/creatives-library 9.0.47 → 9.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +1 -0
- package/package.json +1 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/SmsPreviewContent.js +21 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +5 -0
- package/v2Components/CommonTestAndPreview/index.js +4 -1
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/SmsPreviewContent.test.js +32 -0
- package/v2Components/SmsFallback/constants.js +19 -0
- package/v2Components/SmsFallback/index.js +0 -3
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/dltVarTypes.js +88 -0
- package/v2Containers/SmsTrai/Edit/index.js +198 -92
- package/v2Containers/SmsTrai/Edit/index.scss +117 -1
- package/v2Containers/SmsTrai/Edit/messages.js +40 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +21164 -4559
- package/v2Containers/SmsTrai/Edit/tests/dltVarTypes.test.js +100 -0
- package/v2Containers/Templates/_templates.scss +23 -0
- package/v2Containers/Templates/index.js +113 -32
- package/v2Containers/Templates/messages.js +12 -0
package/constants/unified.js
CHANGED
|
@@ -179,6 +179,7 @@ export const DLT_HASH_TOKEN_INNER_NAME_REGEX = /^\{#\s*(.*?)\s*#\}$/;
|
|
|
179
179
|
export const MUSTACHE_VAR_NAME_CAPTURE_REGEX = /\{\{([^}]+)\}\}/g;
|
|
180
180
|
/** Global with capture group: inner body inside `{#body#}`. */
|
|
181
181
|
export const DLT_VAR_BODY_CAPTURE_REGEX = /\{\#([^#]*)\#\}/g;
|
|
182
|
+
export const DLT_HASH_TOKEN_SPLIT_REGEX = /(\{#[^#]*#\})/g;
|
|
182
183
|
|
|
183
184
|
export const GET_TRANSLATION_MAPPED = {
|
|
184
185
|
'en': 'en-US',
|
package/package.json
CHANGED
|
@@ -24,11 +24,29 @@ import {
|
|
|
24
24
|
TIME_FORMAT_PM,
|
|
25
25
|
} from '../constants';
|
|
26
26
|
import messages from '../messages';
|
|
27
|
+
import { DLT_HASH_TOKEN_SPLIT_REGEX } from '../../../constants/unified';
|
|
27
28
|
|
|
28
29
|
// Import device mockup images
|
|
29
30
|
const smsMobileAndroid = require('../../../assets/Android.png');
|
|
30
31
|
const smsMobileIos = require('../../../assets/iOS.png');
|
|
31
32
|
|
|
33
|
+
// Split-with-capture keeps DLT tokens at odd indices so they can be styled distinctly.
|
|
34
|
+
function renderSmsPreviewWithHighlightedTokens(text) {
|
|
35
|
+
if (typeof text !== 'string' || !text) return text;
|
|
36
|
+
return text.split(DLT_HASH_TOKEN_SPLIT_REGEX).map((part, i) => (
|
|
37
|
+
i % 2 === 1
|
|
38
|
+
? (
|
|
39
|
+
<CapLabel.CapLabelInline
|
|
40
|
+
key={`dlt-tok-${i}`}
|
|
41
|
+
className="sms-preview-dlt-token"
|
|
42
|
+
>
|
|
43
|
+
{part}
|
|
44
|
+
</CapLabel.CapLabelInline>
|
|
45
|
+
)
|
|
46
|
+
: part
|
|
47
|
+
));
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
const SmsPreviewContent = ({
|
|
33
51
|
content,
|
|
34
52
|
device,
|
|
@@ -125,7 +143,9 @@ const SmsPreviewContent = ({
|
|
|
125
143
|
<CapRow className="sms-message-container">
|
|
126
144
|
<CapRow useLegacy className="sms-message-bubble">
|
|
127
145
|
<CapRow useLegacy className="sms-message-text">
|
|
128
|
-
{content
|
|
146
|
+
{content
|
|
147
|
+
? renderSmsPreviewWithHighlightedTokens(content)
|
|
148
|
+
: formatMessage(messages.noContent)}
|
|
129
149
|
</CapRow>
|
|
130
150
|
</CapRow>
|
|
131
151
|
<CapRow useLegacy className="sms-timestamp">
|
|
@@ -1350,7 +1350,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
1350
1350
|
const language = formDataObj?.language || formDataObj?.templateLanguage || 'en';
|
|
1351
1351
|
const mediaType = formDataObj?.mediaType || formDataObj?.templateMediaType || MEDIA_TYPE_TEXT;
|
|
1352
1352
|
const buttonType = formDataObj?.buttonType || BUTTON_TYPE_NONE;
|
|
1353
|
-
const
|
|
1353
|
+
const existingVarMapped = formDataObj?.varMapped || {};
|
|
1354
|
+
const varMapped = Object.keys(existingVarMapped).length > 0
|
|
1355
|
+
? existingVarMapped
|
|
1356
|
+
: (customValuesObj || {});
|
|
1354
1357
|
const isPreviewUrl = formDataObj?.isPreviewUrl || false;
|
|
1355
1358
|
|
|
1356
1359
|
// Extract buttons from ctaData or quickReplyData
|
|
@@ -601,6 +601,38 @@ describe('SmsPreviewContent', () => {
|
|
|
601
601
|
});
|
|
602
602
|
});
|
|
603
603
|
|
|
604
|
+
describe('DLT token highlighting', () => {
|
|
605
|
+
it('wraps `{#…#}` tokens in a highlighted span and leaves plain text as-is', () => {
|
|
606
|
+
const { container } = render(
|
|
607
|
+
<TestWrapper>
|
|
608
|
+
<ComponentToRender {...defaultProps} content="Hi {#alp#}, code {#num#} thanks" />
|
|
609
|
+
</TestWrapper>
|
|
610
|
+
);
|
|
611
|
+
const tokens = container.querySelectorAll('.sms-preview-dlt-token');
|
|
612
|
+
expect(tokens.length).toBe(2);
|
|
613
|
+
expect(tokens[0].textContent).toBe('{#alp#}');
|
|
614
|
+
expect(tokens[1].textContent).toBe('{#num#}');
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('renders no token spans when content has no DLT tokens', () => {
|
|
618
|
+
const { container } = render(
|
|
619
|
+
<TestWrapper>
|
|
620
|
+
<ComponentToRender {...defaultProps} content="Plain SMS body" />
|
|
621
|
+
</TestWrapper>
|
|
622
|
+
);
|
|
623
|
+
expect(container.querySelectorAll('.sms-preview-dlt-token').length).toBe(0);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('falls back to noContent placeholder when content is empty', () => {
|
|
627
|
+
render(
|
|
628
|
+
<TestWrapper>
|
|
629
|
+
<ComponentToRender {...defaultProps} content="" />
|
|
630
|
+
</TestWrapper>
|
|
631
|
+
);
|
|
632
|
+
expect(screen.getByText('No content available')).toBeTruthy();
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
604
636
|
describe('PropTypes Validation', () => {
|
|
605
637
|
it('should accept all required props without warnings', () => {
|
|
606
638
|
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CAP_BLUE01, CAP_GREEN01, CAP_PURPLE01, CAP_PURPLE03, CAP_PALE_GREY, CAP_G04, CAP_G08,
|
|
3
|
+
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
1
4
|
import {
|
|
2
5
|
WHATSAPP,
|
|
3
6
|
RCS,
|
|
@@ -45,6 +48,22 @@ export const SMS_CATEGORY_FILTERS = {
|
|
|
45
48
|
SERVICE_IMPLICIT: 'implicit',
|
|
46
49
|
};
|
|
47
50
|
|
|
51
|
+
/** Backend `versions.base.type` display strings for DLT SMS categories. */
|
|
52
|
+
export const SMS_CATEGORY_LABELS = {
|
|
53
|
+
PROMOTIONAL: 'promotional',
|
|
54
|
+
SERVICE_IMPLICIT: 'service implicit',
|
|
55
|
+
SERVICE_EXPLICIT: 'service explicit',
|
|
56
|
+
TRANSACTIONAL: 'transactional',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Colored-tag styling per DLT SMS category, keyed by lowercased `versions.base.type`. */
|
|
60
|
+
export const SMS_CATEGORY_TAG_STYLES = {
|
|
61
|
+
[SMS_CATEGORY_LABELS.PROMOTIONAL]: { tagColor: CAP_PALE_GREY, tagTextColor: CAP_BLUE01 },
|
|
62
|
+
[SMS_CATEGORY_LABELS.SERVICE_IMPLICIT]: { tagColor: '#E7F6EC', tagTextColor: CAP_GREEN01 },
|
|
63
|
+
[SMS_CATEGORY_LABELS.SERVICE_EXPLICIT]: { tagColor: CAP_PURPLE03, tagTextColor: CAP_PURPLE01 },
|
|
64
|
+
[SMS_CATEGORY_LABELS.TRANSACTIONAL]: { tagColor: CAP_G08, tagTextColor: CAP_G04 },
|
|
65
|
+
};
|
|
66
|
+
|
|
48
67
|
/** `location` for embedded SMS creatives picker (CreativesContainer / TemplatesV2). */
|
|
49
68
|
export const EMBEDDED_SMS_CREATIVES_LOCATION = {
|
|
50
69
|
pathname: '/sms/create',
|
|
@@ -263,9 +263,6 @@ function SmsFallbackPortalContent({
|
|
|
263
263
|
<CapRadio.Button value={SMS_CATEGORY_FILTERS.PROMOTIONAL}>
|
|
264
264
|
<CapLabel type="label2"><FormattedMessage {...templatesListMessages.promotional} /></CapLabel>
|
|
265
265
|
</CapRadio.Button>
|
|
266
|
-
<CapRadio.Button value={SMS_CATEGORY_FILTERS.SERVICE_EXPLICIT}>
|
|
267
|
-
<CapLabel type="label2"><FormattedMessage {...templatesListMessages.serviceExplicit} /></CapLabel>
|
|
268
|
-
</CapRadio.Button>
|
|
269
266
|
<CapRadio.Button value={SMS_CATEGORY_FILTERS.SERVICE_IMPLICIT}>
|
|
270
267
|
<CapLabel type="label2"><FormattedMessage {...templatesListMessages.serviceImplicit} /></CapLabel>
|
|
271
268
|
</CapRadio.Button>
|
|
@@ -15,3 +15,5 @@ export const DEFAULT = 'default';
|
|
|
15
15
|
export const FULL = 'full';
|
|
16
16
|
export const ALL = 'all';
|
|
17
17
|
export const LIBRARY = 'library';
|
|
18
|
+
/** Matches horizontal whitespace only (spaces, tabs) — preserves \r\n so newline-only chunks are still non-empty. */
|
|
19
|
+
export const HORIZONTAL_WHITESPACE_ONLY_REGEX = /[^\S\r\n]/gm;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const DLT_VAR_TYPE_KEYS = [
|
|
2
|
+
'numeric',
|
|
3
|
+
'alphanumeric',
|
|
4
|
+
'url',
|
|
5
|
+
'urlott',
|
|
6
|
+
'cbn',
|
|
7
|
+
'email',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export const DLT_VAR_TYPE_META = {
|
|
11
|
+
numeric: {
|
|
12
|
+
short: '{#num#}',
|
|
13
|
+
long: '{#numeric#}',
|
|
14
|
+
charLimit: 40,
|
|
15
|
+
placeholderKey: 'dltVarPlaceholderNumeric',
|
|
16
|
+
},
|
|
17
|
+
alphanumeric: {
|
|
18
|
+
short: '{#alp#}',
|
|
19
|
+
long: '{#alphanumeric#}',
|
|
20
|
+
charLimit: 40,
|
|
21
|
+
placeholderKey: 'dltVarPlaceholderAlphanumeric',
|
|
22
|
+
},
|
|
23
|
+
url: {
|
|
24
|
+
short: '{#urg#}',
|
|
25
|
+
long: '{#url#}',
|
|
26
|
+
charLimit: 120,
|
|
27
|
+
placeholderKey: 'dltVarPlaceholderUrl',
|
|
28
|
+
},
|
|
29
|
+
urlott: {
|
|
30
|
+
short: '{#uro#}',
|
|
31
|
+
long: '{#urlott#}',
|
|
32
|
+
charLimit: 120,
|
|
33
|
+
placeholderKey: 'dltVarPlaceholderUrlott',
|
|
34
|
+
},
|
|
35
|
+
cbn: {
|
|
36
|
+
short: '{#cbn#}',
|
|
37
|
+
long: '{#cbn#}',
|
|
38
|
+
charLimit: 14,
|
|
39
|
+
placeholderKey: 'dltVarPlaceholderCbn',
|
|
40
|
+
},
|
|
41
|
+
email: {
|
|
42
|
+
short: '{#eml#}',
|
|
43
|
+
long: '{#email#}',
|
|
44
|
+
charLimit: 40,
|
|
45
|
+
placeholderKey: 'dltVarPlaceholderEmail',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const DLT_LEGACY_VAR_TOKEN = '{#var#}';
|
|
50
|
+
|
|
51
|
+
const TOKEN_TO_TYPE = (() => {
|
|
52
|
+
const map = {};
|
|
53
|
+
DLT_VAR_TYPE_KEYS.forEach((key) => {
|
|
54
|
+
const meta = DLT_VAR_TYPE_META[key];
|
|
55
|
+
map[meta.short.toLowerCase()] = key;
|
|
56
|
+
map[meta.long.toLowerCase()] = key;
|
|
57
|
+
});
|
|
58
|
+
map[DLT_LEGACY_VAR_TOKEN] = 'alphanumeric';
|
|
59
|
+
return map;
|
|
60
|
+
})();
|
|
61
|
+
|
|
62
|
+
export const ANY_DLT_HASH_TOKEN_REGEX = /^\{#\s*[^#]*\s*#\}$/;
|
|
63
|
+
export const ANY_DLT_HASH_TOKEN_GLOBAL_REGEX = /\{#\s*[^#]*\s*#\}/g;
|
|
64
|
+
export const ANY_DLT_HASH_TOKEN_PROBE_REGEX = /\{#[^#]*#\}/;
|
|
65
|
+
export const DLT_LEGACY_VAR_REGEX = /\{#\s*var\s*#\}/i;
|
|
66
|
+
export const DLT_HASH_TOKEN_SPLIT_REGEX = /(\{#[^#]*#\})/g;
|
|
67
|
+
|
|
68
|
+
export function isAnyDltVarToken(candidate) {
|
|
69
|
+
return typeof candidate === 'string' && ANY_DLT_HASH_TOKEN_REGEX.test(candidate);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normaliseToken(token) {
|
|
73
|
+
if (typeof token !== 'string') return '';
|
|
74
|
+
return token.replace(/\s+/g, '').toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getDltVarTypeKey(token) {
|
|
78
|
+
return TOKEN_TO_TYPE[normaliseToken(token)] || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getDltVarTypeMeta(token) {
|
|
82
|
+
const key = getDltVarTypeKey(token);
|
|
83
|
+
return key ? DLT_VAR_TYPE_META[key] : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getDltVarCharLimit(token, fallback = 40) {
|
|
87
|
+
return getDltVarTypeMeta(token)?.charLimit ?? fallback;
|
|
88
|
+
}
|