@capillarytech/creatives-library 8.0.223-alpha.0 → 8.0.224
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
package/utils/tagValidations.js
CHANGED
|
@@ -19,7 +19,6 @@ const SUBTAGS = 'subtags';
|
|
|
19
19
|
* @param {Object} tagObject - The tagLookupMap.
|
|
20
20
|
*/
|
|
21
21
|
export const checkSupport = (response = {}, tagObject = {}, eventContextTags = [], isLiquidFlow = false, forwardedTags = {}) => {
|
|
22
|
-
|
|
23
22
|
const supportedList = [];
|
|
24
23
|
// Verifies the presence of the tag in the 'Add Labels' section.
|
|
25
24
|
// Incase of journey event context the tags won't be available in the tagObject(tagLookupMap).
|
|
@@ -40,7 +39,7 @@ export const checkSupport = (response = {}, tagObject = {}, eventContextTags = [
|
|
|
40
39
|
let updatedChildName = childName;
|
|
41
40
|
let updatedWithoutDotChildName = childName;
|
|
42
41
|
if (childName?.includes(".")) {
|
|
43
|
-
updatedChildName =
|
|
42
|
+
updatedChildName = `.${childName?.split(".")?.[1]}`;
|
|
44
43
|
updatedWithoutDotChildName = childName?.split(".")?.[1];
|
|
45
44
|
}
|
|
46
45
|
if (tagObject?.[parentTag]) {
|
|
@@ -71,7 +70,6 @@ export const checkSupport = (response = {}, tagObject = {}, eventContextTags = [
|
|
|
71
70
|
if (item?.children?.length) {
|
|
72
71
|
processChildren(item?.name, item?.children);
|
|
73
72
|
}
|
|
74
|
-
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
|
|
@@ -80,19 +78,19 @@ export const checkSupport = (response = {}, tagObject = {}, eventContextTags = [
|
|
|
80
78
|
|
|
81
79
|
const handleForwardedTags = (forwardedTags) => {
|
|
82
80
|
const result = [];
|
|
83
|
-
Object.keys(forwardedTags).forEach(key => {
|
|
81
|
+
Object.keys(forwardedTags).forEach((key) => {
|
|
84
82
|
result.push(key); // Add the main key to the result array
|
|
85
|
-
|
|
83
|
+
|
|
86
84
|
// Check if there are subtags for the current key
|
|
87
85
|
if (forwardedTags[key].subtags) {
|
|
88
86
|
// If subtags exist, add all subtag keys to the result array
|
|
89
|
-
Object.keys(forwardedTags[key].subtags).forEach(subkey => {
|
|
90
|
-
|
|
87
|
+
Object.keys(forwardedTags[key].subtags).forEach((subkey) => {
|
|
88
|
+
result.push(subkey);
|
|
91
89
|
});
|
|
92
90
|
}
|
|
93
91
|
});
|
|
94
92
|
return result;
|
|
95
|
-
}
|
|
93
|
+
};
|
|
96
94
|
|
|
97
95
|
/**
|
|
98
96
|
* Extracts the names from the given data.
|
|
@@ -155,7 +153,7 @@ export const validateTags = ({
|
|
|
155
153
|
unsupportedTags: [],
|
|
156
154
|
isBraceError: false,
|
|
157
155
|
};
|
|
158
|
-
if(tags && tags.length) {
|
|
156
|
+
if (tags && tags.length) {
|
|
159
157
|
lodashForEach(tags, ({
|
|
160
158
|
definition: {
|
|
161
159
|
supportedModules,
|
|
@@ -216,7 +214,7 @@ export const validateTags = ({
|
|
|
216
214
|
// validations (eg button) are handled on valid property coming from the response.
|
|
217
215
|
response.isBraceError ? response.valid = false : response.valid = true;
|
|
218
216
|
return response;
|
|
219
|
-
}
|
|
217
|
+
};
|
|
220
218
|
|
|
221
219
|
/**
|
|
222
220
|
* Checks if the given tag is supported based on the injected tags.
|
|
@@ -233,25 +231,25 @@ export const checkIfSupportedTag = (checkingTag, injectedTags) => {
|
|
|
233
231
|
result = true;
|
|
234
232
|
}
|
|
235
233
|
});
|
|
236
|
-
|
|
234
|
+
|
|
237
235
|
return result;
|
|
238
|
-
}
|
|
236
|
+
};
|
|
239
237
|
|
|
240
238
|
const indexOfEnd = (targetString, string) => {
|
|
241
|
-
|
|
239
|
+
const io = targetString.indexOf(string);
|
|
242
240
|
return io == -1 ? -1 : io + string.length;
|
|
243
|
-
}
|
|
241
|
+
};
|
|
244
242
|
|
|
245
243
|
export const skipTags = (tag) => {
|
|
246
244
|
// If the tag contains the word "entryTrigger.", then it's an event context tag and should not be skipped.
|
|
247
245
|
if (tag?.match(ENTRY_TRIGGER_TAG_REGEX)) {
|
|
248
246
|
return false;
|
|
249
247
|
}
|
|
250
|
-
const regexGroups = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)","Link_to_[a-zA-z]","SURVEY.*.TOKEN", "^[A-Za-z].*\\([a-zA-Z\\d]*\\)"];
|
|
248
|
+
const regexGroups = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)", "Link_to_[a-zA-z]", "SURVEY.*.TOKEN", "^[A-Za-z].*\\([a-zA-Z\\d]*\\)"];
|
|
251
249
|
let skipped = false;
|
|
252
250
|
lodashForEach(regexGroups, (group) => {
|
|
253
251
|
const groupRegex = new RegExp(group, "g");
|
|
254
|
-
|
|
252
|
+
const match = groupRegex.exec(tag);
|
|
255
253
|
if (match !== null ) {
|
|
256
254
|
skipped = true;
|
|
257
255
|
return true;
|
|
@@ -259,7 +257,7 @@ export const skipTags = (tag) => {
|
|
|
259
257
|
return true;
|
|
260
258
|
});
|
|
261
259
|
return skipped;
|
|
262
|
-
}
|
|
260
|
+
};
|
|
263
261
|
|
|
264
262
|
export const transformInjectedTags = (tags) => {
|
|
265
263
|
lodashForEach(tags, (tag) => {
|
|
@@ -274,35 +272,100 @@ export const transformInjectedTags = (tags) => {
|
|
|
274
272
|
if (subKey !== '') {
|
|
275
273
|
temp['tag-header'] = true;
|
|
276
274
|
if (subKey !== SUBTAGS) {
|
|
277
|
-
temp.subtags =lodashCloneDeep(temp[subKey]);
|
|
275
|
+
temp.subtags = lodashCloneDeep(temp[subKey]);
|
|
278
276
|
delete temp[subKey];
|
|
279
277
|
}
|
|
280
278
|
temp.subtags = transformInjectedTags(temp.subtags);
|
|
281
279
|
}
|
|
282
280
|
});
|
|
283
281
|
return tags;
|
|
284
|
-
}
|
|
282
|
+
};
|
|
285
283
|
|
|
286
284
|
//checks if the opening curly brackets have corresponding closing brackets
|
|
287
285
|
export const validateIfTagClosed = (value) => {
|
|
288
286
|
if (value.includes("{{{{") || value.includes("}}}}")) {
|
|
289
287
|
return false;
|
|
290
288
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
289
|
+
const regex1 = /{{.*?}}/g;
|
|
290
|
+
const regex2 = /{{/g;
|
|
291
|
+
const regex3 = /}}/g;
|
|
294
292
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
const l1 = value.match(regex1)?.length;
|
|
294
|
+
const l2 = value.match(regex2)?.length;
|
|
295
|
+
const l3 = value.match(regex3)?.length;
|
|
298
296
|
|
|
299
297
|
return (l1 == l2 && l2 == l3 && l1 == l3);
|
|
300
|
-
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Validates tag format: ensures tags are in format {{tag_name}} and checks for invalid patterns
|
|
302
|
+
* Validates against:
|
|
303
|
+
* - Single braces like {tag} (must be {{tag}})
|
|
304
|
+
* - Invalid patterns like {{first or first}}, {{first and first}}
|
|
305
|
+
* - Empty tag names
|
|
306
|
+
* - Unclosed single braces within tag names
|
|
307
|
+
* @param {string} textContent - The text content to validate
|
|
308
|
+
* @returns {boolean} - True if all tags have valid format, false otherwise
|
|
309
|
+
*/
|
|
310
|
+
export const validateTagFormat = (textContent) => {
|
|
311
|
+
// Find all potential tag patterns {{tag_name}}
|
|
312
|
+
const tagPattern = /{{[^}]*}}/g;
|
|
313
|
+
const matches = textContent.match(tagPattern) || [];
|
|
314
|
+
|
|
315
|
+
// Remove all valid {{tag}} patterns from content to check for invalid braces
|
|
316
|
+
let contentWithoutValidTags = textContent;
|
|
317
|
+
matches.forEach((match) => {
|
|
318
|
+
contentWithoutValidTags = contentWithoutValidTags.replace(match, '');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Check if there are any remaining braces (single braces or unclosed braces)
|
|
322
|
+
// These would be invalid patterns like {tag}, {first, first}, etc.
|
|
323
|
+
if (contentWithoutValidTags.includes('{') || contentWithoutValidTags.includes('}')) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Check each tag for valid format
|
|
328
|
+
const allTagsValid = matches.every((match) => {
|
|
329
|
+
// Valid tag format: {{tag_name}} - must start with {{ and end with }}
|
|
330
|
+
if (!match.startsWith('{{') || !match.endsWith('}}')) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Extract tag name (content between {{ and }})
|
|
335
|
+
const tagName = match.slice(2, -2).trim();
|
|
336
|
+
|
|
337
|
+
// Tag name should not be empty
|
|
338
|
+
if (!tagName) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Check for invalid patterns in tag name
|
|
343
|
+
// Invalid patterns: "first or first", "first and first", etc.
|
|
344
|
+
const invalidPatterns = [
|
|
345
|
+
/\s+or\s+/i, // " or " as separate word (e.g., "first or first")
|
|
346
|
+
/\s+and\s+/i, // " and " as separate word
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
const hasInvalidPattern = invalidPatterns.some((pattern) => pattern.test(tagName));
|
|
350
|
+
if (hasInvalidPattern) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check for unclosed single braces in tag name (e.g., {{first{name}})
|
|
355
|
+
const singleOpenBraces = (tagName.match(/{/g) || []).length;
|
|
356
|
+
const singleCloseBraces = (tagName.match(/}/g) || []).length;
|
|
357
|
+
if (singleOpenBraces !== singleCloseBraces) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return true;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return allTagsValid;
|
|
301
365
|
};
|
|
302
366
|
|
|
303
367
|
//replaces encoded string with their respective characters
|
|
304
368
|
export const preprocessHtml = (content) => {
|
|
305
|
-
|
|
306
369
|
const replacements = {
|
|
307
370
|
"'": "'",
|
|
308
371
|
""": "'",
|
|
@@ -310,7 +373,7 @@ export const preprocessHtml = (content) => {
|
|
|
310
373
|
"&": "&",
|
|
311
374
|
"<": "<",
|
|
312
375
|
">": ">",
|
|
313
|
-
"\n": "",
|
|
376
|
+
"\n": "", // Handling newlines by replacing them with an empty string
|
|
314
377
|
};
|
|
315
378
|
|
|
316
379
|
|
|
@@ -324,28 +387,22 @@ export const preprocessHtml = (content) => {
|
|
|
324
387
|
});
|
|
325
388
|
|
|
326
389
|
// Step 2: Perform the standard replacements on the entire content
|
|
327
|
-
return contentWithStyleFixes?.replace(/'|"|&|<|>|"|\n/g, match => replacements[match]);
|
|
390
|
+
return contentWithStyleFixes?.replace(/'|"|&|<|>|"|\n/g, (match) => replacements[match]);
|
|
328
391
|
};
|
|
329
392
|
|
|
330
393
|
//this is used to get the subtags from custom or extended tags
|
|
331
|
-
export const getTagMapValue = (object = {}) =>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return { ...acc, ...(current.subtags ?? {}) };
|
|
344
|
-
}
|
|
345
|
-
// If no subtags → keep the tag itself
|
|
346
|
-
return { ...acc, [key]: current };
|
|
347
|
-
}, {});
|
|
348
|
-
};
|
|
394
|
+
export const getTagMapValue = (object = {}) => Object.values(
|
|
395
|
+
object
|
|
396
|
+
).reduce((acc, current) => ({ ...acc, ...current?.subtags ?? {} }), {});
|
|
397
|
+
|
|
398
|
+
export const getLoyaltyTagsMapValue = (object = {}) => Object.entries(object).reduce((acc, [key, current]) => {
|
|
399
|
+
if (current?.subtags && Object.keys(current.subtags).length > 0) {
|
|
400
|
+
// If subtags exist → merge them
|
|
401
|
+
return { ...acc, ...(current.subtags ?? {}) };
|
|
402
|
+
}
|
|
403
|
+
// If no subtags → keep the tag itself
|
|
404
|
+
return { ...acc, [key]: current };
|
|
405
|
+
}, {});
|
|
349
406
|
|
|
350
407
|
|
|
351
408
|
/**
|
|
@@ -354,27 +411,25 @@ export const getLoyaltyTagsMapValue = (object = {}) => {
|
|
|
354
411
|
* @param {Object} object - The input object containing top-level keys with optional subtags.
|
|
355
412
|
* @returns {Object} - A flat map containing all top-level keys and their subtags.
|
|
356
413
|
*/
|
|
357
|
-
export const getForwardedMapValues = (object = {}) => {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}, {});
|
|
380
|
-
};
|
|
414
|
+
export const getForwardedMapValues = (object = {}) => Object?.entries(object)?.reduce((acc, [key, current]) => {
|
|
415
|
+
// Check if current has 'subtags' and it's an object
|
|
416
|
+
if (current && current?.subtags && typeof current?.subtags === 'object') {
|
|
417
|
+
// Add the top-level key with its 'name' and 'desc'
|
|
418
|
+
acc[key] = {
|
|
419
|
+
name: current?.name,
|
|
420
|
+
desc: current?.desc,
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// Merge the subtags into the accumulator
|
|
424
|
+
acc = { ...acc, ...current?.subtags };
|
|
425
|
+
} else if (current && typeof current === 'object') {
|
|
426
|
+
// If no 'subtags', add the top-level key with its 'name' and 'desc'
|
|
427
|
+
acc[key] = {
|
|
428
|
+
name: current?.name,
|
|
429
|
+
desc: current?.desc,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// If the current entry is not an object or lacks 'name'/'desc', skip it
|
|
434
|
+
return acc;
|
|
435
|
+
}, {});
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
INITIAL_PAYLOAD, EMAIL, TEST, DESKTOP, ACTIVE, MOBILE,
|
|
53
53
|
} from './constants';
|
|
54
54
|
import { GLOBAL_CONVERT_OPTIONS } from '../FormBuilder/constants';
|
|
55
|
-
import { validateIfTagClosed } from '../../utils/tagValidations';
|
|
55
|
+
import { validateIfTagClosed, validateTagFormat } from '../../utils/tagValidations';
|
|
56
56
|
|
|
57
57
|
const TestAndPreviewSlidebox = (props) => {
|
|
58
58
|
const {
|
|
@@ -110,7 +110,12 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
110
110
|
requiredTags.some((tag) => !customValues[tag.fullPath]) || !isContentValid
|
|
111
111
|
), [requiredTags, customValues, isContentValid]);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Validates tags in content: checks for proper tag format and balanced braces
|
|
115
|
+
* Uses validateIfTagClosed and validateTagFormat from utils/tagValidations
|
|
116
|
+
* @param {string} content - The HTML content to validate
|
|
117
|
+
* @returns {boolean} - True if content is valid, false otherwise
|
|
118
|
+
*/
|
|
114
119
|
const validateContentTags = (content) => {
|
|
115
120
|
if (!content) return true;
|
|
116
121
|
|
|
@@ -127,66 +132,14 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
127
132
|
return true;
|
|
128
133
|
}
|
|
129
134
|
|
|
130
|
-
// First check if tags are properly closed using the utility function
|
|
135
|
+
// First check if tags are properly closed using the utility function from tagValidations
|
|
131
136
|
// This validates that all opening braces have corresponding closing braces
|
|
132
137
|
if (!validateIfTagClosed(textContent)) {
|
|
133
138
|
return false;
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
// Now validate tag format: tags must be in format {{tag_name}}
|
|
137
|
-
|
|
138
|
-
const tagPattern = /{{[^}]*}}/g;
|
|
139
|
-
const matches = textContent.match(tagPattern) || [];
|
|
140
|
-
|
|
141
|
-
// Remove all valid {{tag}} patterns from content to check for invalid braces
|
|
142
|
-
let contentWithoutValidTags = textContent;
|
|
143
|
-
matches.forEach((match) => {
|
|
144
|
-
contentWithoutValidTags = contentWithoutValidTags.replace(match, '');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Check if there are any remaining braces (single braces or unclosed braces)
|
|
148
|
-
// These would be invalid patterns like {tag}, {first, first}, etc.
|
|
149
|
-
if (contentWithoutValidTags.includes('{') || contentWithoutValidTags.includes('}')) {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Check each tag for valid format
|
|
154
|
-
for (const match of matches) {
|
|
155
|
-
// Valid tag format: {{tag_name}} - must start with {{ and end with }}
|
|
156
|
-
if (!match.startsWith('{{') || !match.endsWith('}}')) {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Extract tag name (content between {{ and }})
|
|
161
|
-
const tagName = match.slice(2, -2).trim();
|
|
162
|
-
|
|
163
|
-
// Tag name should not be empty
|
|
164
|
-
if (!tagName) {
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Check for invalid patterns in tag name
|
|
169
|
-
// Invalid patterns: "first or first", "first and first", etc.
|
|
170
|
-
const invalidPatterns = [
|
|
171
|
-
/\s+or\s+/i, // " or " as separate word (e.g., "first or first")
|
|
172
|
-
/\s+and\s+/i, // " and " as separate word
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
for (const pattern of invalidPatterns) {
|
|
176
|
-
if (pattern.test(tagName)) {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Check for unclosed single braces in tag name (e.g., {{first{name}})
|
|
182
|
-
const singleOpenBraces = (tagName.match(/{/g) || []).length;
|
|
183
|
-
const singleCloseBraces = (tagName.match(/}/g) || []).length;
|
|
184
|
-
if (singleOpenBraces !== singleCloseBraces) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return true;
|
|
142
|
+
return validateTagFormat(textContent);
|
|
190
143
|
} catch (error) {
|
|
191
144
|
// If conversion fails, fall back to validating the original content
|
|
192
145
|
console.warn('Error converting content for validation:', error);
|
|
@@ -200,49 +153,7 @@ const TestAndPreviewSlidebox = (props) => {
|
|
|
200
153
|
return false;
|
|
201
154
|
}
|
|
202
155
|
|
|
203
|
-
|
|
204
|
-
const matches = content.match(tagPattern) || [];
|
|
205
|
-
|
|
206
|
-
// Remove all valid {{tag}} patterns from content to check for invalid braces
|
|
207
|
-
let contentWithoutValidTags = content;
|
|
208
|
-
matches.forEach((match) => {
|
|
209
|
-
contentWithoutValidTags = contentWithoutValidTags.replace(match, '');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// Check if there are any remaining braces (single braces or unclosed braces)
|
|
213
|
-
if (contentWithoutValidTags.includes('{') || contentWithoutValidTags.includes('}')) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
for (const match of matches) {
|
|
218
|
-
if (!match.startsWith('{{') || !match.endsWith('}}')) {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const tagName = match.slice(2, -2).trim();
|
|
223
|
-
if (!tagName) {
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const invalidPatterns = [
|
|
228
|
-
/\s+or\s+/i,
|
|
229
|
-
/\s+and\s+/i,
|
|
230
|
-
];
|
|
231
|
-
|
|
232
|
-
for (const pattern of invalidPatterns) {
|
|
233
|
-
if (pattern.test(tagName)) {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const singleOpenBraces = (tagName.match(/{/g) || []).length;
|
|
239
|
-
const singleCloseBraces = (tagName.match(/}/g) || []).length;
|
|
240
|
-
if (singleOpenBraces !== singleCloseBraces) {
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return true;
|
|
156
|
+
return validateTagFormat(content);
|
|
246
157
|
}
|
|
247
158
|
};
|
|
248
159
|
|