@capillarytech/creatives-library 8.0.309 → 8.0.310

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.
Files changed (79) hide show
  1. package/constants/unified.js +1 -5
  2. package/initialState.js +2 -0
  3. package/package.json +1 -1
  4. package/services/api.js +0 -17
  5. package/services/tests/api.test.js +0 -85
  6. package/utils/common.js +8 -5
  7. package/utils/commonUtils.js +93 -46
  8. package/utils/tagValidations.js +223 -83
  9. package/utils/tests/commonUtil.test.js +124 -316
  10. package/utils/tests/tagValidations.test.js +358 -441
  11. package/v2Components/CommonTestAndPreview/SendTestMessage.js +49 -78
  12. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -134
  13. package/v2Components/CommonTestAndPreview/actions.js +0 -10
  14. package/v2Components/CommonTestAndPreview/constants.js +1 -15
  15. package/v2Components/CommonTestAndPreview/index.js +19 -80
  16. package/v2Components/CommonTestAndPreview/messages.js +0 -94
  17. package/v2Components/CommonTestAndPreview/reducer.js +0 -10
  18. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +0 -53
  19. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
  20. package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -36
  21. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
  22. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +0 -377
  23. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
  24. package/v2Components/ErrorInfoNote/index.js +5 -2
  25. package/v2Components/FormBuilder/index.js +203 -137
  26. package/v2Components/FormBuilder/messages.js +8 -0
  27. package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
  28. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
  30. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
  31. package/v2Containers/Cap/mockData.js +14 -0
  32. package/v2Containers/Cap/reducer.js +55 -3
  33. package/v2Containers/Cap/tests/reducer.test.js +102 -0
  34. package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -5
  35. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
  36. package/v2Containers/CreativesContainer/constants.js +0 -6
  37. package/v2Containers/CreativesContainer/index.js +7 -47
  38. package/v2Containers/Email/index.js +5 -1
  39. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
  40. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +120 -20
  41. package/v2Containers/FTP/index.js +51 -2
  42. package/v2Containers/FTP/messages.js +4 -0
  43. package/v2Containers/InApp/index.js +107 -35
  44. package/v2Containers/InApp/tests/index.test.js +6 -17
  45. package/v2Containers/InappAdvance/index.js +112 -4
  46. package/v2Containers/InappAdvance/tests/index.test.js +0 -2
  47. package/v2Containers/Line/Container/Text/index.js +1 -0
  48. package/v2Containers/MobilePush/Create/index.js +19 -59
  49. package/v2Containers/MobilePush/Edit/index.js +20 -48
  50. package/v2Containers/MobilePushNew/index.js +32 -12
  51. package/v2Containers/MobilepushWrapper/index.js +1 -3
  52. package/v2Containers/Rcs/index.js +37 -12
  53. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1276 -1408
  54. package/v2Containers/Sms/Create/index.js +3 -39
  55. package/v2Containers/Sms/Create/messages.js +0 -4
  56. package/v2Containers/Sms/Edit/index.js +3 -35
  57. package/v2Containers/Sms/commonMethods.js +6 -3
  58. package/v2Containers/SmsTrai/Edit/index.js +47 -11
  59. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +294 -327
  60. package/v2Containers/SmsWrapper/index.js +0 -2
  61. package/v2Containers/TemplatesV2/index.js +13 -28
  62. package/v2Containers/Viber/index.js +1 -0
  63. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
  64. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
  65. package/v2Containers/WebPush/Create/index.js +2 -2
  66. package/v2Containers/WebPush/Create/utils/validation.js +8 -17
  67. package/v2Containers/WebPush/Create/utils/validation.test.js +24 -44
  68. package/v2Containers/Whatsapp/index.js +17 -9
  69. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5246
  70. package/v2Containers/Zalo/index.js +11 -3
  71. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
  72. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -284
  73. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -72
  74. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
  75. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -657
  76. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +0 -172
  77. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -466
  78. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
  79. package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
@@ -8,124 +8,264 @@
8
8
 
9
9
  import lodashForEach from 'lodash/forEach';
10
10
  import lodashCloneDeep from 'lodash/cloneDeep';
