@capillarytech/creatives-library 9.0.33 → 9.0.35-alpha.0
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/package.json +1 -1
- package/v2Containers/CreativesContainer/index.js +23 -1
- package/v2Containers/Rcs/index.js +59 -23
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +5 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +0 -1
- package/v2Containers/TemplatesV2/index.js +1 -9
package/package.json
CHANGED
|
@@ -91,6 +91,7 @@ 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
93
|
import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
|
|
94
|
+
import { resolveRcsCardPreviewStrings, isRcsTextOnlyCardMediaType } from '../Rcs/utils';
|
|
94
95
|
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
95
96
|
import { CREATIVE } from '../Facebook/constants';
|
|
96
97
|
import { LOYALTY } from '../App/constants';
|
|
@@ -1444,7 +1445,28 @@ export class Creatives extends React.Component {
|
|
|
1444
1445
|
const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
|
|
1445
1446
|
? cardContentFromSubmit[0]
|
|
1446
1447
|
: null;
|
|
1447
|
-
const
|
|
1448
|
+
const isLibraryModeForRcsConsumerPayload = isFullModeForRcsConsumerPayload !== true;
|
|
1449
|
+
// Consumers (e.g. campaigns) display `cardDisplayTitle`/`cardDisplayDescription` in
|
|
1450
|
+
// preference to `title`/`description` when present (see getRcsCardDisplayTitle/
|
|
1451
|
+
// Description) but getTemplateData strips them before re-hydrating this editor — so
|
|
1452
|
+
// populating them here with resolved tag values is display-only and never feeds back
|
|
1453
|
+
// into `templateTitle`/`templateDesc` on reopen. Keep raw `title`/`description` as-is.
|
|
1454
|
+
const cardContent = Array.isArray(cardContentFromSubmit)
|
|
1455
|
+
? cardContentFromSubmit.map((card = {}) => {
|
|
1456
|
+
const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
|
|
1457
|
+
card.title || '',
|
|
1458
|
+
card.description || '',
|
|
1459
|
+
card.cardVarMapped || {},
|
|
1460
|
+
isLibraryModeForRcsConsumerPayload,
|
|
1461
|
+
isRcsTextOnlyCardMediaType(card.mediaType),
|
|
1462
|
+
);
|
|
1463
|
+
return {
|
|
1464
|
+
...card,
|
|
1465
|
+
cardDisplayTitle: rcsTitle,
|
|
1466
|
+
cardDisplayDescription: rcsDesc,
|
|
1467
|
+
};
|
|
1468
|
+
})
|
|
1469
|
+
: cardContentFromSubmit;
|
|
1448
1470
|
const rcsContent = {
|
|
1449
1471
|
contentType,
|
|
1450
1472
|
cardType,
|
|
@@ -1066,9 +1066,9 @@ export const Rcs = (props) => {
|
|
|
1066
1066
|
if (mediaType) {
|
|
1067
1067
|
setTemplateMediaType(mediaType);
|
|
1068
1068
|
}
|
|
1069
|
-
const tempOrientation = cardSettings
|
|
1070
|
-
const tempAlignment = cardSettings
|
|
1071
|
-
const tempHeight = mediaData
|
|
1069
|
+
const tempOrientation = cardSettings?.cardOrientation;
|
|
1070
|
+
const tempAlignment = cardSettings?.mediaAlignment;
|
|
1071
|
+
const tempHeight = mediaData?.height;
|
|
1072
1072
|
|
|
1073
1073
|
// Map to actual RCS_IMAGE_DIMENSIONS keys
|
|
1074
1074
|
let tempDimension;
|
|
@@ -1432,6 +1432,31 @@ export const Rcs = (props) => {
|
|
|
1432
1432
|
return templateStr.replace(placeholderRegex, `{{${tagName}}}`);
|
|
1433
1433
|
};
|
|
1434
1434
|
|
|
1435
|
+
/**
|
|
1436
|
+
* Replaces only the var token at `slotOrdinalZeroBased` (the field-local ordinal among
|
|
1437
|
+
* `{{...}}` tokens, left to right) with `{{tagName}}`. Unlike a name-based regex replace,
|
|
1438
|
+
* this targets a single slot even when the same tag name appears in other slots of the field.
|
|
1439
|
+
*/
|
|
1440
|
+
const replaceTagAtFieldSlotOrdinal = (templateStr, slotOrdinalZeroBased, tagName) => {
|
|
1441
|
+
if (!templateStr || slotOrdinalZeroBased === null || slotOrdinalZeroBased === undefined || !tagName) {
|
|
1442
|
+
return templateStr;
|
|
1443
|
+
}
|
|
1444
|
+
let varOrdinal = 0;
|
|
1445
|
+
let replaced = false;
|
|
1446
|
+
const updatedSegments = splitTemplateVarStringRcs(templateStr).map((segment) => {
|
|
1447
|
+
if (rcsVarTestRegex.test(segment)) {
|
|
1448
|
+
const currentOrdinal = varOrdinal;
|
|
1449
|
+
varOrdinal += 1;
|
|
1450
|
+
if (currentOrdinal === slotOrdinalZeroBased) {
|
|
1451
|
+
replaced = true;
|
|
1452
|
+
return `{{${tagName}}}`;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return segment;
|
|
1456
|
+
});
|
|
1457
|
+
return replaced ? updatedSegments.join('') : templateStr;
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1435
1460
|
const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
|
|
1436
1461
|
if (!varSegmentCompositeDomId) return;
|
|
1437
1462
|
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
@@ -1497,36 +1522,47 @@ export const Rcs = (props) => {
|
|
|
1497
1522
|
return updatedCardVarMapped;
|
|
1498
1523
|
});
|
|
1499
1524
|
|
|
1500
|
-
if (
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1525
|
+
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC) {
|
|
1526
|
+
// A numeric slot (e.g. {{1}}) is replaced by its unique placeholder text; a slot that
|
|
1527
|
+
// already holds a named tag (e.g. {{first_name}}) is replaced by field-local position,
|
|
1528
|
+
// since a name-based replace would also overwrite other slots sharing that same tag name.
|
|
1529
|
+
const fieldOffsetForSlot = tagAreaField === RCS_TAG_AREA_FIELD_TITLE
|
|
1530
|
+
? 0
|
|
1531
|
+
: (templateTitle?.match(rcsVarRegex) ?? []).length;
|
|
1532
|
+
const localSlotOrdinal = (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined)
|
|
1533
|
+
? globalVarSlotIndexZeroBased - fieldOffsetForSlot
|
|
1534
|
+
: null;
|
|
1535
|
+
|
|
1504
1536
|
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
|
|
1505
1537
|
setTemplateTitle((previousTitle) => {
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1538
|
+
const titleAfterReplacingTag = isNumericPlaceholderSlot
|
|
1539
|
+
? replaceNumericPlaceholderWithTagInTemplate(
|
|
1540
|
+
previousTitle || '',
|
|
1541
|
+
semanticOrNumericVarName,
|
|
1542
|
+
selectedTagNameFromPicker,
|
|
1543
|
+
)
|
|
1544
|
+
: replaceTagAtFieldSlotOrdinal(previousTitle || '', localSlotOrdinal, selectedTagNameFromPicker);
|
|
1545
|
+
if (titleAfterReplacingTag === previousTitle) return previousTitle;
|
|
1546
|
+
setTemplateTitleError(variableErrorHandling(titleAfterReplacingTag));
|
|
1513
1547
|
// Remount segment editor: tag insert replaces {{n}} with e.g. {{tag.FORMAT_1}} — slot ids change; avoids stale UI vs manual typing in full-mode TextArea
|
|
1514
1548
|
setRcsVarSegmentEditorRemountKey((remountKey) => remountKey + 1);
|
|
1515
|
-
return
|
|
1549
|
+
return titleAfterReplacingTag;
|
|
1516
1550
|
});
|
|
1517
1551
|
} else {
|
|
1518
1552
|
setTemplateDesc((previousDescription) => {
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1553
|
+
const descriptionAfterReplacingTag = isNumericPlaceholderSlot
|
|
1554
|
+
? replaceNumericPlaceholderWithTagInTemplate(
|
|
1555
|
+
previousDescription || '',
|
|
1556
|
+
semanticOrNumericVarName,
|
|
1557
|
+
selectedTagNameFromPicker,
|
|
1558
|
+
)
|
|
1559
|
+
: replaceTagAtFieldSlotOrdinal(previousDescription || '', localSlotOrdinal, selectedTagNameFromPicker);
|
|
1560
|
+
if (descriptionAfterReplacingTag === previousDescription) {
|
|
1525
1561
|
return previousDescription;
|
|
1526
1562
|
}
|
|
1527
|
-
setTemplateDescError(variableErrorHandling(
|
|
1563
|
+
setTemplateDescError(variableErrorHandling(descriptionAfterReplacingTag));
|
|
1528
1564
|
setRcsVarSegmentEditorRemountKey((remountKey) => remountKey + 1);
|
|
1529
|
-
return
|
|
1565
|
+
return descriptionAfterReplacingTag;
|
|
1530
1566
|
});
|
|
1531
1567
|
}
|
|
1532
1568
|
}
|
|
@@ -239,6 +239,7 @@ exports[`Test Templates container Should render sms illustration when no templat
|
|
|
239
239
|
<CapButton
|
|
240
240
|
className="create-new-sms margin-l-8 margin-b-12"
|
|
241
241
|
disabled={false}
|
|
242
|
+
isAddBtn={true}
|
|
242
243
|
onClick={[Function]}
|
|
243
244
|
type="primary"
|
|
244
245
|
>
|
|
@@ -813,6 +814,7 @@ exports[`Test Templates container Should render temlates when whatsapp templates
|
|
|
813
814
|
<CapButton
|
|
814
815
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
815
816
|
disabled={false}
|
|
817
|
+
isAddBtn={true}
|
|
816
818
|
onClick={[Function]}
|
|
817
819
|
type="primary"
|
|
818
820
|
>
|
|
@@ -1223,6 +1225,7 @@ exports[`Test Templates container Test max templates exceeded 1`] = `
|
|
|
1223
1225
|
<CapButton
|
|
1224
1226
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
1225
1227
|
disabled={false}
|
|
1228
|
+
isAddBtn={true}
|
|
1226
1229
|
onClick={[Function]}
|
|
1227
1230
|
type="primary"
|
|
1228
1231
|
>
|
|
@@ -1696,6 +1699,7 @@ exports[`Test Templates container Test max templates not exceeded 1`] = `
|
|
|
1696
1699
|
<CapButton
|
|
1697
1700
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
1698
1701
|
disabled={false}
|
|
1702
|
+
isAddBtn={true}
|
|
1699
1703
|
onClick={[Function]}
|
|
1700
1704
|
type="primary"
|
|
1701
1705
|
>
|
|
@@ -2169,6 +2173,7 @@ exports[`Test Templates container Test max templates warning 1`] = `
|
|
|
2169
2173
|
<CapButton
|
|
2170
2174
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
2171
2175
|
disabled={false}
|
|
2176
|
+
isAddBtn={true}
|
|
2172
2177
|
onClick={[Function]}
|
|
2173
2178
|
type="primary"
|
|
2174
2179
|
>
|
|
@@ -10,7 +10,7 @@ import { connect } from 'react-redux';
|
|
|
10
10
|
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
|
11
11
|
import { createStructuredSelector } from 'reselect';
|
|
12
12
|
import { bindActionCreators, compose } from 'redux';
|
|
13
|
-
import { CapTab, CapCustomCard, CapButton,
|
|
13
|
+
import { CapTab, CapCustomCard, CapButton, CapIcon, CapSpin, CapTooltip } from '@capillarytech/cap-ui-library';
|
|
14
14
|
import { find, get, pick } from 'lodash';
|
|
15
15
|
import Helmet from 'react-helmet';
|
|
16
16
|
|
|
@@ -443,14 +443,6 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
443
443
|
/>
|
|
444
444
|
)}
|
|
445
445
|
<section className="component-wrapper">
|
|
446
|
-
{isFullMode && (
|
|
447
|
-
<CapHeader
|
|
448
|
-
title={<FormattedMessage {...messages.creatives} />}
|
|
449
|
-
{...(!useLocalTemplates && {
|
|
450
|
-
description: <FormattedMessage {...messages.creativesDesc} />,
|
|
451
|
-
})}
|
|
452
|
-
/>
|
|
453
|
-
)}
|
|
454
446
|
{hideChannelTabsForLocalSms ? (
|
|
455
447
|
<section className="templates-v2-local-sms-pane">{activeLocalPane?.content}</section>
|
|
456
448
|
) : (
|