@capillarytech/creatives-library 8.0.249 → 8.0.250-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.
Files changed (136) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/services/api.js +10 -0
  7. package/services/tests/api.test.js +18 -0
  8. package/utils/common.js +5 -0
  9. package/utils/commonUtils.js +28 -5
  10. package/utils/tests/commonUtil.test.js +224 -0
  11. package/utils/transformTemplateConfig.js +0 -10
  12. package/v2Components/CapDeviceContent/index.js +61 -56
  13. package/v2Components/CapTagList/index.js +6 -1
  14. package/v2Components/CapTagListWithInput/index.js +5 -1
  15. package/v2Components/CapTagListWithInput/messages.js +1 -1
  16. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  17. package/v2Components/ErrorInfoNote/index.js +452 -72
  18. package/v2Components/ErrorInfoNote/messages.js +22 -0
  19. package/v2Components/ErrorInfoNote/style.scss +280 -4
  20. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  21. package/v2Components/HtmlEditor/HTMLEditor.js +640 -94
  22. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
  23. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1167 -133
  24. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  25. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  26. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  27. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +13 -101
  28. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -139
  29. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  30. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  31. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
  32. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  33. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  34. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  35. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  36. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  37. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  39. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  40. package/v2Components/HtmlEditor/components/PreviewPane/index.js +11 -13
  41. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  42. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  43. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +68 -39
  44. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  45. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +391 -0
  46. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  47. package/v2Components/HtmlEditor/constants.js +42 -20
  48. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  49. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +795 -0
  50. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  51. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  52. package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
  53. package/v2Components/HtmlEditor/index.js +1 -1
  54. package/v2Components/HtmlEditor/messages.js +95 -85
  55. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -45
  56. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  57. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  58. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +134 -102
  59. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  60. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  61. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  62. package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
  63. package/v2Components/TemplatePreview/index.js +47 -32
  64. package/v2Components/TemplatePreview/messages.js +4 -0
  65. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  66. package/v2Containers/BeeEditor/index.js +172 -90
  67. package/v2Containers/BeePopupEditor/constants.js +10 -0
  68. package/v2Containers/BeePopupEditor/index.js +193 -0
  69. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  70. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
  71. package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
  72. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  73. package/v2Containers/CreativesContainer/constants.js +1 -0
  74. package/v2Containers/CreativesContainer/index.js +239 -46
  75. package/v2Containers/CreativesContainer/messages.js +8 -0
  76. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  77. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  78. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +106 -0
  79. package/v2Containers/Email/actions.js +7 -0
  80. package/v2Containers/Email/constants.js +5 -1
  81. package/v2Containers/Email/index.js +222 -27
  82. package/v2Containers/Email/messages.js +32 -0
  83. package/v2Containers/Email/reducer.js +12 -1
  84. package/v2Containers/Email/sagas.js +61 -7
  85. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  86. package/v2Containers/Email/tests/sagas.test.js +320 -29
  87. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1321 -0
  88. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +210 -15
  89. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  90. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1749 -0
  91. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  92. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  93. package/v2Containers/EmailWrapper/constants.js +2 -0
  94. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +629 -77
  95. package/v2Containers/EmailWrapper/index.js +103 -23
  96. package/v2Containers/EmailWrapper/messages.js +61 -1
  97. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
  98. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
  99. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  100. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  101. package/v2Containers/InApp/actions.js +7 -0
  102. package/v2Containers/InApp/constants.js +20 -4
  103. package/v2Containers/InApp/index.js +802 -359
  104. package/v2Containers/InApp/index.scss +4 -3
  105. package/v2Containers/InApp/messages.js +7 -3
  106. package/v2Containers/InApp/reducer.js +21 -3
  107. package/v2Containers/InApp/sagas.js +29 -9
  108. package/v2Containers/InApp/selectors.js +25 -5
  109. package/v2Containers/InApp/tests/index.test.js +154 -50
  110. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  111. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  112. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  113. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  114. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  115. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  116. package/v2Containers/InAppWrapper/constants.js +16 -0
  117. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  118. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  119. package/v2Containers/InAppWrapper/index.js +148 -0
  120. package/v2Containers/InAppWrapper/messages.js +49 -0
  121. package/v2Containers/InappAdvance/index.js +1099 -0
  122. package/v2Containers/InappAdvance/index.scss +10 -0
  123. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  124. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  125. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  126. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  127. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  128. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  129. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  130. package/v2Containers/TagList/index.js +62 -19
  131. package/v2Containers/Templates/_templates.scss +60 -1
  132. package/v2Containers/Templates/index.js +89 -4
  133. package/v2Containers/Templates/messages.js +4 -0
  134. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  135. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  136. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -0,0 +1,391 @@