11
- import { ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS, TAG_CONTENT_REGEX, UNSUBSCRIBE_TAG, UNSUBSCRIBE_TAG_REGEX } from '../constants/unified';
11
+ import { ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS } from '../constants/unified';
12
12
 
13
13
  const DEFAULT = 'default';
14
14
  const SUBTAGS = 'subtags';
15
15
 
16
- export const hasUnsubscribeTag = (content) =>
17
- typeof content === 'string' && UNSUBSCRIBE_TAG_REGEX.test(content);
18
-
19
16
  /**
20
- * Shared core for tag validation. Used by validateTags (tagValidations) and FormBuilder.validateTags.
21
- * @param {Object} params
22
- * @param {string} params.contentForBraceCheck - String to run validateIfTagClosed on.
23
- * @param {string} params.contentForUnsubscribeScan - String to scan for {{...}} unsubscribe variants.
24
- * @param {Array} [params.tags] - Tag definitions (for definition-based missing tags).
25
- * @param {string} [params.currentModule] - Module context (e.g. 'default', 'outbound').
26
- * @param {boolean} [params.isFullMode] - If true, skip unsubscribe checks.
27
- * @param {Array} [params.initialMissingTags=null] - If set, use instead of computing from definitions.
28
- * @param {function} [params.skipTagsFn=skipTags] - skipTags implementation.
29
- * @param {boolean} [params.includeIsContentEmpty=false] - If true, response includes isContentEmpty: false.
30
- * @returns {{ valid: boolean, missingTags: string[], isBraceError: boolean }}
17
+ * Checks if the response object of Tags supports the Tags added in the Add Labels Section
18
+ * @param {Object} response - The response object to check.
19
+ * @param {Object} tagObject - The tagLookupMap.
31
20
  */
