@capillarytech/creatives-library 9.0.38 → 9.0.39
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/translations/en.json +2 -2
- package/utils/permissions.js +59 -0
- package/utils/tests/permissions.test.js +116 -0
- package/v2Components/AccessForbidden/_accessForbidden.scss +14 -0
- package/v2Components/AccessForbidden/index.js +5 -3
- package/v2Components/AccessForbidden/messages.js +3 -2
- package/v2Components/NoPermissionWrapper/_noPermissionWrapper.scss +12 -0
- package/v2Components/NoPermissionWrapper/index.js +26 -0
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/Assets/Gallery/_gallery.scss +6 -0
- package/v2Containers/Assets/Gallery/index.js +34 -10
- package/v2Containers/Cap/index.js +5 -2
- package/v2Containers/Cap/messages.js +4 -0
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +2 -2
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +6 -6
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +4 -4
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +32 -32
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +36 -36
- package/v2Containers/Rcs/carouselUtils.js +46 -73
- package/v2Containers/Rcs/components/CarouselCard.js +6 -9
- package/v2Containers/Rcs/constants.js +2 -7
- package/v2Containers/Rcs/index.js +72 -126
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +5 -42
- package/v2Containers/Rcs/tests/CarouselCard.test.js +1 -2
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +118 -118
- package/v2Containers/Rcs/tests/carouselUtils.test.js +15 -15
- package/v2Containers/Rcs/tests/index.test.js +10 -232
- package/v2Containers/Rcs/tests/utils.test.js +0 -17
- package/v2Containers/Rcs/utils.js +12 -51
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +8 -8
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +18 -18
- package/v2Containers/Templates/ChannelTypeIllustration.js +13 -2
- package/v2Containers/Templates/_templates.scss +6 -0
- package/v2Containers/Templates/index.js +141 -100
- package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +1 -1
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +174 -121
- package/v2Containers/TemplatesV2/index.js +16 -5
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +268 -268
|
@@ -15,7 +15,6 @@ 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';
|
|
19
18
|
import CarouselCardMedia from './CarouselCardMedia';
|
|
20
19
|
import CarouselCardButtons from './CarouselCardButtons';
|
|
21
20
|
import CarouselCharacterCount from './CarouselCharacterCount';
|
|
@@ -51,8 +50,7 @@ const CarouselCard = ({
|
|
|
51
50
|
onDescriptionChange,
|
|
52
51
|
onTitleAddVar,
|
|
53
52
|
onDescriptionAddVar,
|
|
54
|
-
|
|
55
|
-
onDescTagSelect,
|
|
53
|
+
onTagSelect,
|
|
56
54
|
onContextChange,
|
|
57
55
|
renderEditMessage,
|
|
58
56
|
onFieldChange,
|
|
@@ -139,7 +137,7 @@ const CarouselCard = ({
|
|
|
139
137
|
(isEditFlow || !isFullMode) ? (
|
|
140
138
|
<TagList
|
|
141
139
|
label={formatMessage(globalMessages.addLabels)}
|
|
142
|
-
onTagSelect={
|
|
140
|
+
onTagSelect={onTagSelect}
|
|
143
141
|
location={location}
|
|
144
142
|
tags={tagsForPicker}
|
|
145
143
|
onContextChange={onContextChange}
|
|
@@ -161,7 +159,7 @@ const CarouselCard = ({
|
|
|
161
159
|
/>
|
|
162
160
|
<CapRow className="rcs_text_area_wrapper">
|
|
163
161
|
{(isEditFlow || !isFullMode) ? (
|
|
164
|
-
renderEditMessage(card.title || '', cardIndex
|
|
162
|
+
renderEditMessage(card.title || '', cardIndex)
|
|
165
163
|
) : (
|
|
166
164
|
<CapInput
|
|
167
165
|
value={card.title || ''}
|
|
@@ -191,7 +189,7 @@ const CarouselCard = ({
|
|
|
191
189
|
(isEditFlow || !isFullMode) ? (
|
|
192
190
|
<TagList
|
|
193
191
|
label={formatMessage(globalMessages.addLabels)}
|
|
194
|
-
onTagSelect={
|
|
192
|
+
onTagSelect={onTagSelect}
|
|
195
193
|
location={location}
|
|
196
194
|
tags={tagsForPicker}
|
|
197
195
|
onContextChange={onContextChange}
|
|
@@ -213,7 +211,7 @@ const CarouselCard = ({
|
|
|
213
211
|
/>
|
|
214
212
|
<CapRow className="rcs_text_area_wrapper">
|
|
215
213
|
{(isEditFlow || !isFullMode) ? (
|
|
216
|
-
renderEditMessage(card.description || '', cardIndex
|
|
214
|
+
renderEditMessage(card.description || '', cardIndex)
|
|
217
215
|
) : (
|
|
218
216
|
<TextArea
|
|
219
217
|
autosize={{ minRows: 3, maxRows: 5 }}
|
|
@@ -288,8 +286,7 @@ CarouselCard.propTypes = {
|
|
|
288
286
|
onDescriptionChange: PropTypes.func.isRequired,
|
|
289
287
|
onTitleAddVar: PropTypes.func.isRequired,
|
|
290
288
|
onDescriptionAddVar: PropTypes.func.isRequired,
|
|
291
|
-
|
|
292
|
-
onDescTagSelect: PropTypes.func.isRequired,
|
|
289
|
+
onTagSelect: PropTypes.func.isRequired,
|
|
293
290
|
onContextChange: PropTypes.func,
|
|
294
291
|
renderEditMessage: PropTypes.func.isRequired,
|
|
295
292
|
onFieldChange: PropTypes.func.isRequired,
|
|
@@ -78,13 +78,8 @@ 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
|
-
|
|
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.()]+$/;
|
|
81
|
+
/** Semantic Liquid-style keys on RCS `cardVarMapped` (same class as `{{…}}` inner names in the editor). */
|
|
82
|
+
export const RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX = /^[\w.()]+$/;
|
|
88
83
|
/** Escape all special RegExp characters in an arbitrary string before using it in `new RegExp(...)`. */
|
|
89
84
|
export const REGEX_SPECIAL_CHARS_ESCAPE_PATTERN = /[-/\\^$*+?.()|[\]{}]/g;
|
|
90
85
|
/** Entire string is wrapped in `{{…}}` mustache delimiters (allows any content including `}`). */
|
|
@@ -175,7 +175,7 @@ import {
|
|
|
175
175
|
getCarouselDescriptionCharacterCount,
|
|
176
176
|
buildCarouselCardsForPreview as buildCarouselCardsForPreviewUtil,
|
|
177
177
|
buildCarouselCardContentForPayload,
|
|
178
|
-
|
|
178
|
+
getCarouselVarMapKey,
|
|
179
179
|
} from './carouselUtils';
|
|
180
180
|
import CarouselDimensionSelection from './components/CarouselDimensionSelection';
|
|
181
181
|
import CarouselCard from './components/CarouselCard';
|
|
@@ -271,12 +271,7 @@ export const Rcs = (props) => {
|
|
|
271
271
|
const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
|
|
272
272
|
const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
|
|
273
273
|
const [carouselResetNonce, setCarouselResetNonce] = useState(0);
|
|
274
|
-
|
|
275
|
-
// descTextAreaId for the single-card flow) — a single shared id would let a click on the
|
|
276
|
-
// "Add Labels" picker next to one field silently apply to whichever field was focused last,
|
|
277
|
-
// if the user hadn't just clicked into that field's own var slot.
|
|
278
|
-
const [carouselTitleFocusedVarId, setCarouselTitleFocusedVarId] = useState('');
|
|
279
|
-
const [carouselDescFocusedVarId, setCarouselDescFocusedVarId] = useState('');
|
|
274
|
+
const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
|
|
280
275
|
const [imageError, setImageError] = useState(null);
|
|
281
276
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
282
277
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
@@ -602,8 +597,7 @@ export const Rcs = (props) => {
|
|
|
602
597
|
}}
|
|
603
598
|
onTitleAddVar={() => appendVarToCarouselField(index, 'title')}
|
|
604
599
|
onDescriptionAddVar={() => appendVarToCarouselField(index, 'description')}
|
|
605
|
-
|
|
606
|
-
onDescTagSelect={onCarouselDescTagSelect}
|
|
600
|
+
onTagSelect={onCarouselTagSelect}
|
|
607
601
|
onContextChange={handleOnTagsContextChange}
|
|
608
602
|
renderEditMessage={renderCarouselEditMessage}
|
|
609
603
|
onFieldChange={(fieldName, value) => handleCarouselValueChange(index, [{ fieldName, value }])}
|
|
@@ -662,8 +656,7 @@ export const Rcs = (props) => {
|
|
|
662
656
|
onChange={(key) => {
|
|
663
657
|
setActiveCarouselIndex(`${key}`);
|
|
664
658
|
// reset focused var when switching cards (as per requirement)
|
|
665
|
-
|
|
666
|
-
setCarouselDescFocusedVarId('');
|
|
659
|
+
setCarouselFocusedVarId('');
|
|
667
660
|
}}
|
|
668
661
|
panes={getCarouselTabPanes()}
|
|
669
662
|
/>
|
|
@@ -783,7 +776,7 @@ export const Rcs = (props) => {
|
|
|
783
776
|
['title', 'description'].forEach((field) => {
|
|
784
777
|
const templateStr = card?.[field] || '';
|
|
785
778
|
if (!templateStr) return;
|
|
786
|
-
const resolved = resolveCarouselTemplateWithMap(templateStr, idx
|
|
779
|
+
const resolved = resolveCarouselTemplateWithMap(templateStr, idx);
|
|
787
780
|
if (!resolved) {
|
|
788
781
|
updateCarouselErrors(idx, { [field]: false });
|
|
789
782
|
return;
|
|
@@ -876,22 +869,17 @@ export const Rcs = (props) => {
|
|
|
876
869
|
};
|
|
877
870
|
|
|
878
871
|
/**
|
|
879
|
-
* Carousel resolve: each
|
|
880
|
-
*
|
|
881
|
-
*
|
|
872
|
+
* Carousel resolve: each card's variables live under their own `getCarouselVarMapKey(cardIndex, name)`
|
|
873
|
+
* key in `cardVarMapped` (see that helper's doc) — no global slot-offset arithmetic needed since
|
|
874
|
+
* cards can no longer collide with each other's entries.
|
|
882
875
|
*/
|
|
883
|
-
const resolveCarouselTemplateWithMap = (str = '', cardIndex
|
|
876
|
+
const resolveCarouselTemplateWithMap = (str = '', cardIndex) => {
|
|
884
877
|
if (!str) return '';
|
|
885
878
|
const arr = splitTemplateVarStringRcs(str);
|
|
886
|
-
const fieldKeys = (isDescField
|
|
887
|
-
? computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex]?.description
|
|
888
|
-
: computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex]?.title) ?? [];
|
|
889
|
-
let varOrdinal = 0;
|
|
890
879
|
return arr.map((elem) => {
|
|
891
880
|
if (rcsVarTestRegex.test(elem)) {
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
const slotValue = cardVarMapped?.[slotKey];
|
|
881
|
+
const varName = getVarNameFromToken(elem);
|
|
882
|
+
const slotValue = cardVarMapped?.[getCarouselVarMapKey(cardIndex, varName)];
|
|
895
883
|
if (isNil(slotValue) || String(slotValue)?.trim?.() === '') return elem;
|
|
896
884
|
return String(slotValue);
|
|
897
885
|
}
|
|
@@ -1249,15 +1237,35 @@ export const Rcs = (props) => {
|
|
|
1249
1237
|
setActiveCarouselIndex('0');
|
|
1250
1238
|
|
|
1251
1239
|
if (cards.length > 1) {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
// sits in the template text, and never depends on which tag fills it. So merging every
|
|
1255
|
-
// card's own map directly is correct and collision-free: no per-card coalescing,
|
|
1256
|
-
// normalizing, or re-scoping needed (unlike the old name-based scoped-key scheme).
|
|
1257
|
-
setCardVarMapped((previousVarMap) => cards.reduce((merged, c = {}) => {
|
|
1240
|
+
|
|
1241
|
+
setCardVarMapped((previousVarMap) => cards.reduce((merged, c = {}, idx) => {
|
|
1258
1242
|
const rawCardMap = c?.cardVarMapped;
|
|
1259
1243
|
if (rawCardMap == null || typeof rawCardMap !== 'object') return merged;
|
|
1260
|
-
return { ...merged, ...rawCardMap };
|
|
1244
|
+
if (idx === 0) return { ...merged, ...rawCardMap };
|
|
1245
|
+
const cardTitle = c?.title != null ? String(c.title) : '';
|
|
1246
|
+
const cardDesc = c?.description != null ? String(c.description) : '';
|
|
1247
|
+
const cardTokens = [
|
|
1248
|
+
...(cardTitle ? cardTitle.match(rcsVarRegex) ?? [] : []),
|
|
1249
|
+
...(cardDesc ? cardDesc.match(rcsVarRegex) ?? [] : []),
|
|
1250
|
+
];
|
|
1251
|
+
const cardOrderedTagNames = cardTokens.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
1252
|
+
const cardMapBeforeCoalesce = isFullMode
|
|
1253
|
+
? normalizeCardVarMapped(rawCardMap, cardOrderedTagNames)
|
|
1254
|
+
: { ...rawCardMap };
|
|
1255
|
+
const cardMapAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
1256
|
+
cardMapBeforeCoalesce,
|
|
1257
|
+
cardTitle,
|
|
1258
|
+
cardDesc,
|
|
1259
|
+
rcsVarRegex,
|
|
1260
|
+
);
|
|
1261
|
+
const cardMapAfterSlotSync = !isFullMode
|
|
1262
|
+
? syncCardVarMappedSemanticsFromSlots(cardMapAfterCoalesce, cardTitle, cardDesc, rcsVarRegex)
|
|
1263
|
+
: cardMapAfterCoalesce;
|
|
1264
|
+
const namespacedCardMap = {};
|
|
1265
|
+
Object.entries(cardMapAfterSlotSync).forEach(([key, value]) => {
|
|
1266
|
+
namespacedCardMap[getCarouselVarMapKey(idx, key)] = value;
|
|
1267
|
+
});
|
|
1268
|
+
return { ...merged, ...namespacedCardMap };
|
|
1261
1269
|
}, { ...(previousVarMap || {}) }));
|
|
1262
1270
|
}
|
|
1263
1271
|
|
|
@@ -1598,51 +1606,25 @@ export const Rcs = (props) => {
|
|
|
1598
1606
|
|
|
1599
1607
|
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
1600
1608
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
if (`${segments[segmentIndex]}_${segmentIndex}` === compositeId) return varOrdinal;
|
|
1609
|
-
varOrdinal += 1;
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
return null;
|
|
1613
|
-
};
|
|
1614
|
-
|
|
1615
|
-
// The card's placeholder (numeric {{N}} or a bare semantic token like {{first_name}}) is locked
|
|
1616
|
-
// here — picking a tag only supplies/replaces the *value* behind that slot in cardVarMapped, it
|
|
1617
|
-
// never rewrites the card's title/description text. `focusedVarId` is passed in by the caller
|
|
1618
|
-
// (title vs description each track their own last-focused slot, like titleTextAreaId/
|
|
1619
|
-
// descTextAreaId) so a click on one field's "Add Labels" can never apply to the other field's
|
|
1620
|
-
// last-focused slot. The slot's key is its global position across the whole carousel — assigned
|
|
1621
|
-
// once from where its token sits in the template text, never from whatever tag currently fills
|
|
1622
|
-
// it — so swapping tags never changes the key and never collides with another card's slot.
|
|
1623
|
-
const onCarouselTagSelect = (data, focusedVarId, isDescField) => {
|
|
1624
|
-
if (!focusedVarId) return;
|
|
1609
|
+
const onCarouselTagSelect = (data) => {
|
|
1610
|
+
if (!carouselFocusedVarId) return;
|
|
1611
|
+
const sep = carouselFocusedVarId.lastIndexOf('_');
|
|
1612
|
+
if (sep === -1) return;
|
|
1613
|
+
const token = carouselFocusedVarId.slice(0, sep);
|
|
1614
|
+
const variableName = getVarNameFromToken(token);
|
|
1615
|
+
if (!variableName) return;
|
|
1625
1616
|
const activeIdx = parseInt(activeCarouselIndex, 10);
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
setCardVarMapped((prev) => ({
|
|
1637
|
-
...(prev || {}),
|
|
1638
|
-
[slotKey]: `{{${data}}}`,
|
|
1639
|
-
}));
|
|
1617
|
+
const mapKey = getCarouselVarMapKey(isNaN(activeIdx) ? 0 : activeIdx, variableName);
|
|
1618
|
+
setCardVarMapped((prev) => {
|
|
1619
|
+
const base = (prev?.[mapKey] ?? '').toString();
|
|
1620
|
+
const nextVal = `${base}{{${data}}}`;
|
|
1621
|
+
return {
|
|
1622
|
+
...(prev || {}),
|
|
1623
|
+
[mapKey]: nextVal,
|
|
1624
|
+
};
|
|
1625
|
+
});
|
|
1640
1626
|
};
|
|
1641
1627
|
|
|
1642
|
-
const onCarouselTitleTagSelect = (data) => onCarouselTagSelect(data, carouselTitleFocusedVarId, false);
|
|
1643
|
-
|
|
1644
|
-
const onCarouselDescTagSelect = (data) => onCarouselTagSelect(data, carouselDescFocusedVarId, true);
|
|
1645
|
-
|
|
1646
1628
|
//removing optout tag for rcs
|
|
1647
1629
|
const getRcsTags = () => {
|
|
1648
1630
|
const tempTags = cloneDeep(tags);
|
|
@@ -1728,7 +1710,7 @@ export const Rcs = (props) => {
|
|
|
1728
1710
|
key={`${elem}_${index}`}
|
|
1729
1711
|
placeholder={`enter the value for ${elem}`}
|
|
1730
1712
|
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1731
|
-
onChange={e => textAreaValueChange(e)}
|
|
1713
|
+
onChange={e => textAreaValueChange(e, type)}
|
|
1732
1714
|
value={textAreaValue(index, type)}
|
|
1733
1715
|
onFocus={(e) => setTextAreaId(e, type)}
|
|
1734
1716
|
/>
|
|
@@ -1948,21 +1930,16 @@ const onTitleAddVar = () => {
|
|
|
1948
1930
|
|
|
1949
1931
|
// Carousel: render variable-value editor for a given template string (title/description) belonging
|
|
1950
1932
|
// to `cardIndex`. This matches rich-card/text edit behavior: static pieces are read-only, variable
|
|
1951
|
-
// tokens are editable. Each
|
|
1952
|
-
//
|
|
1953
|
-
const renderCarouselEditMessage = (templateStr, cardIndex
|
|
1933
|
+
// tokens are editable. Each card's values live under their own `getCarouselVarMapKey(cardIndex, name)`
|
|
1934
|
+
// key in `cardVarMapped` so two cards using the same variable/tag name never collide.
|
|
1935
|
+
const renderCarouselEditMessage = (templateStr, cardIndex) => {
|
|
1954
1936
|
const renderArray = [];
|
|
1955
1937
|
const templateArr = splitTemplateVarString(templateStr);
|
|
1956
|
-
const isDescField = fieldType === RCS_TAG_AREA_FIELD_DESC;
|
|
1957
|
-
const slotKeysForCard = computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex];
|
|
1958
|
-
const fieldKeys = (isDescField ? slotKeysForCard?.description : slotKeysForCard?.title) ?? [];
|
|
1959
|
-
let varOrdinal = 0;
|
|
1960
1938
|
if (templateArr?.length) {
|
|
1961
1939
|
templateArr.forEach((elem, index) => {
|
|
1962
1940
|
if (rcsVarTestRegex.test(elem)) {
|
|
1963
1941
|
const varName = getVarNameFromToken(elem);
|
|
1964
|
-
const mapKey =
|
|
1965
|
-
varOrdinal += 1;
|
|
1942
|
+
const mapKey = getCarouselVarMapKey(cardIndex, varName);
|
|
1966
1943
|
const computedValue = varName ? ((cardVarMapped?.[mapKey] ?? '').toString()) : '';
|
|
1967
1944
|
renderArray.push(
|
|
1968
1945
|
<div
|
|
@@ -1973,15 +1950,11 @@ const onTitleAddVar = () => {
|
|
|
1973
1950
|
id={`${elem}_${index}`}
|
|
1974
1951
|
placeholder={`enter the value for ${elem}`}
|
|
1975
1952
|
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1976
|
-
onChange={(e) =>
|
|
1953
|
+
onChange={(e) => textAreaValueChange(e, cardIndex)}
|
|
1977
1954
|
value={computedValue}
|
|
1978
1955
|
onFocus={(e) => {
|
|
1979
1956
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1980
|
-
|
|
1981
|
-
setCarouselDescFocusedVarId(id);
|
|
1982
|
-
} else {
|
|
1983
|
-
setCarouselTitleFocusedVarId(id);
|
|
1984
|
-
}
|
|
1957
|
+
setCarouselFocusedVarId(id);
|
|
1985
1958
|
}}
|
|
1986
1959
|
/>
|
|
1987
1960
|
</div>
|
|
@@ -2002,7 +1975,7 @@ const onTitleAddVar = () => {
|
|
|
2002
1975
|
return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
|
|
2003
1976
|
};
|
|
2004
1977
|
|
|
2005
|
-
const textAreaValueChange = (e) => {
|
|
1978
|
+
const textAreaValueChange = (e, cardIndex) => {
|
|
2006
1979
|
const value = e?.target?.value ?? '';
|
|
2007
1980
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2008
1981
|
if (!id) return;
|
|
@@ -2013,38 +1986,14 @@ const onTitleAddVar = () => {
|
|
|
2013
1986
|
const variableName = getVarNameFromToken(token);
|
|
2014
1987
|
|
|
2015
1988
|
if (variableName) {
|
|
2016
|
-
const
|
|
1989
|
+
const mapKey = getCarouselVarMapKey(cardIndex, variableName);
|
|
2017
1990
|
setCardVarMapped((prev) => ({
|
|
2018
1991
|
...prev,
|
|
2019
|
-
[
|
|
1992
|
+
[mapKey]: isInvalidValue ? "" : value,
|
|
2020
1993
|
}));
|
|
2021
1994
|
}
|
|
2022
1995
|
};
|
|
2023
1996
|
|
|
2024
|
-
// Carousel: typing directly into a slot's value box (as opposed to picking a tag) — same global
|
|
2025
|
-
// positional key as onCarouselTagSelect/renderCarouselEditMessage, so both ways of filling a
|
|
2026
|
-
// slot land on the identical key.
|
|
2027
|
-
const carouselTextAreaValueChange = (e, cardIndex, fieldType) => {
|
|
2028
|
-
const value = e?.target?.value ?? '';
|
|
2029
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2030
|
-
if (!id) return;
|
|
2031
|
-
const isDescField = fieldType === RCS_TAG_AREA_FIELD_DESC;
|
|
2032
|
-
const fieldTemplateStr = (isDescField
|
|
2033
|
-
? carouselData?.[cardIndex]?.description
|
|
2034
|
-
: carouselData?.[cardIndex]?.title) || '';
|
|
2035
|
-
const localVarOrdinal = getCarouselFieldLocalVarOrdinal(id, fieldTemplateStr);
|
|
2036
|
-
if (localVarOrdinal == null) return;
|
|
2037
|
-
const slotKeys = computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex];
|
|
2038
|
-
const mapKey = (isDescField ? slotKeys?.description : slotKeys?.title)?.[localVarOrdinal];
|
|
2039
|
-
if (!mapKey) return;
|
|
2040
|
-
const isInvalidValue = value?.trim() === "";
|
|
2041
|
-
const nextVal = isInvalidValue ? "" : value;
|
|
2042
|
-
setCardVarMapped((prev) => ({
|
|
2043
|
-
...prev,
|
|
2044
|
-
[mapKey]: nextVal,
|
|
2045
|
-
}));
|
|
2046
|
-
};
|
|
2047
|
-
|
|
2048
1997
|
const setTextAreaId = (e, type) => {
|
|
2049
1998
|
// VarSegmentMessageEditor calls onFocus(id) with a plain string; DOM events
|
|
2050
1999
|
// have an `.target.id` shape. Support both.
|
|
@@ -2895,8 +2844,9 @@ const onTitleAddVar = () => {
|
|
|
2895
2844
|
// the user mapped via cardVarMapped. Run this for ALL modes (create, edit, consumer) so that:
|
|
2896
2845
|
// - buildRcsTestMessagePayload sends real Capillary tag names to the test API, and
|
|
2897
2846
|
// - prepareTagExtractionPayload can extract tag metadata from the card content.
|
|
2898
|
-
// Each
|
|
2899
|
-
//
|
|
2847
|
+
// Each card's variables live under their own getCarouselVarMapKey(cardIndex, name) slot, so
|
|
2848
|
+
// no cross-card slot-offset bookkeeping is needed (or safe — two cards' variables never share
|
|
2849
|
+
// a key, so there is nothing to accidentally collide).
|
|
2900
2850
|
rcsForTest = {
|
|
2901
2851
|
...rcsForTest,
|
|
2902
2852
|
rcsContent: {
|
|
@@ -2904,8 +2854,8 @@ const onTitleAddVar = () => {
|
|
|
2904
2854
|
cardContent: cardContent.map((card, cardIndex) => {
|
|
2905
2855
|
const rawTitle = card.title || '';
|
|
2906
2856
|
const rawDesc = card.description || '';
|
|
2907
|
-
const resolvedTitle = resolveCarouselTemplateWithMap(rawTitle, cardIndex
|
|
2908
|
-
const resolvedDesc = resolveCarouselTemplateWithMap(rawDesc, cardIndex
|
|
2857
|
+
const resolvedTitle = resolveCarouselTemplateWithMap(rawTitle, cardIndex);
|
|
2858
|
+
const resolvedDesc = resolveCarouselTemplateWithMap(rawDesc, cardIndex);
|
|
2909
2859
|
return { ...card, title: resolvedTitle, description: resolvedDesc };
|
|
2910
2860
|
}),
|
|
2911
2861
|
},
|
|
@@ -3116,16 +3066,12 @@ const onTitleAddVar = () => {
|
|
|
3116
3066
|
['title', 'description'].some((field) => !(card?.[field] || '').trim())
|
|
3117
3067
|
);
|
|
3118
3068
|
if (hasEmptyField) return true;
|
|
3119
|
-
const slotKeysByCard = computeCarouselVarSlotKeys(carouselData, rcsVarRegex);
|
|
3120
3069
|
const unfilledVar = (carouselData || []).some((card, cardIndex) =>
|
|
3121
3070
|
['title', 'description'].some((field) => {
|
|
3122
|
-
const isDescField = field === 'description';
|
|
3123
3071
|
const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((token) => rcsVarTestRegex.test(token));
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
const slotKey = fieldKeys[localVarOrdinal];
|
|
3128
|
-
const slotValue = cardVarMapped?.[slotKey];
|
|
3072
|
+
return tokens.some((token) => {
|
|
3073
|
+
const name = token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
|
|
3074
|
+
const slotValue = cardVarMapped?.[getCarouselVarMapKey(cardIndex, name)];
|
|
3129
3075
|
return slotValue == null || String(slotValue).trim() === '';
|
|
3130
3076
|
});
|
|
3131
3077
|
})
|
|
@@ -459,48 +459,11 @@ export function syncCardVarMappedSemanticsFromSlots(
|
|
|
459
459
|
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
460
460
|
const semanticVarName = getVarNameFromToken(token);
|
|
461
461
|
if (!semanticVarName) return;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const mustacheSelfReference = `{{${semanticVarName}}}`;
|
|
468
|
-
const existingNumericKeyForSemantic = Object.keys(cardVarMappedSynced).find((key) => (
|
|
469
|
-
RCS_NUMERIC_VAR_NAME_REGEX.test(key) && cardVarMappedSynced[key] === mustacheSelfReference
|
|
470
|
-
));
|
|
471
|
-
if (existingNumericKeyForSemantic) {
|
|
472
|
-
const semanticValueTrimmed = String(cardVarMappedSynced[semanticVarName] ?? '').trim();
|
|
473
|
-
const numericValueTrimmed = String(cardVarMappedSynced[existingNumericKeyForSemantic] ?? '').trim();
|
|
474
|
-
if (!semanticValueTrimmed && numericValueTrimmed) {
|
|
475
|
-
cardVarMappedSynced[semanticVarName] = cardVarMappedSynced[existingNumericKeyForSemantic];
|
|
476
|
-
}
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Legacy: template embeds the semantic token directly ({{user_name}}), but older payloads
|
|
481
|
-
// stored the resolved value under a position-based numeric key ("1", "2", …).
|
|
482
|
-
const numericSlotKey = String(slotIndexZeroBased + 1);
|
|
483
|
-
const semanticValueTrimmed = String(cardVarMappedSynced[semanticVarName] ?? '').trim();
|
|
484
|
-
const numericSlotValueTrimmed = String(cardVarMappedSynced[numericSlotKey] ?? '').trim();
|
|
485
|
-
if (!semanticValueTrimmed && numericSlotValueTrimmed) {
|
|
486
|
-
cardVarMappedSynced[semanticVarName] = cardVarMappedSynced[numericSlotKey];
|
|
487
|
-
}
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Carousel numeric placeholder ({{3}}, {{4}}, …): the slot key IS the token digit itself —
|
|
492
|
-
// numbers are allocated globally across the whole carousel (getNextCarouselVarToken), not
|
|
493
|
-
// re-numbered per card, so a position-based key here would target the wrong slot. Its value
|
|
494
|
-
// may be a mustache-wrapped tag reference (e.g. "3" -> "{{last_name}}") — mirror that onto the
|
|
495
|
-
// bare semantic key too so VarSegment editors keyed by tag name also prepopulate.
|
|
496
|
-
const slotValueTrimmed = String(cardVarMappedSynced[semanticVarName] ?? '').trim();
|
|
497
|
-
if (!slotValueTrimmed) return;
|
|
498
|
-
const mustacheInnerMatch = slotValueTrimmed.match(/^\{\{([^}]+)\}\}$/);
|
|
499
|
-
const innerSemanticName = mustacheInnerMatch?.[1]?.trim();
|
|
500
|
-
if (!innerSemanticName || innerSemanticName === semanticVarName) return;
|
|
501
|
-
const existingSemanticValue = String(cardVarMappedSynced[innerSemanticName] ?? '').trim();
|
|
502
|
-
if (!existingSemanticValue) {
|
|
503
|
-
cardVarMappedSynced[innerSemanticName] = cardVarMappedSynced[semanticVarName];
|
|
462
|
+
const numericSlotKey = String(slotIndexZeroBased + 1);
|
|
463
|
+
const semanticValueTrimmed = String(cardVarMappedSynced[semanticVarName] ?? '').trim();
|
|
464
|
+
const numericSlotValueTrimmed = String(cardVarMappedSynced[numericSlotKey] ?? '').trim();
|
|
465
|
+
if (!semanticValueTrimmed && numericSlotValueTrimmed) {
|
|
466
|
+
cardVarMappedSynced[semanticVarName] = cardVarMappedSynced[numericSlotKey];
|
|
504
467
|
}
|
|
505
468
|
});
|
|
506
469
|
return cardVarMappedSynced;
|
|
@@ -220,8 +220,7 @@ const baseProps = {
|
|
|
220
220
|
onDescriptionChange: jest.fn(),
|
|
221
221
|
onTitleAddVar: jest.fn(),
|
|
222
222
|
onDescriptionAddVar: jest.fn(),
|
|
223
|
-
|
|
224
|
-
onDescTagSelect: jest.fn(),
|
|
223
|
+
onTagSelect: jest.fn(),
|
|
225
224
|
onContextChange: jest.fn(),
|
|
226
225
|
renderEditMessage: (val) => <div data-testid="render-edit-message">{val}</div>,
|
|
227
226
|
onFieldChange: jest.fn(),
|