@capillarytech/creatives-library 9.0.35 → 9.0.36
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/app.js +175 -10
- package/entry.js +1 -67
- package/mfe-exposed-components.js +4 -2
- package/package.json +2 -2
- package/services/api.js +1 -9
- package/styles/containers/layout/_layoutPage.scss +6 -8
- package/utils/gtmTrackers/gtmEvents/creativeDetails.js +1 -2
- package/utils/rcsPayloadUtils.js +2 -5
- package/utils/tests/rcsPayloadUtils.test.js +2 -54
- package/v2Components/CapActionButton/constants.js +0 -1
- package/v2Components/CapActionButton/index.js +9 -77
- package/v2Components/CapActionButton/index.scss +0 -13
- package/v2Components/CapActionButton/messages.js +0 -13
- package/v2Components/CapActionButton/tests/index.test.js +1 -32
- package/v2Components/CommonTestAndPreview/index.js +15 -44
- package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -78
- package/v2Components/CommonTestAndPreview/utils.js +0 -34
- package/v2Components/NavigationBar/index.js +7 -9
- package/v2Components/NavigationBar/tests/index.test.js +19 -39
- package/v2Components/SmsFallback/index.js +0 -6
- package/v2Containers/Cap/constants.js +0 -1
- package/v2Containers/Cap/index.js +27 -57
- package/v2Containers/CreativesContainer/index.js +8 -11
- package/v2Containers/CreativesContainer/tests/index.test.js +0 -53
- package/v2Containers/Rcs/carouselUtils.js +73 -46
- package/v2Containers/Rcs/components/CarouselCard.js +9 -11
- package/v2Containers/Rcs/components/CarouselCardButtons.js +0 -20
- package/v2Containers/Rcs/constants.js +7 -2
- package/v2Containers/Rcs/index.js +205 -226
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +47 -60
- package/v2Containers/Rcs/tests/CarouselCard.test.js +2 -1
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -286
- package/v2Containers/Rcs/tests/carouselUtils.test.js +32 -28
- package/v2Containers/Rcs/tests/index.test.js +238 -151
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -110
- package/v2Containers/Rcs/tests/utils.test.js +17 -36
- package/v2Containers/Rcs/utils.js +55 -28
- package/v2Containers/SmsTrai/Edit/index.js +24 -35
- package/v2Containers/Templates/_templates.scss +1 -1
- package/v2Containers/Templates/index.js +0 -1
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +0 -5
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +3 -8
- package/v2Containers/TemplatesV2/index.js +9 -22
- package/AppRoot.js +0 -124
- package/app-config.js +0 -61
- package/bootstrap.js +0 -185
- package/utils/getDataLayer.js +0 -15
- package/utils/mfeDetect.js +0 -1
- package/utils/mfeFirstPaintReady.js +0 -29
- package/utils/mfeHistory.js +0 -62
- package/v2Components/NavigationBar/mfeModuleHeader.config.js +0 -16
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
REQUEST,
|
|
31
31
|
DEFAULT,
|
|
32
32
|
ENABLE_PRODUCT_SUPPORT_VIDEOS,
|
|
33
|
-
ENABLE_NEW_LEFT_NAVIGATION,
|
|
34
33
|
CAMPAIGN_SETTINGS_URL,
|
|
35
34
|
} from './constants';
|
|
36
35
|
import './_cap.scss';
|
|
@@ -54,8 +53,7 @@ import { v2ViberSagas } from '../Viber/sagas';
|
|
|
54
53
|
import { v2FacebookSagas } from '../Facebook/sagas';
|
|
55
54
|
import createReducer from '../Line/Container/reducer';
|
|
56
55
|
import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
|
|
57
|
-
|
|
58
|
-
const gtm = getDataLayer();
|
|
56
|
+
const gtm = window.dataLayer || [];
|
|
59
57
|
const {
|
|
60
58
|
logNewTab,
|
|
61
59
|
utilsGetOrgNameFromId,
|
|
@@ -74,19 +72,18 @@ const CapWrapper = styled.div`
|
|
|
74
72
|
min-height: 100%;
|
|
75
73
|
padding: 0;
|
|
76
74
|
flex-direction: column;
|
|
77
|
-
position: relative;
|
|
78
75
|
`;
|
|
79
76
|
GA.initialize({ accessKey: GTM_TRACKING_ID, trackUIError: true, trackLoadPerformance: 'creatives' });
|
|
80
77
|
|
|
81
78
|
const MainWrapper = styled.div`
|
|
82
79
|
position: relative;
|
|
83
|
-
min-height:
|
|
84
|
-
top:
|
|
80
|
+
min-height: calc(100vh - 5.29rem);
|
|
81
|
+
top: 4.72rem;
|
|
85
82
|
`;
|
|
86
83
|
|
|
87
84
|
const ContentWrapper = styled.div`
|
|
88
85
|
&& {
|
|
89
|
-
margin-left:
|
|
86
|
+
margin-left: ${(props) => (props.leftNavbarExpanded ? '17rem' : '4.475rem')};
|
|
90
87
|
}
|
|
91
88
|
`;
|
|
92
89
|
|
|
@@ -121,16 +118,12 @@ export class Cap extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
121
118
|
const userGtmData = this.getUserGtmData();
|
|
122
119
|
this.setDimensionData(userGtmData);
|
|
123
120
|
gtm.push({ ...userGtmData, event: 'userAuthenticated' });
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
roles: Object.keys(userData.accessRoles),
|
|
131
|
-
accessibleFeatures,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
121
|
+
Auth.initialize({
|
|
122
|
+
permissions: userData.accessiblePermissions,
|
|
123
|
+
isCapUser: userData.isCapUser,
|
|
124
|
+
roles: Object.keys(userData.accessRoles),
|
|
125
|
+
accessibleFeatures,
|
|
126
|
+
});
|
|
134
127
|
});
|
|
135
128
|
this.props.actions.getTopbarMenuData(parentModule);
|
|
136
129
|
if (this.props.Global.orgID !== undefined) {
|
|
@@ -507,8 +500,6 @@ export class Cap extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
507
500
|
const type = this.props.location.query.type;
|
|
508
501
|
const toastMessages = this.props.Global.messages;
|
|
509
502
|
const { isCreativesAccessible, showOrgChangeModal, showRefreshModal } = this.state;
|
|
510
|
-
const { accessibleFeatures = [] } = currentOrgDetails;
|
|
511
|
-
const isLatestLeftNavigationEnabled = accessibleFeatures.includes(ENABLE_NEW_LEFT_NAVIGATION);
|
|
512
503
|
return (
|
|
513
504
|
<CapWrapper>
|
|
514
505
|
{ this.props.loader.localeLoading || this.props.Global.fetching_userdata ?
|
|
@@ -546,47 +537,26 @@ export class Cap extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
546
537
|
campaignOrgV2Status={currentOrgDetails.org_campaign_v2_status}
|
|
547
538
|
handleLeftNavBarExpanded={this.handleLeftNavBarExpanded}
|
|
548
539
|
leftNavbarExpandedProp={this.state.leftNavbarExpanded}
|
|
540
|
+
/>) : ''}
|
|
541
|
+
<MainWrapper className="main">
|
|
549
542
|
|
|
543
|
+
<ContentWrapper
|
|
544
|
+
className={`main-content ${this.state.leftNavbarExpanded ? "contet-width-collapse" : "content-width-expanded"}`}
|
|
545
|
+
leftNavbarExpanded={this.state.leftNavbarExpanded}
|
|
550
546
|
>
|
|
551
|
-
<
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return (
|
|
560
|
-
<RenderRoute
|
|
561
|
-
{...routeProps}
|
|
562
|
-
key={routeProps.path}
|
|
563
|
-
isLoggedIn={isLoggedIn}
|
|
564
|
-
/>
|
|
565
|
-
)}
|
|
566
|
-
)}
|
|
567
|
-
</Switch>
|
|
568
|
-
</ContentWrapper>
|
|
569
|
-
</MainWrapper>
|
|
570
|
-
</NavigationBar>) : (
|
|
571
|
-
<MainWrapper isLatestLeftNavigationEnabled={isLatestLeftNavigationEnabled} className="main">
|
|
572
|
-
<ContentWrapper
|
|
573
|
-
className={`main-content ${this.state.leftNavbarExpanded ? "contet-width-collapse" : "content-width-expanded"}`}
|
|
574
|
-
isLatestLeftNavigationEnabled={isLatestLeftNavigationEnabled}
|
|
575
|
-
leftNavbarExpanded={this.state.leftNavbarExpanded}
|
|
576
|
-
>
|
|
577
|
-
<Switch>
|
|
578
|
-
{componentRoutes.map(routeProps => {
|
|
579
|
-
return (
|
|
580
|
-
<RenderRoute
|
|
581
|
-
{...routeProps}
|
|
582
|
-
key={routeProps.path}
|
|
583
|
-
isLoggedIn={isLoggedIn}
|
|
584
|
-
/>
|
|
585
|
-
)}
|
|
547
|
+
<Switch>
|
|
548
|
+
{componentRoutes.map(routeProps => {
|
|
549
|
+
return (
|
|
550
|
+
<RenderRoute
|
|
551
|
+
{...routeProps}
|
|
552
|
+
key={routeProps.path}
|
|
553
|
+
isLoggedIn={isLoggedIn}
|
|
554
|
+
/>
|
|
586
555
|
)}
|
|
587
|
-
|
|
588
|
-
</
|
|
589
|
-
</
|
|
556
|
+
)}
|
|
557
|
+
</Switch>
|
|
558
|
+
</ContentWrapper>
|
|
559
|
+
</MainWrapper>
|
|
590
560
|
</div>
|
|
591
561
|
{(toastMessages && toastMessages.length > 0) &&
|
|
592
562
|
toastMessages.map((message) => {
|
|
@@ -90,8 +90,8 @@ import {
|
|
|
90
90
|
import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
|
|
91
91
|
import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
|
|
92
92
|
import { RCS_STATUSES, contentType as RCS_CONTENT_TYPE, EMBEDDED } from '../Rcs/constants';
|
|
93
|
-
import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
|
|
94
93
|
import { mapRcsCardContentForConsumerWithResolvedTags } from '../Rcs/utils';
|
|
94
|
+
import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
|
|
95
95
|
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
96
96
|
import { CREATIVE } from '../Facebook/constants';
|
|
97
97
|
import { LOYALTY } from '../App/constants';
|
|
@@ -1442,21 +1442,13 @@ export class Creatives extends React.Component {
|
|
|
1442
1442
|
cardSettings = {},
|
|
1443
1443
|
accountId = "",
|
|
1444
1444
|
} = get(versions, 'base.content.RCS.rcsContent', {});
|
|
1445
|
+
const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
|
|
1445
1446
|
const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
|
|
1446
1447
|
? cardContentFromSubmit[0]
|
|
1447
1448
|
: null;
|
|
1448
|
-
const cardVarMappedFromFirstRcsCard =
|
|
1449
|
-
firstCardFromSubmit?.cardVarMapped != null
|
|
1450
|
-
&& typeof firstCardFromSubmit.cardVarMapped === 'object'
|
|
1451
|
-
? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
|
|
1452
|
-
: null;
|
|
1453
|
-
// Campaigns/consumers should see the literal tag (e.g. `{{first_name}}`) in title/description,
|
|
1454
|
-
// not the internal numeric slot placeholder (`{{1}}`) — that slot scheme is only meaningful
|
|
1455
|
-
// to this editor's own reopen/hydration (see getFormData, which persists raw cardContentFromSubmit
|
|
1456
|
-
// separately for that round-trip and is unaffected by this).
|
|
1457
1449
|
const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
|
|
1458
1450
|
cardContentFromSubmit,
|
|
1459
|
-
|
|
1451
|
+
rootRcsCardVarMappedFromSubmit,
|
|
1460
1452
|
isFullModeForRcsConsumerPayload,
|
|
1461
1453
|
);
|
|
1462
1454
|
const rcsContent = {
|
|
@@ -1465,6 +1457,11 @@ export class Creatives extends React.Component {
|
|
|
1465
1457
|
cardSettings,
|
|
1466
1458
|
cardContent,
|
|
1467
1459
|
};
|
|
1460
|
+
const cardVarMappedFromFirstRcsCard =
|
|
1461
|
+
firstCardFromSubmit?.cardVarMapped != null
|
|
1462
|
+
&& typeof firstCardFromSubmit.cardVarMapped === 'object'
|
|
1463
|
+
? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
|
|
1464
|
+
: null;
|
|
1468
1465
|
const includeRootRcsCardVarMappedOnConsumerPayload =
|
|
1469
1466
|
cardVarMappedFromFirstRcsCard
|
|
1470
1467
|
&& Object.keys(cardVarMappedFromFirstRcsCard).length > 0
|
|
@@ -267,59 +267,6 @@ describe('Test SlideBoxContent container', () => {
|
|
|
267
267
|
expect(instance.state.templateData.rcsCardVarMapped).toEqual(cardVarMapped);
|
|
268
268
|
});
|
|
269
269
|
|
|
270
|
-
it('RCS getCreativesData sends the literal tag (not the numeric slot) in title/description to consumers', async () => {
|
|
271
|
-
renderedComponent = shallowWithIntl(
|
|
272
|
-
<Creatives
|
|
273
|
-
loyaltyMetaData={loyaltyMetaData}
|
|
274
|
-
Templates={rcsTemplates}
|
|
275
|
-
channel="RCS"
|
|
276
|
-
slidBoxContent="editTemplate"
|
|
277
|
-
templateData={rcsEditTemplateData}
|
|
278
|
-
handleCloseCreatives={handleCloseCreatives}
|
|
279
|
-
getCreativesData={getCreativesData}
|
|
280
|
-
isFullMode={false}
|
|
281
|
-
templateActions={{
|
|
282
|
-
getCdnTransformationConfig,
|
|
283
|
-
}}
|
|
284
|
-
/>,
|
|
285
|
-
);
|
|
286
|
-
renderedComponent.instance().onEditTemplate();
|
|
287
|
-
|
|
288
|
-
const mockValue = {
|
|
289
|
-
validity: true,
|
|
290
|
-
value: {
|
|
291
|
-
name: 'rcs_creative_name',
|
|
292
|
-
type: 'RCS',
|
|
293
|
-
versions: {
|
|
294
|
-
base: {
|
|
295
|
-
content: {
|
|
296
|
-
RCS: {
|
|
297
|
-
rcsContent: {
|
|
298
|
-
contentType: 'RICHCARD',
|
|
299
|
-
cardType: 'STANDALONE',
|
|
300
|
-
cardSettings: {},
|
|
301
|
-
cardContent: [{
|
|
302
|
-
title: 'Hi {{1}}',
|
|
303
|
-
description: 'Visit {{2}}',
|
|
304
|
-
cardVarMapped: { 1: '{{first_name}}', 2: '{{loyalty_points}}' },
|
|
305
|
-
}],
|
|
306
|
-
},
|
|
307
|
-
smsFallBackContent: {},
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
},
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
renderedComponent.instance().getFormData(mockValue);
|
|
316
|
-
await tick();
|
|
317
|
-
|
|
318
|
-
const sentCardContent = getCreativesData.mock.calls[0][0].rcsContent.cardContent[0];
|
|
319
|
-
expect(sentCardContent.title).toBe('Hi {{first_name}}');
|
|
320
|
-
expect(sentCardContent.description).toBe('Visit {{loyalty_points}}');
|
|
321
|
-
});
|
|
322
|
-
|
|
323
270
|
it('Text getCreatives data for rcs, data from creatives done to campaigns', async () => {
|
|
324
271
|
renderFunction('SMS', 'editTemplate', smsTemplates, smsEditTemplateData );
|
|
325
272
|
renderedComponent.instance().onEditTemplate();
|
|
@@ -19,8 +19,29 @@ import { sanitizeCardVarMappedValue } from './utils';
|
|
|
19
19
|
|
|
20
20
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Assigns every variable token across the whole carousel a stable `cardVarMapped` key (a plain
|
|
24
|
+
* number, title then description, per card, in encounter order) — {cardIndex: {title: [key, …],
|
|
25
|
+
* description: [key, …]}}. Each occurrence is its own independent slot — even two tokens sharing
|
|
26
|
+
* the same literal tag name (e.g. "{{name}}" in both a card's title and description) get separate
|
|
27
|
+
* keys and are filled independently. A slot's key depends only on where its token sits in the
|
|
28
|
+
* template text, never on which tag currently fills it, so re-picking a different tag for the
|
|
29
|
+
* same slot never changes its key.
|
|
30
|
+
*/
|
|
31
|
+
export const computeCarouselVarSlotKeys = (carouselData, rcsVarRegex) => {
|
|
32
|
+
let nextNumber = 1;
|
|
33
|
+
return (carouselData || []).map((card = {}) => {
|
|
34
|
+
const keysForField = (fieldStr) => (fieldStr || '').match(rcsVarRegex)?.map(() => {
|
|
35
|
+
const key = String(nextNumber);
|
|
36
|
+
nextNumber += 1;
|
|
37
|
+
return key;
|
|
38
|
+
}) ?? [];
|
|
39
|
+
return {
|
|
40
|
+
title: keysForField(card.title),
|
|
41
|
+
description: keysForField(card.description),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
};
|
|
24
45
|
|
|
25
46
|
export const getCarouselImageAssetIndex = (cardIndex) =>
|
|
26
47
|
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
|
|
@@ -147,19 +168,19 @@ export const getCarouselDescriptionCharacterCount = (carouselData, cardIndex) =>
|
|
|
147
168
|
|
|
148
169
|
/**
|
|
149
170
|
* Preview (TestAndPreviewSlidebox / listing preview): resolve each card's title/desc through
|
|
150
|
-
* `resolveCarouselTemplateWithMap(str, cardIndex)` —
|
|
151
|
-
*
|
|
152
|
-
*
|
|
171
|
+
* `resolveCarouselTemplateWithMap(str, cardIndex, isDescField)` — each variable slot's key is its
|
|
172
|
+
* global position across the whole carousel (see `getCarouselGlobalVarSlotKey`), so two cards can
|
|
173
|
+
* never collide even when they pick the same tag name for their own slot.
|
|
153
174
|
*/
|
|
154
175
|
export const buildCarouselCardsForPreview = (cards = [], { isFullMode, resolveCarouselTemplateWithMap }) =>
|
|
155
176
|
(cards || []).map((card = {}, cardIndex) => {
|
|
156
177
|
const rawTitle = card.title || '';
|
|
157
178
|
const rawDesc = card.description || '';
|
|
158
179
|
const resolvedTitle = !isFullMode
|
|
159
|
-
? resolveCarouselTemplateWithMap(rawTitle, cardIndex)
|
|
180
|
+
? resolveCarouselTemplateWithMap(rawTitle, cardIndex, false)
|
|
160
181
|
: rawTitle;
|
|
161
182
|
const resolvedDesc = !isFullMode
|
|
162
|
-
? resolveCarouselTemplateWithMap(rawDesc, cardIndex)
|
|
183
|
+
? resolveCarouselTemplateWithMap(rawDesc, cardIndex, true)
|
|
163
184
|
: rawDesc;
|
|
164
185
|
const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
|
|
165
186
|
const videoSrc = card.videoAsset?.videoSrc || '';
|
|
@@ -180,44 +201,50 @@ export const buildCarouselCardContentForPayload = (carouselData, {
|
|
|
180
201
|
cardVarMapped,
|
|
181
202
|
selectedCarouselHeight,
|
|
182
203
|
rcsVarRegex,
|
|
183
|
-
}) =>
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
}) => {
|
|
205
|
+
const slotKeysByCard = computeCarouselVarSlotKeys(carouselData, rcsVarRegex);
|
|
206
|
+
return (carouselData || []).map((card = {}, cardIndex) => {
|
|
207
|
+
const cardMediaType = card.mediaType;
|
|
208
|
+
const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
209
|
+
const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
210
|
+
const mediaUrl = isCardVideo
|
|
211
|
+
? (card.videoAsset?.videoSrc || '')
|
|
212
|
+
: (card.imageSrc || '');
|
|
213
|
+
const thumbnailUrl = isCardVideo
|
|
214
|
+
? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
|
|
215
|
+
: '';
|
|
216
|
+
const cardVarTokens = [
|
|
217
|
+
...((card.title || '').match(rcsVarRegex) ?? []),
|
|
218
|
+
...((card.description || '').match(rcsVarRegex) ?? []),
|
|
219
|
+
];
|
|
220
|
+
const cardSlotKeys = [
|
|
221
|
+
...(slotKeysByCard[cardIndex]?.title ?? []),
|
|
222
|
+
...(slotKeysByCard[cardIndex]?.description ?? []),
|
|
223
|
+
];
|
|
224
|
+
const cardVarMappedForCard = {};
|
|
225
|
+
cardVarTokens.forEach((token, tokenIndex) => {
|
|
200
226
|
const varName = getVarNameFromToken(token);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
227
|
+
const slotKey = cardSlotKeys[tokenIndex];
|
|
228
|
+
if (!varName || !isSlotMappingMode) return;
|
|
229
|
+
const scopedValue = cardVarMapped?.[slotKey] ?? '';
|
|
230
|
+
cardVarMappedForCard[slotKey] = sanitizeCardVarMappedValue(scopedValue);
|
|
204
231
|
});
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
};
|
|
223
|
-
}
|
|
232
|
+
return {
|
|
233
|
+
title: card.title || '',
|
|
234
|
+
description: card.description || '',
|
|
235
|
+
mediaType: cardMediaType,
|
|
236
|
+
...((isCardImage || isCardVideo) && {
|
|
237
|
+
media: {
|
|
238
|
+
mediaUrl,
|
|
239
|
+
thumbnailUrl,
|
|
240
|
+
height: selectedCarouselHeight || MEDIUM,
|
|
241
|
+
...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
|
|
242
|
+
},
|
|
243
|
+
}),
|
|
244
|
+
...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
|
|
245
|
+
suggestions: card.suggestions,
|
|
246
|
+
}),
|
|
247
|
+
...(Object.keys(cardVarMappedForCard).length > 0 && { cardVarMapped: cardVarMappedForCard }),
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
};
|
|
@@ -15,6 +15,7 @@ import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
|
15
15
|
import TagList from '../../TagList';
|
|
16
16
|
import globalMessages from '../../Cap/messages';
|
|
17
17
|
import messages from '../messages';
|
|
18
|
+
import { RCS_TAG_AREA_FIELD_TITLE, RCS_TAG_AREA_FIELD_DESC } from '../constants';
|
|
18
19
|
import CarouselCardMedia from './CarouselCardMedia';
|
|
19
20
|
import CarouselCardButtons from './CarouselCardButtons';
|
|
20
21
|
import CarouselCharacterCount from './CarouselCharacterCount';
|
|
@@ -50,7 +51,8 @@ const CarouselCard = ({
|
|
|
50
51
|
onDescriptionChange,
|
|
51
52
|
onTitleAddVar,
|
|
52
53
|
onDescriptionAddVar,
|
|
53
|
-
|
|
54
|
+
onTitleTagSelect,
|
|
55
|
+
onDescTagSelect,
|
|
54
56
|
onContextChange,
|
|
55
57
|
renderEditMessage,
|
|
56
58
|
onFieldChange,
|
|
@@ -137,7 +139,7 @@ const CarouselCard = ({
|
|
|
137
139
|
(isEditFlow || !isFullMode) ? (
|
|
138
140
|
<TagList
|
|
139
141
|
label={formatMessage(globalMessages.addLabels)}
|
|
140
|
-
onTagSelect={
|
|
142
|
+
onTagSelect={onTitleTagSelect}
|
|
141
143
|
location={location}
|
|
142
144
|
tags={tagsForPicker}
|
|
143
145
|
onContextChange={onContextChange}
|
|
@@ -159,7 +161,7 @@ const CarouselCard = ({
|
|
|
159
161
|
/>
|
|
160
162
|
<CapRow className="rcs_text_area_wrapper">
|
|
161
163
|
{(isEditFlow || !isFullMode) ? (
|
|
162
|
-
renderEditMessage(card.title || '', cardIndex)
|
|
164
|
+
renderEditMessage(card.title || '', cardIndex, RCS_TAG_AREA_FIELD_TITLE)
|
|
163
165
|
) : (
|
|
164
166
|
<CapInput
|
|
165
167
|
value={card.title || ''}
|
|
@@ -189,7 +191,7 @@ const CarouselCard = ({
|
|
|
189
191
|
(isEditFlow || !isFullMode) ? (
|
|
190
192
|
<TagList
|
|
191
193
|
label={formatMessage(globalMessages.addLabels)}
|
|
192
|
-
onTagSelect={
|
|
194
|
+
onTagSelect={onDescTagSelect}
|
|
193
195
|
location={location}
|
|
194
196
|
tags={tagsForPicker}
|
|
195
197
|
onContextChange={onContextChange}
|
|
@@ -211,7 +213,7 @@ const CarouselCard = ({
|
|
|
211
213
|
/>
|
|
212
214
|
<CapRow className="rcs_text_area_wrapper">
|
|
213
215
|
{(isEditFlow || !isFullMode) ? (
|
|
214
|
-
renderEditMessage(card.description || '', cardIndex)
|
|
216
|
+
renderEditMessage(card.description || '', cardIndex, RCS_TAG_AREA_FIELD_DESC)
|
|
215
217
|
) : (
|
|
216
218
|
<TextArea
|
|
217
219
|
autosize={{ minRows: 3, maxRows: 5 }}
|
|
@@ -249,11 +251,6 @@ const CarouselCard = ({
|
|
|
249
251
|
hostName={hostName}
|
|
250
252
|
formatMessage={formatMessage}
|
|
251
253
|
onSuggestionsChange={onSuggestionsChange}
|
|
252
|
-
location={location}
|
|
253
|
-
tags={tagsForPicker}
|
|
254
|
-
injectedTags={injectedTags}
|
|
255
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
256
|
-
onContextChange={onContextChange}
|
|
257
254
|
/>
|
|
258
255
|
</CapCard>
|
|
259
256
|
);
|
|
@@ -286,7 +283,8 @@ CarouselCard.propTypes = {
|
|
|
286
283
|
onDescriptionChange: PropTypes.func.isRequired,
|
|
287
284
|
onTitleAddVar: PropTypes.func.isRequired,
|
|
288
285
|
onDescriptionAddVar: PropTypes.func.isRequired,
|
|
289
|
-
|
|
286
|
+
onTitleTagSelect: PropTypes.func.isRequired,
|
|
287
|
+
onDescTagSelect: PropTypes.func.isRequired,
|
|
290
288
|
onContextChange: PropTypes.func,
|
|
291
289
|
renderEditMessage: PropTypes.func.isRequired,
|
|
292
290
|
onFieldChange: PropTypes.func.isRequired,
|
|
@@ -18,11 +18,6 @@ const CarouselCardButtons = ({
|
|
|
18
18
|
hostName,
|
|
19
19
|
formatMessage,
|
|
20
20
|
onSuggestionsChange,
|
|
21
|
-
location,
|
|
22
|
-
tags,
|
|
23
|
-
injectedTags,
|
|
24
|
-
selectedOfferDetails,
|
|
25
|
-
onContextChange,
|
|
26
21
|
}) => {
|
|
27
22
|
const suggestionsForCard = suggestions || [];
|
|
28
23
|
return (
|
|
@@ -73,11 +68,6 @@ const CarouselCardButtons = ({
|
|
|
73
68
|
host={hostName}
|
|
74
69
|
minSavedSuggestions={cardIndex === 0 ? 1 : 0}
|
|
75
70
|
hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
|
|
76
|
-
location={location}
|
|
77
|
-
tags={tags}
|
|
78
|
-
injectedTags={injectedTags}
|
|
79
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
80
|
-
onContextChange={onContextChange}
|
|
81
71
|
/>
|
|
82
72
|
</>
|
|
83
73
|
);
|
|
@@ -91,11 +81,6 @@ CarouselCardButtons.propTypes = {
|
|
|
91
81
|
hostName: PropTypes.string,
|
|
92
82
|
formatMessage: PropTypes.func.isRequired,
|
|
93
83
|
onSuggestionsChange: PropTypes.func.isRequired,
|
|
94
|
-
location: PropTypes.object,
|
|
95
|
-
tags: PropTypes.array,
|
|
96
|
-
injectedTags: PropTypes.object,
|
|
97
|
-
selectedOfferDetails: PropTypes.array,
|
|
98
|
-
onContextChange: PropTypes.func,
|
|
99
84
|
};
|
|
100
85
|
|
|
101
86
|
CarouselCardButtons.defaultProps = {
|
|
@@ -103,11 +88,6 @@ CarouselCardButtons.defaultProps = {
|
|
|
103
88
|
isEditFlow: false,
|
|
104
89
|
isFullMode: false,
|
|
105
90
|
hostName: '',
|
|
106
|
-
location: {},
|
|
107
|
-
tags: [],
|
|
108
|
-
injectedTags: {},
|
|
109
|
-
selectedOfferDetails: [],
|
|
110
|
-
onContextChange: () => {},
|
|
111
91
|
};
|
|
112
92
|
|
|
113
93
|
export default React.memo(CarouselCardButtons);
|
|
@@ -78,8 +78,13 @@ export const rcsVarTestRegex = /^\{\{[^}]+\}\}$/;
|
|
|
78
78
|
export const RCS_NUMERIC_VAR_TOKEN_REGEX = /\{\{(\d+)\}\}/g;
|
|
79
79
|
/** `cardVarMapped` slot keys that are numeric only (legacy ordering). */
|
|
80
80
|
export const RCS_NUMERIC_VAR_NAME_REGEX = /^\d+$/;
|
|
81
|
-
/**
|
|
82
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Semantic Liquid-style keys on RCS `cardVarMapped` (same class as `{{…}}` inner names in the
|
|
83
|
+
* editor). Carousel slots are now keyed by global position (see `computeCarouselVarSlotKeys`), a
|
|
84
|
+
* plain number, so this only needs to match bare semantic names — the optional `cN::` prefix is
|
|
85
|
+
* kept solely so `pickRcsCardVarMappedEntries` doesn't drop legacy scoped keys from older payloads.
|
|
86
|
+
*/
|
|
87
|
+
export const RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX = /^(?:c\d+::)?[\w.()]+$/;
|
|
83
88
|
/** Escape all special RegExp characters in an arbitrary string before using it in `new RegExp(...)`. */
|
|
84
89
|
export const REGEX_SPECIAL_CHARS_ESCAPE_PATTERN = /[-/\\^$*+?.()|[\]{}]/g;
|
|
85
90
|
/** Entire string is wrapped in `{{…}}` mustache delimiters (allows any content including `}`). */
|