1
+ /**
2
+ * ValidationTabs Component
3
+ *
4
+ * Displays validation errors in a tabbed interface with 3 categories:
5
+ * - HTML issues: General HTML/CSS validation errors and warnings
6
+ * - Label issues: Tag syntax errors (open/close tags, attributes, brackets)
7
+ * - Liquid issues: Liquid expression errors (shown when liquid content is detected, even if liquid feature is disabled)
8
+ */
9
+
10
+ import React, { useState, useMemo } from 'react';
11
+ import PropTypes from 'prop-types';
12
+ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
13
+
14
+ // Cap UI Library
15
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
16
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
17
+ import CapTab from '@capillarytech/cap-ui-library/CapTab';
18
+ import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
19
+
20
+ // Messages
21
+ import messages from './messages';
22
+ import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
23
+
24
+ // Styles
25
+ import './_validationTabs.scss';
26
+
27
+ // Constants for issue sources
28
+ const ISSUE_SOURCES = {
29
+ HTMLHINT: 'htmlhint',
30
+ CSS_VALIDATOR: 'css-validator',
31
+ CUSTOM: 'custom',
32
+ SECURITY: 'security',
33
+ LIQUID: 'liquid-validator',
34
+ };
35
+
36
+ // Label issue patterns - syntax errors related to tags
37
+ const LABEL_ISSUE_PATTERNS = [
38
+ 'tag must be paired',
39
+ 'open tag match failed',
40
+ 'closed tag match failed',
41
+ 'unclosed',
42
+ 'missing required',
43
+ 'tag-pair',
44
+ 'attr-value-not-empty',
45
+ 'attr-no-duplication',
46
+ 'tag-self-close',
47
+ 'spec-char-escape',
48
+ 'tagname-lowercase',
49
+ 'attr-lowercase',
50
+ 'id-unique',
51
+ 'src-not-empty',
52
+ 'alt-require',
53
+ ];
54
+
55
+ /**
56
+ * Categorize issues into HTML, Label, and Liquid categories
57
+ */
58
+ const categorizeIssues = (allIssues) => {
59
+ const htmlIssues = [];
60
+ const labelIssues = [];
61
+ const liquidIssues = [];
62
+
63
+ allIssues.forEach((issue) => {
64
+ const { source, rule, message } = issue;
65
+ const messageLower = (message || '').toLowerCase();
66
+ const ruleLower = (rule || '').toLowerCase();
67
+
68
+ // Check if it's a Liquid issue - ONLY by source, not by message content
69
+ // This prevents false positives where HTML errors mention liquid syntax
70
+ if (source === ISSUE_SOURCES.LIQUID) {
71
+ liquidIssues.push(issue);
72
+ return;
73
+ }
74
+
75
+ // Check if it's a Label (tag syntax) issue
76
+ const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
77
+ (pattern) => messageLower.includes(pattern.toLowerCase())
78
+ || ruleLower.includes(pattern.toLowerCase()),
79
+ );
80
+
81
+ if (isLabelIssue) {
82
+ labelIssues.push(issue);
83
+ return;
84
+ }
85
+
86
+ // Default to HTML issues
87
+ htmlIssues.push(issue);
88
+ });
89
+
90
+ return { htmlIssues, labelIssues, liquidIssues };
91
+ };
92
+
93
+ /**
94
+ * Check if an issue is a blocking error (API error, Rule Group #1, or client-side Liquid validation errors)
95
+ */
96
+ const isBlockingError = (issue) => {
97
+ const { rule, source, severity } = issue || {};
98
+ // API errors are blocking
99
+ if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') {
100
+ return true;
101
+ }
102
+ // Client-side Liquid validation errors are blocking (genuine syntax errors)
103
+ if (source === 'liquid-validator' && severity === 'error') {
104
+ return true;
105
+ }
106
+ // Rule Group #1 errors are blocking
107
+ if (BLOCKING_ERROR_RULE_IDS.includes(rule)) {
108
+ return true;
109
+ }
110
+ return false;
111
+ };
112
+
113
+ /**
114
+ * Get icon based on whether issue is blocking error or warning
115
+ * Blocking errors use error-icon, warnings use alert-warning
116
+ */
117
+ const getSeverityIcon = (issue) => {
118
+ if (isBlockingError(issue)) {
119
+ return <CapIcon type="error-icon" className="validation-tabs__icon validation-tabs__icon--error" />;
120
+ }
121
+ // All other issues (warnings, non-blocking) use warning icon
122
+ return <CapIcon type="alert-warning" className="validation-tabs__icon validation-tabs__icon--warning" />;
123
+ };
124
+
125
+ /**
126
+ * ValidationTabContent - Renders the content for each tab
127
+ */
128
+ const ValidationTabContent = ({
129
+ issues,
130
+ onErrorClick,
131
+ }) => {
132
+ if (!issues || issues.length === 0) {
133
+ return null;
134
+ }
135
+
136
+ const handleNavigateClick = (issue, e) => {
137
+ e.stopPropagation();
138
+ if (onErrorClick) {
139
+ // Always call onErrorClick to acknowledge the error (enables buttons)
140
+ // If line number exists, navigate to it; otherwise just acknowledge and focus editor
141
+ onErrorClick({
142
+ line: issue.line || 1, // Default to line 1 if no line number (for API errors)
143
+ column: issue.column || 1,
144
+ message: issue.message,
145
+ severity: issue.severity,
146
+ });
147
+ }
148
+ };
149
+
150
+ return (
151
+ <div className="validation-tabs__content">
152
+ {issues.map((issue, index) => {
153
+ const {
154
+ message, line, column,
155
+ } = issue;
156
+ const key = `${message}-${line}-${column}-${index}`;
157
+ const isBlocking = isBlockingError(issue);
158
+ const displaySeverity = isBlocking ? 'error' : 'warning';
159
+
160
+ return (
161
+ <div
162
+ key={key}
163
+ className={`validation-tabs__item validation-tabs__item--${displaySeverity}`}
164
+ >
165
+ <div className="validation-tabs__item-icon">
166
+ {getSeverityIcon(issue)}
167
+ </div>
168
+ <div className="validation-tabs__item-content">
169
+ <span className="validation-tabs__item-message">
170
+ {message}
171
+ </span>
172
+ {line && (
173
+ <span className="validation-tabs__item-location">
174
+ <FormattedMessage
175
+ {...messages.lineChar}
176
+ values={{ line, char: column || 1 }}
177
+ />
178
+ </span>
179
+ )}
180
+ </div>
181
+ {/* Always show redirection icon for errors (even API errors without line numbers) */}
182
+ {/* Clicking it acknowledges the error and enables buttons */}
183
+ <CapTooltip title={line ? `Line ${line}, Char ${column || 1}` : 'Click to acknowledge error and focus editor'}>
184
+ <button
185
+ type="button"
186
+ className="validation-tabs__item-navigate"
187
+ onClick={(e) => handleNavigateClick(issue, e)}
188
+ aria-label={line ? `Line ${line}, Char ${column || 1}` : 'Acknowledge error'}
189
+ >
190
+ <CapIcon type="redirection" />
191
+ </button>
192
+ </CapTooltip>
193
+ </div>
194
+ );
195
+ })}
196
+ </div>
197
+ );
198
+ };
199
+
200
+ ValidationTabContent.propTypes = {
201
+ issues: PropTypes.array,
202
+ onErrorClick: PropTypes.func,
203
+ };
204
+
205
+ ValidationTabContent.defaultProps = {
206
+ issues: [],
207
+ onErrorClick: null,
208
+ };
209
+
210
+ /**
211
+ * ValidationTabs Component
212
+ */
213
+ const ValidationTabs = ({
214
+ intl,
215
+ validation,
216
+ onErrorClick,
217
+ onClose,
218
+ className,
219
+ }) => {
220
+ const [activeKey, setActiveKey] = useState(null);
221
+
222
+ // Categorize issues
223
+ const { htmlIssues, labelIssues, liquidIssues } = useMemo(() => {
224
+ if (!validation) {
225
+ return { htmlIssues: [], labelIssues: [], liquidIssues: [] };
226
+ }
227
+
228
+ // Get all issues from validation
229
+ const allIssues = validation.getAllIssues ? validation.getAllIssues() : [];
230
+ const categorized = categorizeIssues(allIssues);
231
+ return categorized;
232
+ }, [validation]);
233
+
234
+ // Calculate counts
235
+ const htmlCount = htmlIssues.length;
236
+ const labelCount = labelIssues.length;
237
+ const liquidCount = liquidIssues.length;
238
+ // Include liquid issues in total count even when liquid is disabled
239
+ // This ensures liquid errors are shown when liquid content is detected but feature is disabled
240
+ const totalCount = htmlCount + labelCount + liquidCount;
241
+
242
+ // Set default active key when issues change
243
+ useMemo(() => {
244
+ if (htmlCount > 0 && !activeKey) {
245
+ setActiveKey('html');
246
+ } else if (labelCount > 0 && !activeKey) {
247
+ setActiveKey('label');
248
+ } else if (liquidCount > 0 && !activeKey) {
249
+ // Show liquid tab even when liquid is disabled if liquid content is detected
250
+ setActiveKey('liquid');
251
+ }
252
+ }, [htmlCount, labelCount, liquidCount, activeKey]);
253
+
254
+ // Don't render if no issues
255
+ if (totalCount === 0) {
256
+ return null;
257
+ }
258
+
259
+ // Build tab panes (CapTab uses 'panes' with 'tab' and 'content' properties)
260
+ const tabPanes = [];
261
+
262
+ if (htmlCount > 0) {
263
+ tabPanes.push({
264
+ key: 'html',
265
+ tab: (
266
+ <CapTooltip title={`${intl.formatMessage(messages.htmlIssues)} (${htmlCount})`}>
267
+ <span className="validation-tabs__tab-label">
268
+ <FormattedMessage {...messages.htmlIssues} />
269
+ <span className="validation-tabs__tab-count">
270
+ (
271
+ {htmlCount}
272
+ )
273
+ </span>
274
+ </span>
275
+ </CapTooltip>
276
+ ),
277
+ content: (
278
+ <ValidationTabContent
279
+ issues={htmlIssues}
280
+ onErrorClick={onErrorClick}
281
+ intl={intl}
282
+ />
283
+ ),
284
+ });
285
+ }
286
+
287
+ if (labelCount > 0) {
288
+ tabPanes.push({
289
+ key: 'label',
290
+ tab: (
291
+ <CapTooltip title={`${intl.formatMessage(messages.labelIssues)} (${labelCount})`}>
292
+ <span className="validation-tabs__tab-label">
293
+ <FormattedMessage {...messages.labelIssues} />
294
+ <span className="validation-tabs__tab-count">
295
+ (
296
+ {labelCount}
297
+ )
298
+ </span>
299
+ </span>
300
+ </CapTooltip>
301
+ ),
302
+ content: (
303
+ <ValidationTabContent
304
+ issues={labelIssues}
305
+ onErrorClick={onErrorClick}
306
+ intl={intl}
307
+ />
308
+ ),
309
+ });
310
+ }
311
+
312
+ // Show liquid issues tab even when liquid is disabled if liquid content is detected
313
+ // This allows users to see errors when they add liquid content but liquid feature is not enabled
314
+ if (liquidCount > 0) {
315
+ tabPanes.push({
316
+ key: 'liquid',
317
+ tab: (
318
+ <CapTooltip title={`${intl.formatMessage(messages.liquidIssues)} (${liquidCount})`}>
319
+ <span className="validation-tabs__tab-label">
320
+ <FormattedMessage {...messages.liquidIssues} />
321
+ <span className="validation-tabs__tab-count">
322
+ (
323
+ {liquidCount}
324
+ )
325
+ </span>
326
+ </span>
327
+ </CapTooltip>
328
+ ),
329
+ content: (
330
+ <ValidationTabContent
331
+ issues={liquidIssues}
332
+ onErrorClick={onErrorClick}
333
+ intl={intl}
334
+ />
335
+ ),
336
+ });
337
+ }
338
+
339
+ // Handle close
340
+ const handleClose = () => {
341
+ if (onClose) {
342
+ onClose();
343
+ }
344
+ };
345
+
346
+ return (
347
+ <div className={`validation-tabs ${className || ''}`}>
348
+ <CapRow className="validation-tabs__header">
349
+ <CapTab
350
+ activeKey={activeKey || (tabPanes[0]?.key)}
351
+ onChange={setActiveKey}
352
+ panes={tabPanes}
353
+ className="validation-tabs__tabs"
354
+ />
355
+ <CapRow className="validation-tabs__actions">
356
+ <CapTooltip title={intl.formatMessage(messages.closePanel)}>
357
+ <button
358
+ type="button"
359
+ className="validation-tabs__close"
360
+ onClick={handleClose}
361
+ aria-label={intl.formatMessage(messages.closePanel)}
362
+ >
363
+ <CapIcon type="close" />
364
+ </button>
365
+ </CapTooltip>
366
+ </CapRow>
367
+ </CapRow>
368
+ </div>
369
+ );
370
+ };
371
+
372
+ ValidationTabs.propTypes = {
373
+ intl: intlShape.isRequired,
374
+ validation: PropTypes.shape({
375
+ getAllIssues: PropTypes.func,
376
+ }),
377
+ onErrorClick: PropTypes.func,
378
+ onClose: PropTypes.func,
379
+ isLiquidEnabled: PropTypes.bool,
380
+ className: PropTypes.string,
381
+ };
382
+
383
+ ValidationTabs.defaultProps = {
384
+ validation: null,
385
+ onErrorClick: null,
386
+ onClose: null,
387
+ isLiquidEnabled: false,
388
+ className: '',
389
+ };
390
+
391
+ export default injectIntl(ValidationTabs);
@@ -0,0 +1,51 @@
1
+ /**
2
+ * ValidationTabs Messages
3
+ *
4
+ * Internationalization messages for the ValidationTabs component
5
+ */
6
+
7
+ import { defineMessages } from 'react-intl';
8
+
9
+ const scope = 'app.components.HtmlEditor.ValidationTabs';
10
+
11
+ export default defineMessages({
12
+ // Tab labels
13
+ htmlIssues: {
14
+ id: `${scope}.htmlIssues`,
15
+ defaultMessage: 'HTML issues',
16
+ },
17
+ labelIssues: {
18
+ id: `${scope}.labelIssues`,
19
+ defaultMessage: 'Label issues',
20
+ },
21
+ liquidIssues: {
22
+ id: `${scope}.liquidIssues`,
23
+ defaultMessage: 'Liquid issues',
24
+ },
25
+
26
+ // Error item labels
27
+ syntaxError: {
28
+ id: `${scope}.syntaxError`,
29
+ defaultMessage: 'Syntax Error',
30
+ },
31
+ lineChar: {
32
+ id: `${scope}.lineChar`,
33
+ defaultMessage: 'Line {line}, Char {char}.',
34
+ },
35
+
36
+ // Tooltips
37
+ navigateToError: {
38
+ id: `${scope}.navigateToError`,
39
+ defaultMessage: 'Go to error location',
40
+ },
41
+ closePanel: {
42
+ id: `${scope}.closePanel`,
43
+ defaultMessage: 'Close validation panel',
44
+ },
45
+
46
+ // Liquid documentation
47
+ liquidDocumentation: {
48
+ id: `${scope}.liquidDocumentation`,
49
+ defaultMessage: 'Liquid documentation',
50
+ },
51
+ });
@@ -7,26 +7,26 @@
7
7
  // HTML Editor Variants
