@capillarytech/creatives-library 8.0.329 → 8.0.330-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/constants/unified.js +4 -0
- package/package.json +1 -1
- package/utils/commonUtils.js +19 -1
- package/utils/templateVarUtils.js +35 -6
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CommonTestAndPreview/index.js +49 -57
- package/v2Components/SmsFallback/smsFallbackUtils.js +14 -3
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +16 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +5 -0
- package/v2Containers/CreativesContainer/index.js +9 -3
- package/v2Containers/Rcs/constants.js +6 -2
- package/v2Containers/Rcs/index.js +218 -83
- package/v2Containers/Rcs/messages.js +2 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +20 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +484 -4
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +67 -0
- package/v2Containers/Rcs/tests/utils.test.js +56 -0
- package/v2Containers/Rcs/utils.js +53 -6
- package/v2Containers/SmsTrai/Edit/index.js +27 -0
- package/v2Containers/SmsTrai/Edit/messages.js +5 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
|
@@ -44,10 +44,12 @@ import {
|
|
|
44
44
|
normalizeLibraryLoadedTitleDesc,
|
|
45
45
|
mergeRcsSmsFallBackContentFromDetails,
|
|
46
46
|
mergeRcsSmsFallbackVarMapLayers,
|
|
47
|
+
extractRegisteredSenderIdsFromSmsFallbackRecord,
|
|
47
48
|
pickFirstSmsFallbackTemplateString,
|
|
48
49
|
syncCardVarMappedSemanticsFromSlots,
|
|
49
50
|
hasMeaningfulSmsFallbackShape,
|
|
50
51
|
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
52
|
+
pickRcsCardVarMappedEntries,
|
|
51
53
|
} from './rcsLibraryHydrationUtils';
|
|
52
54
|
import {
|
|
53
55
|
RCS,
|
|
@@ -118,6 +120,7 @@ import {
|
|
|
118
120
|
getTemplateStatusType,
|
|
119
121
|
normalizeCardVarMapped,
|
|
120
122
|
coalesceCardVarMappedToTemplate,
|
|
123
|
+
getRcsSemanticVarNamesSpanningTitleAndDesc,
|
|
121
124
|
resolveCardVarMappedSlotValue,
|
|
122
125
|
sanitizeCardVarMappedValue,
|
|
123
126
|
} from './utils';
|
|
@@ -386,6 +389,12 @@ export const Rcs = (props) => {
|
|
|
386
389
|
|
|
387
390
|
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
388
391
|
|
|
392
|
+
/** Same `{{tag}}` in both title and description must not share one semantic map entry. */
|
|
393
|
+
const rcsSpanningSemanticVarNames = useMemo(
|
|
394
|
+
() => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
|
|
395
|
+
[templateTitle, templateDesc],
|
|
396
|
+
);
|
|
397
|
+
|
|
389
398
|
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
390
399
|
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
391
400
|
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
@@ -418,6 +427,7 @@ export const Rcs = (props) => {
|
|
|
418
427
|
key,
|
|
419
428
|
globalSlot,
|
|
420
429
|
isEditLike,
|
|
430
|
+
rcsSpanningSemanticVarNames.has(key),
|
|
421
431
|
);
|
|
422
432
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
423
433
|
return String(v);
|
|
@@ -484,6 +494,7 @@ export const Rcs = (props) => {
|
|
|
484
494
|
isFullMode,
|
|
485
495
|
isEditFlow,
|
|
486
496
|
cardVarMapped,
|
|
497
|
+
rcsSpanningSemanticVarNames,
|
|
487
498
|
]);
|
|
488
499
|
|
|
489
500
|
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
@@ -514,7 +525,7 @@ export const Rcs = (props) => {
|
|
|
514
525
|
const nextVarMap = {};
|
|
515
526
|
let varOrdinal = 0;
|
|
516
527
|
arr.forEach((elem, idx) => {
|
|
517
|
-
//
|
|
528
|
+
// Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
|
|
518
529
|
if (rcsVarTestRegex.test(elem)) {
|
|
519
530
|
const id = `${elem}_${idx}`;
|
|
520
531
|
const varName = getVarNameFromToken(elem);
|
|
@@ -525,6 +536,7 @@ export const Rcs = (props) => {
|
|
|
525
536
|
varName,
|
|
526
537
|
globalSlot,
|
|
527
538
|
isEditLike,
|
|
539
|
+
rcsSpanningSemanticVarNames.has(varName),
|
|
528
540
|
);
|
|
529
541
|
nextVarMap[id] = mappedValue;
|
|
530
542
|
}
|
|
@@ -534,7 +546,7 @@ export const Rcs = (props) => {
|
|
|
534
546
|
|
|
535
547
|
initField(templateTitle, setTitleVarMappedData, 0);
|
|
536
548
|
initField(templateDesc, setDescVarMappedData, titleTokenCount);
|
|
537
|
-
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
549
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames]);
|
|
538
550
|
|
|
539
551
|
useEffect(() => {
|
|
540
552
|
if (!isEditFlow && isFullMode) {
|
|
@@ -780,12 +792,17 @@ export const Rcs = (props) => {
|
|
|
780
792
|
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
781
793
|
? smsFallbackContent.unicodeValidity
|
|
782
794
|
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
795
|
+
const registeredSenderIdsFromApi =
|
|
796
|
+
extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
|
|
783
797
|
const nextSmsState = {
|
|
784
798
|
templateName: smsFallbackContent.smsTemplateName || '',
|
|
785
799
|
content: fallbackMessage,
|
|
786
800
|
templateContent: fallbackMessage,
|
|
787
801
|
unicodeValidity: unicodeFromApi,
|
|
788
802
|
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
803
|
+
...(Array.isArray(registeredSenderIdsFromApi) && registeredSenderIdsFromApi.length > 0
|
|
804
|
+
? { registeredSenderIds: registeredSenderIdsFromApi }
|
|
805
|
+
: {}),
|
|
789
806
|
};
|
|
790
807
|
const hydrationKey = JSON.stringify({
|
|
791
808
|
creativeKey: details._id || details.name || details.creativeName || '',
|
|
@@ -793,6 +810,10 @@ export const Rcs = (props) => {
|
|
|
793
810
|
content: nextSmsState.content,
|
|
794
811
|
unicodeValidity: nextSmsState.unicodeValidity,
|
|
795
812
|
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
813
|
+
senderIds:
|
|
814
|
+
Array.isArray(registeredSenderIdsFromApi)
|
|
815
|
+
? registeredSenderIdsFromApi.join('\u001f')
|
|
816
|
+
: '',
|
|
796
817
|
});
|
|
797
818
|
if (
|
|
798
819
|
isFullMode
|
|
@@ -892,58 +913,87 @@ export const Rcs = (props) => {
|
|
|
892
913
|
return templateStr.replace(re, `{{${tagName}}}`);
|
|
893
914
|
};
|
|
894
915
|
|
|
895
|
-
const onTagSelect = (
|
|
896
|
-
if (!
|
|
897
|
-
const
|
|
898
|
-
if (
|
|
899
|
-
const
|
|
900
|
-
if (
|
|
901
|
-
const
|
|
902
|
-
const
|
|
903
|
-
if (!
|
|
904
|
-
const
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
+
const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
|
|
917
|
+
if (!varSegmentCompositeDomId) return;
|
|
918
|
+
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
919
|
+
if (underscoreIndexInCompositeId === -1) return;
|
|
920
|
+
const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
|
|
921
|
+
if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
|
|
922
|
+
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
923
|
+
const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
924
|
+
if (!semanticOrNumericVarName) return;
|
|
925
|
+
const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
|
|
926
|
+
const templateStringForField =
|
|
927
|
+
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
928
|
+
const titleOrMessageFieldType =
|
|
929
|
+
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
930
|
+
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
931
|
+
varSegmentCompositeDomId,
|
|
932
|
+
templateStringForField,
|
|
933
|
+
titleOrMessageFieldType,
|
|
934
|
+
);
|
|
935
|
+
const cardVarMappedNumericSlotKey =
|
|
936
|
+
globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
|
|
937
|
+
? String(globalVarSlotIndexZeroBased + 1)
|
|
938
|
+
: null;
|
|
939
|
+
|
|
940
|
+
setCardVarMapped((previousCardVarMapped) => {
|
|
941
|
+
const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
|
|
942
|
+
if (isNumericPlaceholderSlot) {
|
|
943
|
+
const existingValueBeforeAppend = (
|
|
944
|
+
previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
|
|
945
|
+
).toString();
|
|
946
|
+
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
947
|
+
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
948
|
+
updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
|
|
916
949
|
} else {
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
950
|
+
// Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
|
|
951
|
+
// "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
|
|
952
|
+
// read/write the global numeric slot only and drop the shared semantic key.
|
|
953
|
+
const existingValueBeforeAppend = cardVarMappedNumericSlotKey
|
|
954
|
+
? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
|
|
955
|
+
: String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
|
|
956
|
+
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
957
|
+
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
958
|
+
if (cardVarMappedNumericSlotKey) {
|
|
959
|
+
updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
|
|
926
960
|
} else {
|
|
927
|
-
|
|
961
|
+
updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
|
|
928
962
|
}
|
|
929
963
|
}
|
|
930
|
-
return
|
|
964
|
+
return updatedCardVarMapped;
|
|
931
965
|
});
|
|
932
966
|
|
|
933
|
-
if (
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
967
|
+
if (
|
|
968
|
+
isNumericPlaceholderSlot
|
|
969
|
+
&& (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
|
|
970
|
+
) {
|
|
971
|
+
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
|
|
972
|
+
setTemplateTitle((previousTitle) => {
|
|
973
|
+
const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
974
|
+
previousTitle || '',
|
|
975
|
+
semanticOrNumericVarName,
|
|
976
|
+
selectedTagNameFromPicker,
|
|
977
|
+
);
|
|
978
|
+
if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
|
|
979
|
+
setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
|
|
980
|
+
// 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
|
|
981
|
+
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
982
|
+
return titleAfterReplacingNumericPlaceholder;
|
|
940
983
|
});
|
|
941
984
|
} else {
|
|
942
|
-
setTemplateDesc((
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
985
|
+
setTemplateDesc((previousDescription) => {
|
|
986
|
+
const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
987
|
+
previousDescription || '',
|
|
988
|
+
semanticOrNumericVarName,
|
|
989
|
+
selectedTagNameFromPicker,
|
|
990
|
+
);
|
|
991
|
+
if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
|
|
992
|
+
return previousDescription;
|
|
993
|
+
}
|
|
994
|
+
setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
|
|
995
|
+
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
996
|
+
return descriptionAfterReplacingNumericPlaceholder;
|
|
947
997
|
});
|
|
948
998
|
}
|
|
949
999
|
}
|
|
@@ -1101,7 +1151,8 @@ export const Rcs = (props) => {
|
|
|
1101
1151
|
if(!isFullMode){
|
|
1102
1152
|
return false;
|
|
1103
1153
|
}
|
|
1104
|
-
|
|
1154
|
+
// Allow Liquid-style param names: letters, digits, underscore, dots (e.g. dynamic_expiry_date_after_3_days.FORMAT_1)
|
|
1155
|
+
if (!/^[\w.]+$/.test(paramName)) {
|
|
1105
1156
|
return formatMessage(messages.unknownCharactersError);
|
|
1106
1157
|
}
|
|
1107
1158
|
}
|
|
@@ -1214,6 +1265,7 @@ const onTitleAddVar = () => {
|
|
|
1214
1265
|
varName,
|
|
1215
1266
|
globalSlot,
|
|
1216
1267
|
isEditLike,
|
|
1268
|
+
rcsSpanningSemanticVarNames.has(varName),
|
|
1217
1269
|
);
|
|
1218
1270
|
}
|
|
1219
1271
|
});
|
|
@@ -1222,42 +1274,37 @@ const onTitleAddVar = () => {
|
|
|
1222
1274
|
|
|
1223
1275
|
const titleVarSegmentValueMapById = useMemo(
|
|
1224
1276
|
() => getRcsValueMap(templateTitle, TITLE_TEXT),
|
|
1225
|
-
[templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1277
|
+
[templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
|
|
1226
1278
|
);
|
|
1227
1279
|
const descriptionVarSegmentValueMapById = useMemo(
|
|
1228
1280
|
() => getRcsValueMap(templateDesc, MESSAGE_TEXT),
|
|
1229
|
-
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1281
|
+
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
|
|
1230
1282
|
);
|
|
1231
1283
|
|
|
1232
|
-
const handleRcsVarChange = (
|
|
1233
|
-
const
|
|
1234
|
-
if (
|
|
1235
|
-
const
|
|
1236
|
-
const variableName = getVarNameFromToken(
|
|
1284
|
+
const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
|
|
1285
|
+
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
1286
|
+
if (underscoreIndexInCompositeId === -1) return;
|
|
1287
|
+
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
1288
|
+
const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
1237
1289
|
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
1238
1290
|
const isInvalidValue = value?.trim() === '';
|
|
1239
1291
|
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
1240
|
-
const
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
if (variableName !== numericKey) {
|
|
1255
|
-
delete nextVarMap[variableName];
|
|
1256
|
-
}
|
|
1257
|
-
} else {
|
|
1258
|
-
nextVarMap[variableName] = coercedSlotValue;
|
|
1292
|
+
const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1293
|
+
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
1294
|
+
varSegmentCompositeDomId,
|
|
1295
|
+
templateStringForField,
|
|
1296
|
+
type,
|
|
1297
|
+
);
|
|
1298
|
+
setCardVarMapped((previousCardVarMapped) => {
|
|
1299
|
+
const updatedCardVarMapped = { ...previousCardVarMapped };
|
|
1300
|
+
// Remove stale semantic key: keeping it causes every other slot sharing the same
|
|
1301
|
+
// variable name (e.g. {{adv}} in both title and description) to read the same value
|
|
1302
|
+
// via the semantic-key fallback in resolveCardVarMappedSlotValue.
|
|
1303
|
+
delete updatedCardVarMapped[variableName];
|
|
1304
|
+
if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
|
|
1305
|
+
updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
|
|
1259
1306
|
}
|
|
1260
|
-
return
|
|
1307
|
+
return updatedCardVarMapped;
|
|
1261
1308
|
});
|
|
1262
1309
|
};
|
|
1263
1310
|
|
|
@@ -1375,6 +1422,7 @@ const onTitleAddVar = () => {
|
|
|
1375
1422
|
</CapRow>
|
|
1376
1423
|
<CapRow className="rcs-create-template-message-input">
|
|
1377
1424
|
<div className="rcs_text_area_wrapper">
|
|
1425
|
+
{/* Edit/library: segmented inputs (split on {{…}}). Full-mode create: single TextArea below — manual entry there never hits segment split. TagList replaces {{n}} in template string here. */}
|
|
1378
1426
|
{(isEditFlow || !isFullMode)
|
|
1379
1427
|
? (
|
|
1380
1428
|
<VarSegmentMessageEditor
|
|
@@ -1990,23 +2038,25 @@ const onTitleAddVar = () => {
|
|
|
1990
2038
|
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
1991
2039
|
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
1992
2040
|
];
|
|
2041
|
+
const cardVarMappedForRcsCardOnly = pickRcsCardVarMappedEntries(
|
|
2042
|
+
cardVarMapped,
|
|
2043
|
+
);
|
|
2044
|
+
// Persist numeric slot keys only ("1","2",…) — avoids duplicating the same value under
|
|
2045
|
+
// semantic names (e.g. both "1" and dynamic_expiry_date_after_3_days.FORMAT_1). Hydration
|
|
2046
|
+
// and coalesceCardVarMappedToTemplate still resolve from numeric keys + template tokens.
|
|
1993
2047
|
const persistedSlotVarMap = {};
|
|
1994
|
-
const seenSemanticVarNames = new Set();
|
|
1995
2048
|
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
1996
2049
|
const varName = getVarNameFromToken(token);
|
|
1997
2050
|
if (!varName) return;
|
|
1998
2051
|
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
1999
|
-
|
|
2052
|
+
cardVarMappedForRcsCardOnly,
|
|
2000
2053
|
varName,
|
|
2001
2054
|
slotIndexZeroBased,
|
|
2002
2055
|
isSlotMappingMode,
|
|
2056
|
+
rcsSpanningSemanticVarNames.has(varName),
|
|
2003
2057
|
);
|
|
2004
2058
|
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
2005
2059
|
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
2006
|
-
if (!seenSemanticVarNames.has(varName)) {
|
|
2007
|
-
seenSemanticVarNames.add(varName);
|
|
2008
|
-
persistedSlotVarMap[varName] = sanitizedSlotValue;
|
|
2009
|
-
}
|
|
2010
2060
|
});
|
|
2011
2061
|
return { cardVarMapped: persistedSlotVarMap };
|
|
2012
2062
|
})()),
|
|
@@ -2023,6 +2073,53 @@ const onTitleAddVar = () => {
|
|
|
2023
2073
|
|| smsFallbackForPayload.message
|
|
2024
2074
|
|| smsFallbackForPayload.smsContent
|
|
2025
2075
|
|| '';
|
|
2076
|
+
/**
|
|
2077
|
+
* Campaigns `getTraiSenderIds` / Iris read `smsFallBackContent.templateConfigs.registeredSenderIds`.
|
|
2078
|
+
* Library `smsFallbackForPayload` omits ids — use merged state (`smsFallbackMerged`) like test preview.
|
|
2079
|
+
*/
|
|
2080
|
+
const m = smsFallbackMerged || {};
|
|
2081
|
+
const tcSibling = m.templateConfigs && typeof m.templateConfigs === 'object'
|
|
2082
|
+
? m.templateConfigs
|
|
2083
|
+
: {};
|
|
2084
|
+
const smsFallbackTemplateId =
|
|
2085
|
+
(m.smsTemplateId != null && String(m.smsTemplateId).trim() !== ''
|
|
2086
|
+
? String(m.smsTemplateId)
|
|
2087
|
+
: '')
|
|
2088
|
+
|| (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
|
|
2089
|
+
? String(tcSibling.templateId)
|
|
2090
|
+
: '');
|
|
2091
|
+
const smsFallbackTemplateStr =
|
|
2092
|
+
pickFirstSmsFallbackTemplateString(m)
|
|
2093
|
+
|| (typeof m.templateContent === 'string' ? m.templateContent : '')
|
|
2094
|
+
|| (typeof tcSibling.template === 'string' ? tcSibling.template : '')
|
|
2095
|
+
|| '';
|
|
2096
|
+
const smsFallbackTemplateName =
|
|
2097
|
+
m.templateName
|
|
2098
|
+
|| m.smsTemplateName
|
|
2099
|
+
|| tcSibling.templateName
|
|
2100
|
+
|| tcSibling.name
|
|
2101
|
+
|| '';
|
|
2102
|
+
const registeredSenderIdsForPayload = Array.isArray(m.registeredSenderIds)
|
|
2103
|
+
? m.registeredSenderIds
|
|
2104
|
+
: Array.isArray(tcSibling.registeredSenderIds)
|
|
2105
|
+
? tcSibling.registeredSenderIds
|
|
2106
|
+
: Array.isArray(tcSibling.header)
|
|
2107
|
+
? tcSibling.header
|
|
2108
|
+
: null;
|
|
2109
|
+
const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
|
|
2110
|
+
const smsFallbackTemplateConfigs =
|
|
2111
|
+
smsFallbackTemplateId || hasRegisteredSenderIds
|
|
2112
|
+
? {
|
|
2113
|
+
...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
|
|
2114
|
+
...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
|
|
2115
|
+
...(smsFallbackTemplateName && {
|
|
2116
|
+
templateName: smsFallbackTemplateName,
|
|
2117
|
+
}),
|
|
2118
|
+
...(hasRegisteredSenderIds && {
|
|
2119
|
+
registeredSenderIds: registeredSenderIdsForPayload,
|
|
2120
|
+
}),
|
|
2121
|
+
}
|
|
2122
|
+
: null;
|
|
2026
2123
|
return {
|
|
2027
2124
|
smsFallBackContent: {
|
|
2028
2125
|
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
@@ -2036,6 +2133,9 @@ const onTitleAddVar = () => {
|
|
|
2036
2133
|
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
2037
2134
|
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
2038
2135
|
}),
|
|
2136
|
+
...(smsFallbackTemplateConfigs && {
|
|
2137
|
+
templateConfigs: smsFallbackTemplateConfigs,
|
|
2138
|
+
}),
|
|
2039
2139
|
},
|
|
2040
2140
|
};
|
|
2041
2141
|
})()),
|
|
@@ -2058,13 +2158,36 @@ const onTitleAddVar = () => {
|
|
|
2058
2158
|
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
2059
2159
|
? String(wecrmAccountId)
|
|
2060
2160
|
: accountId;
|
|
2061
|
-
const
|
|
2161
|
+
const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
|
|
2162
|
+
let rcsForTest = {
|
|
2062
2163
|
...rcs,
|
|
2063
2164
|
rcsContent: {
|
|
2064
2165
|
...rcs.rcsContent,
|
|
2065
2166
|
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
2066
2167
|
},
|
|
2067
2168
|
};
|
|
2169
|
+
/** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
|
|
2170
|
+
if (isSlotMappingModeForPreview) {
|
|
2171
|
+
const cardContent = rcsForTest.rcsContent?.cardContent;
|
|
2172
|
+
if (Array.isArray(cardContent) && cardContent[0]) {
|
|
2173
|
+
const fullCardVarMapped = coalesceCardVarMappedToTemplate(
|
|
2174
|
+
pickRcsCardVarMappedEntries(cardVarMapped),
|
|
2175
|
+
templateTitle,
|
|
2176
|
+
templateDesc,
|
|
2177
|
+
rcsVarRegex,
|
|
2178
|
+
);
|
|
2179
|
+
rcsForTest = {
|
|
2180
|
+
...rcsForTest,
|
|
2181
|
+
rcsContent: {
|
|
2182
|
+
...rcsForTest.rcsContent,
|
|
2183
|
+
cardContent: [
|
|
2184
|
+
{ ...cardContent[0], cardVarMapped: fullCardVarMapped },
|
|
2185
|
+
...cardContent.slice(1),
|
|
2186
|
+
],
|
|
2187
|
+
},
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2068
2191
|
const out = {
|
|
2069
2192
|
versions: {
|
|
2070
2193
|
base: {
|
|
@@ -2235,7 +2358,13 @@ const onTitleAddVar = () => {
|
|
|
2235
2358
|
return true;
|
|
2236
2359
|
}
|
|
2237
2360
|
return orderedVarNames.some((name, globalIdx) => {
|
|
2238
|
-
const v = resolveCardVarMappedSlotValue(
|
|
2361
|
+
const v = resolveCardVarMappedSlotValue(
|
|
2362
|
+
cardVarMapped,
|
|
2363
|
+
name,
|
|
2364
|
+
globalIdx,
|
|
2365
|
+
true,
|
|
2366
|
+
rcsSpanningSemanticVarNames.has(name),
|
|
2367
|
+
);
|
|
2239
2368
|
const s = v == null ? '' : String(v);
|
|
2240
2369
|
return s.trim() === '';
|
|
2241
2370
|
});
|
|
@@ -2554,13 +2683,19 @@ const onTitleAddVar = () => {
|
|
|
2554
2683
|
content={testAndPreviewContent}
|
|
2555
2684
|
currentChannel={RCS}
|
|
2556
2685
|
orgUnitId={orgUnitId}
|
|
2686
|
+
rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
|
|
2557
2687
|
smsFallbackContent={
|
|
2558
2688
|
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
2559
2689
|
? {
|
|
2560
2690
|
templateContent:
|
|
2561
2691
|
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
2562
2692
|
templateName: smsFallbackData.templateName || '',
|
|
2563
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
2693
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
|
|
2694
|
+
? mergeRcsSmsFallbackVarMapLayers(
|
|
2695
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
2696
|
+
smsFallbackData,
|
|
2697
|
+
)
|
|
2698
|
+
: mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
|
|
2564
2699
|
}
|
|
2565
2700
|
: null
|
|
2566
2701
|
}
|
|
@@ -466,7 +466,8 @@ export default defineMessages({
|
|
|
466
466
|
},
|
|
467
467
|
unknownCharactersError: {
|
|
468
468
|
id: `${prefix}.unknownCharactersError`,
|
|
469
|
-
defaultMessage:
|
|
469
|
+
defaultMessage:
|
|
470
|
+
'Only letters, numbers, underscores, and dots are allowed in custom param (e.g. tag.FORMAT_1). Spaces and other special characters are not allowed.',
|
|
470
471
|
},
|
|
471
472
|
emptyVariableError: {
|
|
472
473
|
id: `${prefix}.emptyVariableError`,
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import isEmpty from 'lodash/isEmpty';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
3
|
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
4
|
+
import {
|
|
5
|
+
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
6
|
+
RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX,
|
|
7
|
+
} from './constants';
|
|
8
|
+
|
|
9
|
+
/** RCS card `cardVarMapped` / `rcsCardVarMapped` only (SMS fallback slot keys stay on `smsFallBackContent`). */
|
|
10
|
+
export function pickRcsCardVarMappedEntries(record) {
|
|
11
|
+
if (record == null || typeof record !== 'object' || Array.isArray(record)) return {};
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
Object.entries(record).filter(([k]) => {
|
|
14
|
+
const key = String(k);
|
|
15
|
+
return (
|
|
16
|
+
RCS_NUMERIC_VAR_NAME_REGEX.test(key)
|
|
17
|
+
|| RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX.test(key)
|
|
18
|
+
);
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
4
22
|
|
|
5
23
|
/**
|
|
6
24
|
* Nested `versions…smsFallBackContent` and root `smsFallBackContent` (CreativesContainer mirror)
|
|
@@ -88,6 +106,8 @@ export function getLibrarySmsFallbackApiBaselineFromTemplateData(templateData) {
|
|
|
88
106
|
};
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
export { extractRegisteredSenderIdsFromSmsFallbackRecord } from '../../utils/commonUtils';
|
|
110
|
+
|
|
91
111
|
export function pickFirstSmsFallbackTemplateString(sms = {}) {
|
|
92
112
|
if (!sms || typeof sms !== 'object') return '';
|
|
93
113
|
const keys = [
|