@capillarytech/creatives-library 8.0.239 → 8.0.241
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
CHANGED
|
@@ -3,7 +3,10 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
|
3
3
|
import { bindActionCreators } from 'redux';
|
|
4
4
|
import { createStructuredSelector } from 'reselect';
|
|
5
5
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
6
|
-
import
|
|
6
|
+
import get from 'lodash/get';
|
|
7
|
+
import isEmpty from 'lodash/isEmpty';
|
|
8
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
9
|
+
import isNil from 'lodash/isNil';
|
|
7
10
|
import styled from 'styled-components';
|
|
8
11
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
9
12
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
@@ -282,9 +285,22 @@ export const Rcs = (props) => {
|
|
|
282
285
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
283
286
|
return;
|
|
284
287
|
}
|
|
288
|
+
|
|
289
|
+
let contentForValidation = resolved;
|
|
290
|
+
const placeholderTokens = templateStr.match(rcsVarRegex) || [];
|
|
291
|
+
placeholderTokens.forEach((t) => {
|
|
292
|
+
const escaped = t.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
293
|
+
contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
|
|
294
|
+
});
|
|
295
|
+
if (!contentForValidation.trim()) {
|
|
296
|
+
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
297
|
+
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
285
301
|
const validationResponse =
|
|
286
302
|
validateTags({
|
|
287
|
-
content:
|
|
303
|
+
content: contentForValidation,
|
|
288
304
|
tagsParam: tags,
|
|
289
305
|
injectedTagsParams: injectedTags,
|
|
290
306
|
location,
|
|
@@ -322,7 +338,8 @@ export const Rcs = (props) => {
|
|
|
322
338
|
if (rcsVarTestRegex.test(elem)) {
|
|
323
339
|
const key = getVarNameFromToken(elem);
|
|
324
340
|
const v = cardVarMapped?.[key];
|
|
325
|
-
|
|
341
|
+
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
342
|
+
return String(v);
|
|
326
343
|
}
|
|
327
344
|
return elem;
|
|
328
345
|
}).join('');
|
|
@@ -330,7 +347,7 @@ export const Rcs = (props) => {
|
|
|
330
347
|
|
|
331
348
|
|
|
332
349
|
useEffect(() => {
|
|
333
|
-
if (isFullMode) return;
|
|
350
|
+
if (isFullMode || isEditFlow) return;
|
|
334
351
|
const tokens = [
|
|
335
352
|
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
336
353
|
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
@@ -1729,8 +1746,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1729
1746
|
const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
1730
1747
|
return (
|
|
1731
1748
|
<>
|
|
1732
|
-
{renderLabel('cardOrientationLabel')}
|
|
1733
|
-
{/* Match image behavior: allow changing dimensions only in full-mode create flow */}
|
|
1749
|
+
{isFullMode && renderLabel('cardOrientationLabel')}
|
|
1734
1750
|
{!isEditFlow && isFullMode && (
|
|
1735
1751
|
<CapSelect
|
|
1736
1752
|
id="rcs-dimension-select"
|
|
@@ -1829,11 +1845,27 @@ const splitTemplateVarString = (str) => {
|
|
|
1829
1845
|
if (!str) return '';
|
|
1830
1846
|
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1831
1847
|
let result = str;
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
const
|
|
1848
|
+
const replacements = [];
|
|
1849
|
+
Object.entries(mapping).forEach(([key, value]) => {
|
|
1850
|
+
const raw = (value ?? '').toString();
|
|
1851
|
+
if (!raw || raw?.trim?.() === '') return;
|
|
1852
|
+
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1853
|
+
replacements.push({ key, needle: raw });
|
|
1854
|
+
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1855
|
+
});
|
|
1856
|
+
const seen = new Set();
|
|
1857
|
+
const uniq = replacements
|
|
1858
|
+
.filter(({ key, needle }) => {
|
|
1859
|
+
const id = `${key}::${needle}`;
|
|
1860
|
+
if (seen.has(id)) return false;
|
|
1861
|
+
seen.add(id);
|
|
1862
|
+
return true;
|
|
1863
|
+
})
|
|
1864
|
+
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1865
|
+
|
|
1866
|
+
uniq.forEach(({ key, needle }) => {
|
|
1867
|
+
if (!needle) return;
|
|
1868
|
+
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1837
1869
|
const regex = new RegExp(escaped, 'g');
|
|
1838
1870
|
result = result.replace(regex, `{{${key}}}`);
|
|
1839
1871
|
});
|
|
@@ -1879,8 +1911,24 @@ const splitTemplateVarString = (str) => {
|
|
|
1879
1911
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
1880
1912
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
1881
1913
|
}}),
|
|
1882
|
-
|
|
1883
|
-
|
|
1914
|
+
...(!isFullMode && (() => {
|
|
1915
|
+
const tokens = [
|
|
1916
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
1917
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
1918
|
+
];
|
|
1919
|
+
const allowedKeys = tokens
|
|
1920
|
+
.map((t) => getVarNameFromToken(t))
|
|
1921
|
+
.filter(Boolean);
|
|
1922
|
+
const nextMap = {};
|
|
1923
|
+
allowedKeys.forEach((k) => {
|
|
1924
|
+
if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
|
|
1925
|
+
nextMap[k] = cardVarMapped[k];
|
|
1926
|
+
} else {
|
|
1927
|
+
nextMap[k] = '';
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
return { cardVarMapped: nextMap };
|
|
1931
|
+
})()),
|
|
1884
1932
|
...(suggestions.length > 0 && { suggestions }),
|
|
1885
1933
|
}
|
|
1886
1934
|
],
|
|
@@ -968,6 +968,87 @@ describe('RCS createPayload', () => {
|
|
|
968
968
|
expect(updatedTitleVarAreas.at(0).prop('value')).toBe('{{first_name}}');
|
|
969
969
|
expect(updatedTitleVarAreas.at(1).prop('value')).toBe('{{first_name}}');
|
|
970
970
|
});
|
|
971
|
+
|
|
972
|
+
it('should keep two tags + freetext inside the variable textarea in non-full mode edit (not merged into static text)', () => {
|
|
973
|
+
const templateData = {
|
|
974
|
+
name: 'TwoTagsAndFreeText',
|
|
975
|
+
versions: {
|
|
976
|
+
base: {
|
|
977
|
+
content: {
|
|
978
|
+
RCS: {
|
|
979
|
+
rcsContent: {
|
|
980
|
+
cardType: 'STANDALONE',
|
|
981
|
+
cardSettings: { cardOrientation: 'VERTICAL', cardWidth: 'SMALL' },
|
|
982
|
+
cardContent: [
|
|
983
|
+
{
|
|
984
|
+
// NOTE: In campaigns/edit flows, title/description can be stored "resolved" (values applied).
|
|
985
|
+
// We expect RCS to "unmap" this back to placeholders and show values in variable textareas.
|
|
986
|
+
title: 'Update for {{user_id_b64}} regarding {{first_name}}{{adv}}freeText',
|
|
987
|
+
description: 'Hi {{user_id_b64}}, your {{first_name}}{{adv}}freeText is ready.',
|
|
988
|
+
mediaType: 'NONE',
|
|
989
|
+
cardVarMapped: {
|
|
990
|
+
user_name: '{{user_id_b64}}',
|
|
991
|
+
service_type: '{{first_name}}{{adv}}freeText',
|
|
992
|
+
},
|
|
993
|
+
suggestions: [],
|
|
994
|
+
},
|
|
995
|
+
],
|
|
996
|
+
contentType: 'RICHCARD',
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
},
|
|
1000
|
+
},
|
|
1001
|
+
},
|
|
1002
|
+
type: 'RCS',
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
const wrapper = mountWithIntl(
|
|
1006
|
+
<Provider store={store}>
|
|
1007
|
+
<Rcs
|
|
1008
|
+
actions={{
|
|
1009
|
+
clearCreateResponse: jest.fn(),
|
|
1010
|
+
getTemplateDetails: jest.fn(),
|
|
1011
|
+
uploadRcsAsset: jest.fn(),
|
|
1012
|
+
clearRcsMediaAsset: jest.fn(),
|
|
1013
|
+
editTemplate: jest.fn(),
|
|
1014
|
+
clearEditResponse: jest.fn(),
|
|
1015
|
+
}}
|
|
1016
|
+
globalActions={{ fetchSchemaForEntity }}
|
|
1017
|
+
onCreateComplete={onCreateComplete}
|
|
1018
|
+
handleClose={handleClose}
|
|
1019
|
+
intl={{ formatMessage }}
|
|
1020
|
+
location={{ pathname: '/rcs/edit', query: { type: false, module: 'default' }, search: '' }}
|
|
1021
|
+
params={params}
|
|
1022
|
+
templateData={templateData}
|
|
1023
|
+
rcsData={{}}
|
|
1024
|
+
isFullMode={false}
|
|
1025
|
+
isEditFlow={false}
|
|
1026
|
+
loadingTags={false}
|
|
1027
|
+
metaEntities={[]}
|
|
1028
|
+
isDltEnabled={false}
|
|
1029
|
+
smsRegister={'DLT'}
|
|
1030
|
+
getFormData={jest.fn()}
|
|
1031
|
+
/>
|
|
1032
|
+
</Provider>,
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
wrapper.update();
|
|
1036
|
+
|
|
1037
|
+
// The placeholder {{service_type}} should exist as a variable textarea id, and its value should be the mixed string.
|
|
1038
|
+
const serviceTypeAreas = wrapper.find('TextArea').filterWhere((n) => {
|
|
1039
|
+
const id = n.prop('id') || '';
|
|
1040
|
+
return id.includes('{{service_type}}_');
|
|
1041
|
+
});
|
|
1042
|
+
expect(serviceTypeAreas.length).toBeGreaterThanOrEqual(1);
|
|
1043
|
+
expect(serviceTypeAreas.at(0).prop('value')).toBe('{{first_name}}{{adv}}freeText');
|
|
1044
|
+
|
|
1045
|
+
// Ensure freetext does NOT leak into static text blocks.
|
|
1046
|
+
const staticAreas = wrapper.find('TextArea').filterWhere((n) => !!n.prop('disabled'));
|
|
1047
|
+
const staticValues = staticAreas.map((n) => String(n.prop('value') || ''));
|
|
1048
|
+
staticValues.forEach((v) => {
|
|
1049
|
+
expect(v.includes('freeText')).toBe(false);
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
971
1052
|
});
|
|
972
1053
|
|
|
973
1054
|
describe('Character Counting Functions', () => {
|