8
8
  export const HTML_EDITOR_VARIANTS = {
9
9
  EMAIL: 'email',
10
- INAPP: 'inapp'
10
+ INAPP: 'inapp',
11
11
  };
12
12
 
13
13
  // Device Types (for InApp variant)
14
14
  export const DEVICE_TYPES = {
15
15
  ANDROID: 'android',
16
- IOS: 'ios'
16
+ IOS: 'ios',
17
17
  };
18
18
 
19
19
  // Editor languages
20
20
  export const EDITOR_LANGUAGES = {
21
21
  HTML: 'html',
22
22
  CSS: 'css',
23
- JAVASCRIPT: 'javascript'
23
+ JAVASCRIPT: 'javascript',
24
24
  };
25
25
 
26
26
  // Preview modes
27
27
  export const PREVIEW_MODES = {
28
28
  DESKTOP: 'desktop',
29
- MOBILE: 'mobile'
29
+ MOBILE: 'mobile',
30
30
  };
31
31
 
32
32
  // Device specifications for mobile preview
@@ -38,7 +38,7 @@ export const DEVICE_PRESETS = {
38
38
  devicePixelRatio: 2.75,
39
39
  userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36',
40
40
  platform: 'android',
41
- model: 'Pixel 7'
41
+ model: 'Pixel 7',
42
42
  },