32
- export const validateTagsCore = ({
33
- contentForBraceCheck,
34
- contentForUnsubscribeScan,
35
- tags,
36
- currentModule,
37
- isFullMode,
38
- initialMissingTags = null,
39
- skipTagsFn = skipTags,
40
- includeIsContentEmpty = false,
41
- }) => {
42
- const response = {
43
- valid: true,
44
- missingTags: [],
45
- isBraceError: false,
46
- ...(includeIsContentEmpty && { isContentEmpty: false }),
21
+ export const checkSupport = (response = {}, tagObject = {}, eventContextTags = [], isLiquidFlow = false, forwardedTags = {}) => {
22
+ const supportedList = [];
23
+ // Verifies the presence of the tag in the 'Add Labels' section.
24
+ // Incase of journey event context the tags won't be available in the tagObject(tagLookupMap).
25
+ //Here forwardedTags only use in case of loyalty module
26
+ const mappedForwardedTags = handleForwardedTags(forwardedTags);
27
+ const checkNameInTagObjectOrEventContext = (name) => !!tagObject[name] || eventContextTags?.some((tag) => tag?.tagName === name) || mappedForwardedTags.includes(name);
28
+
29
+ // Verify if childTag is a valid sub-tag of parentTag from the 'Add Labels' section or if it's unsupported.
30
+ const checkSubtags = (parentTag, childName) => {
31
+ // For event context tags the parentTag will be the event context tag name and subtags will be the child attributes for leaderboards
32
+ if (checkNameInTagObjectOrEventContext(parentTag) && isLiquidFlow && eventContextTags?.length) {
33
+ const childNameAfterDot = childName?.split(".")?.[1];
34
+ if (eventContextTags?.some((tag) => tag?.subTags?.includes(childNameAfterDot))) {
35
+ supportedList.push(childName);
36
+ }
37
+ }
38
+ if (!tagObject?.[parentTag] && !mappedForwardedTags.includes(parentTag)) return false;
39
+ let updatedChildName = childName;
40
+ let updatedWithoutDotChildName = childName;
41
+ if (childName?.includes(".")) {
42
+ updatedChildName = `.${childName?.split(".")?.[1]}`;
43
+ updatedWithoutDotChildName = childName?.split(".")?.[1];
44
+ }
45
+ if (tagObject?.[parentTag]) {
46
+ const subTags = tagObject?.[parentTag]?.definition?.subtags;
47
+ return subTags?.includes(updatedChildName);
48
+ }
49
+ return mappedForwardedTags.includes(updatedChildName) || mappedForwardedTags.includes(updatedWithoutDotChildName) || mappedForwardedTags.includes(childName);
47
50
  };
48
51
 
49
- if (tags?.length && !isFullMode) {
50
- if (initialMissingTags == null) {
51
- // Definition-based: same as original tagValidations (when caller does not pass initialMissingTags)
52
- lodashForEach(tags, ({
53
- definition: {
54
- supportedModules,
55
- value,
56
- },
57
- }) => {
58
- if (value === UNSUBSCRIBE_TAG) {
59
- lodashForEach(supportedModules, (module) => {
60
- if (module.mandatory && (currentModule === module.context)) {
61
- if (!hasUnsubscribeTag(contentForUnsubscribeScan)) {
62
- response.missingTags.push(value);
63
- }
64
- }
65
- });
66
- }
52
+ //Recursively checks if the childTag is actually a Sub-tag of the ParentTag
53
+ const processChildren = (parentTag = "", children = []) => {
54
+ for (const child of children) {
55
+ if (checkSubtags(parentTag, child?.name)) {
56
+ supportedList.push(child?.name);
57
+ }
58
+ if (child?.children?.length) {
59
+ processChildren(child?.name, child?.children);
60
+ }
61
+ }
62
+ };
63
+
64
+ //Checks if the tag is present in the Add Label Section
65
+ for (const item of response?.data || []) {
66
+ if (checkNameInTagObjectOrEventContext(item?.name)) {
67
+ supportedList?.push(item?.name);
68
+ }
69
+ //Repeat the process for subtags
70
+ if (item?.children?.length) {
71
+ processChildren(item?.name, item?.children);
72
+ }
73
+ }
74
+
75
+
76
+ return supportedList;
77
+ };
78
+
79
+ const handleForwardedTags = (forwardedTags) => {
80
+ const result = [];
81
+ Object.keys(forwardedTags).forEach((key) => {
82
+ result.push(key); // Add the main key to the result array
83
+
84
+ // Check if there are subtags for the current key
85
+ if (forwardedTags[key].subtags) {
86
+ // If subtags exist, add all subtag keys to the result array
87
+ Object.keys(forwardedTags[key].subtags).forEach((subkey) => {
88
+ result.push(subkey);
67
89
  });
68
- } else {
69
- response.missingTags = [...initialMissingTags];
70
90
  }
91
+ });
92
+ return result;
93
+ };
71
94
 
72
- TAG_CONTENT_REGEX.lastIndex = 0;
73
- let match = TAG_CONTENT_REGEX.exec(contentForUnsubscribeScan);
74
- while (match !== null) {
75
- const tagValue = match[1]?.trim();
76
- const ifSkipped = skipTagsFn(tagValue);
77
- if (ifSkipped && tagValue?.indexOf(UNSUBSCRIBE_TAG) !== -1) {
78
- const missingTagIndex = response?.missingTags?.indexOf(UNSUBSCRIBE_TAG);
79
- if (missingTagIndex !== -1) {
80
- response?.missingTags?.splice(missingTagIndex, 1);
81
- }
82
- }
83
- match = TAG_CONTENT_REGEX.exec(contentForUnsubscribeScan);
95
+ /**
96
+ * Extracts the names from the given data.
97
+ * @param {Array} data - The data to extract names from.
98
+ * @returns {Array} - The extracted names.
99
+ */
100
+ export function extractNames(data) {
101
+ const names = [];
102
+
103
+ function traverse(node) {
104
+ if (node?.name) {
105
+ names.push(node?.name);
84
106
  }
85
107
 
86
- if (response?.missingTags?.length > 0) {
87
- response.valid = false;
108
+ if (node?.children?.length > 0) {
109
+ node?.children?.forEach((child) => traverse(child));
88
110
  }
89
111
  }
90
112
 
91
- response.isBraceError = !validateIfTagClosed(contentForBraceCheck);
92
- if (response.isBraceError) {
93
- response.valid = false;
94
- } else if (response?.missingTags?.length > 0) {
95
- response.valid = false;
113
+ data?.forEach((item) => traverse(item));
114
+
115
+ return names;
116
+ }
117
+
118
+ // Helper to check if a tag is inside a {% ... %} block
119
+ // Handles all Liquid block types: {% %}, {%- -%}, {%- %}, {% -%}
120
+ // Content inside {% %} blocks can contain any dynamic tags and should not be validated
121
+ export const isInsideLiquidBlock = (content, tagIndex) => {
122
+ if (!content || tagIndex < 0 || tagIndex >= content.length) {
123
+ return false;
124
+ }
125
+
126
+ // Check if tagIndex is at the start of a {% block
127
+ // Need to check a few characters ahead to catch {% patterns
128
+ const checkWindow = content.substring(Math.max(0, tagIndex - 1), Math.min(content.length, tagIndex + 3));
129
+ if (/{%-?/.test(checkWindow) && content.substring(tagIndex, tagIndex + 2) === '{%') {
130
+ return true;
96
131
  }
97
- return response;
132
+
133
+ // Check content up to tagIndex to see if we're inside any {% %} block
134
+ // We need to properly handle Liquid tags:
135
+ // - Block opening tags: {% for %}, {% if %}, {% unless %}, {% case %}, {% capture %}, etc.
136
+ // These OPEN a block that needs to be closed with an "end" tag
137
+ // - Block closing tags: {% endfor %}, {% endif %}, {% endunless %}, etc.
138
+ // These CLOSE a block opened by a corresponding opening tag
139
+ // - Self-contained tags: {% assign %}, {% comment %}, etc.
140
+ // These are blocks themselves (from {% to %})
141
+ const contentBeforeTag = content.substring(0, tagIndex);
142
+
143
+ // Regex to match Liquid block opening tags (for, if, unless, case, capture, tablerow, raw, comment)
144
+ // These tags OPEN a block that needs to be closed with an "end" tag
145
+ const blockOpeningTags = ['for', 'if', 'unless', 'case', 'capture', 'tablerow', 'raw', 'comment'];
146
+ const blockOpeningPattern = blockOpeningTags.map(tag => `\\b${tag}\\b`).join('|');
147
+ const blockOpeningRegex = new RegExp(`{%-?\\s*(${blockOpeningPattern})[^%]*%}`, 'gi');
148
+
149
+ // Regex to match Liquid block closing tags (endfor, endif, endunless, endcase, endcapture, etc.)
150
+ const blockClosingTags = ['endfor', 'endif', 'endunless', 'endcase', 'endcapture', 'endtablerow', 'endraw', 'endcomment'];
151
+ const blockClosingPattern = blockClosingTags.map(tag => `\\b${tag}\\b`).join('|');
152
+ const blockClosingRegex = new RegExp(`{%-?\\s*(${blockClosingPattern})[^%]*%}`, 'gi');
153
+
154
+ // Find all block opening tags before tagIndex
155
+ const blockOpenings = Array.from(contentBeforeTag.matchAll(blockOpeningRegex), (match) => match.index);
156
+
157
+ // Find all block closing tags before tagIndex
158
+ const blockClosings = Array.from(contentBeforeTag.matchAll(blockClosingRegex), (match) => match.index);
159
+
160
+ // Count unmatched opening blocks (for, if, etc.)
161
+ const openBlockCount = blockOpenings.length - blockClosings.length;
162
+
163
+ // Also check for self-contained tags (assign, etc.) that create blocks from {% to %}
164
+ // These are any {% %} tags that are not block-opening or block-closing tags
165
+ const allBlockStarts = /{%-?/g;
166
+ const allBlockEnds = /-?%}/g;
167
+ const allStarts = Array.from(contentBeforeTag.matchAll(allBlockStarts), (match) => match.index);
168
+ const allEnds = Array.from(contentBeforeTag.matchAll(allBlockEnds), (match) => match.index);
169
+
170
+ // Check if we're inside a self-contained tag (more {% than %} before tagIndex, excluding block tags)
171
+ const selfContainedCount = allStarts.length - allEnds.length;
172
+
173
+ // We're inside a block if:
174
+ // 1. We're inside an unclosed block-opening tag (for, if, etc.), OR
175
+ // 2. We're inside a self-contained tag (assign, etc.)
176
+ return openBlockCount > 0 || selfContainedCount > 0;
98
177
  };
99
178
 
100
179
  /**
101
180
  * Validates the tags based on the provided parameters.
102
181
  * @param {Object} params - The parameters for tag validation.
103
- * @param {string} params.content - Content to validate.
104
- * @param {Array} [params.tagsParam] - Tag definitions.
105
- * @param {Object} [params.location] - Location with query.module.
106
- * @param {string} [params.tagModule] - Override for current module context.
107
- * @param {boolean} [params.isFullMode] - If true, skip unsubscribe checks.
108
- * @returns {{ valid: boolean, missingTags: string[], isBraceError: boolean }}
109
182
  */
110
183
  export const validateTags = ({
111
184
  content,
112
185
  tagsParam,
186
+ injectedTagsParams,
113
187
  location,
114
188
  tagModule,
189
+ eventContextTags,
115
190
  isFullMode,
116
191
  }) => {
117
192
  const tags = tagsParam;
193
+ const injectedTags = transformInjectedTags(injectedTagsParams);
118
194
  let currentModule = location?.query?.module ? location?.query?.module : DEFAULT;
119
195
  if (tagModule) {
120
196
  currentModule = tagModule;
121
197
  }
122
- return validateTagsCore({
123
- contentForBraceCheck: content,
124
- contentForUnsubscribeScan: content,
125
- tags,
126
- currentModule,
127
- isFullMode,
128
- });
198
+ const response = {
199
+ valid: true,
200
+ missingTags: [],
201
+ unsupportedTags: [],
202
+ isBraceError: false,
203
+ };
204
+ // Mandatory-tags check: only when we have a tags list and are in library mode
205
+ if (tags && tags.length && !isFullMode) {
206
+ lodashForEach(tags, ({
207
+ definition: {
208
+ supportedModules,
209
+ value,
210
+ },
211
+ }) => {
212
+ lodashForEach(supportedModules, (module) => {
213
+ if (module.mandatory && (currentModule === module.context)) {
214
+ if (content.indexOf(`{{${value}}}`) === -1) {
215
+ response.valid = false;
216
+ response.missingTags.push(value);
217
+ }
218
+ }
219
+ });
220
+ });
221
+ }
222
+ // In library mode, always scan content for {{...}} and flag unsupported tags (even when tags list is empty)
223
+ if (!isFullMode && content) {
224
+ const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
225
+ let match = regex.exec(content);
226
+ while (match !== null) {
227
+ const tagValue = match[0].substring(indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
228
+ const tagIndex = match?.index;
229
+ match = regex.exec(content);
230
+ let ifSupported = false;
231
+ lodashForEach(tags || [], (tag) => {
232
+ if (tag?.definition?.value === tagValue) {
233
+ ifSupported = true;
234
+ }
235
+ });
236
+ const ifSkipped = skipTags(tagValue);
237
+ if (ifSkipped) {
238
+ ifSupported = true;
239
+ const isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") !== -1;
240
+ if (isUnsubscribeSkipped) {
241
+ const missingTagIndex = response.missingTags.indexOf("unsubscribe");
242
+ if (missingTagIndex !== -1) {
243
+ response.missingTags.splice(missingTagIndex, 1);
244
+ }
245
+ }
246
+ }
247
+ // Journey Event Context Tags support
248
+ eventContextTags?.forEach((tag) => {
249
+ if (tagValue === tag?.tagName) {
250
+ ifSupported = true;
251
+ }
252
+ });
253
+ ifSupported = ifSupported || checkIfSupportedTag(tagValue, injectedTags);
254
+ // Only add to unsupportedTags if not inside a {% ... %} block and does not contain a dot
255
+ if (!ifSupported && !isInsideLiquidBlock(content, tagIndex) && tagValue?.indexOf('.') === -1) {
256
+ response.unsupportedTags.push(tagValue);
257
+ response.valid = false;
258
+ }
259
+ if (response.unsupportedTags.length === 0 && response.missingTags.length === 0) {
260
+ response.valid = true;
261
+ }
262
+ }
263
+ }
264
+ response.isBraceError = !validateIfTagClosed(content);
265
+ // response should not be valid if there is unbalanced bracket error, as
266
+ // validations (eg button) are handled on valid property coming from the response.
267
+ response.isBraceError ? response.valid = false : response.valid = true;
268
+ return response;
129
269
  };
130
270
 
131
271
  /**