43
43
  IPHONE: {
44
44
  key: 'iphone',
@@ -47,30 +47,43 @@ export const DEVICE_PRESETS = {
47
47
  devicePixelRatio: 3,
48
48
  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)',
49
49
  platform: 'ios',
50
- model: 'iPhone 15'
51
- }
50
+ model: 'iPhone 15',
51
+ },
52
52
  };
53
53
 
54
54
  // Validation severity levels
55
55
  export const VALIDATION_SEVERITY = {
56
56
  ERROR: 'error',
57
57
  WARNING: 'warning',
58
- INFO: 'info'
58
+ INFO: 'info',
59
59
  };
60
60
 
61
+ /**
62
+ * Rule Group #1: Input & Sanitization Errors.
63
+ * ONLY these rules are treated as BLOCKING ERROR for Save/Update/Preview/Test.
64
+ * All other rule groups (HTML/CSS/Liquid, compatibility, security, etc.) are WARNING only.
65
+ * Backward compatibility: CKEditor legacy templates (unclosed CSS, indentation, etc.) must never block.
66
+ */
67
+ export const BLOCKING_ERROR_RULE_IDS = [
68
+ 'sanitizer.invalidInput',
69
+ 'sanitizer.invalidInputNonEmpty',
70
+ 'sanitizer.sanitizationFailed',
71
+ 'sanitizer.dangerousProtocolDetected',
72
+ ];
73
+
61
74
  // Validation types
62
75
  export const VALIDATION_TYPES = {
63
76
  HTML: 'html',
64
77
  CSS: 'css',
65
78
  JAVASCRIPT: 'javascript',
66
- SECURITY: 'security'
79
+ SECURITY: 'security',
67
80
  };
68
81
 
69
82
  // Error types for preview
70
83
  export const PREVIEW_ERROR_TYPES = {
71
84
  RUNTIME: 'runtime',
72
85
  RENDER: 'render',
73
- SECURITY: 'security'
86
+ SECURITY: 'security',
74
87
  };
75
88
 
76
89
  // Performance thresholds
@@ -79,7 +92,7 @@ export const PERFORMANCE = {
79
92
  VALIDATION_DEBOUNCE: 500, // ms
80
93
  AUTO_SAVE_INTERVAL: 30000, // ms
81
94
  MAX_CONTENT_LENGTH: 100000, // characters
82
- PERFORMANCE_WARNING_THRESHOLD: 50000 // characters
95
+ PERFORMANCE_WARNING_THRESHOLD: 50000, // characters
83
96
  };
84
97
 
85
98
  // Layout constraints
@@ -89,7 +102,7 @@ export const LAYOUT = {
89
102
  DEFAULT_SPLIT_SIZES: [50, 50], // [left, right] percentages
90
103
  GUTTER_SIZE: 8, // pixels
91
104
  MOBILE_BREAKPOINT: 768, // pixels
92
- MOBILE_WIDTH_DEFAULT: 375 // pixels
105
+ MOBILE_WIDTH_DEFAULT: 375, // pixels
93
106
  };
94
107
 
95
108
 
@@ -105,7 +118,7 @@ export const CODEMIRROR_CONFIG = {
105
118
  FOLD_GUTTER: true,
106
119
  SEARCH_ENABLED: true,
107
120
  AUTOCOMPLETE_ENABLED: true,
108
- LINT_ENABLED: true
121
+ LINT_ENABLED: true,
109
122
  };
110
123
 
111
124
  // HTML validation rules (HTMLHint)
@@ -126,7 +139,7 @@ export const HTML_VALIDATION_RULES = {
126
139
  'inline-style-disabled': false,
127
140
  'inline-script-disabled': false,
128
141
  'space-tab-mixed-disabled': 'space',
129
- 'spec-char-escape': true
142
+ 'spec-char-escape': true,
130
143
  };
131
144
 
132
145
  // CSS validation rules
@@ -137,7 +150,7 @@ export const CSS_VALIDATION_RULES = {
137
150
  'declaration-colon-space-after': 'always',
138
151
  'declaration-colon-space-before': 'never',
139
152
  'block-closing-brace-newline-after': 'always',
140
- 'block-opening-brace-space-before': 'always'
153
+ 'block-opening-brace-space-before': 'always',
141
154
  };
142
155
 
143
156
  // JavaScript validation rules (ESLint-style)
@@ -148,7 +161,7 @@ export const JS_VALIDATION_RULES = {
148
161
  'no-console': 'warn',
149
162
  'semi': ['error', 'always'],
150
163
  'quotes': ['error', 'single'],
151
- 'indent': ['error', 2]
164
+ 'indent': ['error', 2],
152
165
  };
153
166
 
154
167
  // Keyboard shortcuts
@@ -163,7 +176,7 @@ export const KEYBOARD_SHORTCUTS = {
163
176
  FIND: 'Ctrl+F',
164
177
  FIND_MAC: 'Cmd+F',
165
178
  REPLACE: 'Ctrl+H',
166
- REPLACE_MAC: 'Cmd+Option+F'
179
+ REPLACE_MAC: 'Cmd+Option+F',
167
180
  };
168
181
 
169
182
  // Feature flags
@@ -176,7 +189,7 @@ export const FEATURES = {
176
189
  KEYBOARD_SHORTCUTS: true,
177
190
  ERROR_RECOVERY: true,
178
191
  PERFORMANCE_MONITORING: true,
179
- ACCESSIBILITY_FEATURES: true
192
+ ACCESSIBILITY_FEATURES: true,
180
193
  };
181
194
 
182
195
  // Animation durations (ms)
@@ -186,7 +199,7 @@ export const ANIMATIONS = {
186
199
  SLOW: 500,
187
200
  PANEL_RESIZE: 200,
188
201
  FADE_IN: 300,
189
- SLIDE_IN: 250
202
+ SLIDE_IN: 250,
190
203
  };
191
204
 
192
205
  // Z-index layers
@@ -195,7 +208,7 @@ export const Z_INDEX = {
195
208
  OVERLAY: 1000,
196
209
  MODAL: 1050,
197
210
  FULLSCREEN: 9999,
198
- TOOLTIP: 10000
211
+ TOOLTIP: 10000,
199
212
  };
200
213
 
201
214
  // Default HTML content template
@@ -239,3 +252,12 @@ export const DEFAULT_HTML_CONTENT = `<!DOCTYPE html>
239
252
  </script>
240
253
  </body>
241
254
  </html>`;
255
+
256
+ // Constants for tag API calls
257
+ export const TAG = 'TAG';
258
+ export const EMBEDDED = 'embedded';
259
+ export const DEFAULT = 'default';
260
+ export const FULL = 'full';
261
+ export const ALL = 'all';
262
+ export const SMS = 'SMS';
263
+ export const EMAIL = 'EMAIL';