@capillarytech/creatives-library 8.0.271 → 8.0.272
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/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/constants/unified.js +2 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +10 -0
- package/services/tests/api.test.js +34 -0
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
- package/tests/integration/TemplateCreation/api-response.js +31 -1
- package/tests/integration/TemplateCreation/msw-handler.js +2 -0
- package/utils/common.js +5 -0
- package/utils/commonUtils.js +28 -5
- package/utils/tests/commonUtil.test.js +224 -0
- package/utils/transformTemplateConfig.js +0 -10
- package/v2Components/CapDeviceContent/index.js +61 -56
- package/v2Components/CapTagList/index.js +6 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
- package/v2Components/ErrorInfoNote/constants.js +1 -0
- package/v2Components/ErrorInfoNote/index.js +402 -72
- package/v2Components/ErrorInfoNote/messages.js +32 -6
- package/v2Components/ErrorInfoNote/style.scss +278 -6
- package/v2Components/FormBuilder/tests/index.test.js +13 -4
- package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
- package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
- package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
- package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
- package/v2Components/HtmlEditor/constants.js +45 -20
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
- package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +102 -94
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
- package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
- package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
- package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
- package/v2Components/MobilePushPreviewV2/constants.js +6 -0
- package/v2Components/MobilePushPreviewV2/index.js +33 -7
- package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
- package/v2Components/TemplatePreview/index.js +47 -32
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
- package/v2Containers/BeeEditor/index.js +172 -90
- package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
- package/v2Containers/BeePopupEditor/constants.js +10 -0
- package/v2Containers/BeePopupEditor/index.js +194 -0
- package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +251 -47
- package/v2Containers/CreativesContainer/messages.js +8 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +234 -29
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +61 -7
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
- package/v2Containers/Email/tests/reducer.test.js +46 -0
- package/v2Containers/Email/tests/sagas.test.js +320 -29
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2472 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
- package/v2Containers/EmailWrapper/index.js +103 -23
- package/v2Containers/EmailWrapper/messages.js +65 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
- package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
- package/v2Containers/InApp/actions.js +7 -0
- package/v2Containers/InApp/constants.js +20 -4
- package/v2Containers/InApp/index.js +802 -360
- package/v2Containers/InApp/index.scss +4 -3
- package/v2Containers/InApp/messages.js +7 -3
- package/v2Containers/InApp/reducer.js +21 -3
- package/v2Containers/InApp/sagas.js +29 -9
- package/v2Containers/InApp/selectors.js +25 -5
- package/v2Containers/InApp/tests/index.test.js +154 -50
- package/v2Containers/InApp/tests/reducer.test.js +34 -0
- package/v2Containers/InApp/tests/sagas.test.js +61 -9
- package/v2Containers/InApp/tests/selectors.test.js +612 -0
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
- package/v2Containers/InAppWrapper/constants.js +16 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
- package/v2Containers/InAppWrapper/index.js +148 -0
- package/v2Containers/InAppWrapper/messages.js +49 -0
- package/v2Containers/InappAdvance/index.js +1099 -0
- package/v2Containers/InappAdvance/index.scss +10 -0
- package/v2Containers/InappAdvance/tests/index.test.js +448 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/TagList/index.js +62 -19
- package/v2Containers/Templates/_templates.scss +60 -1
- package/v2Containers/Templates/index.js +89 -4
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* - Filters dangerous protocols (javascript:, data:, vbscript:)
|
|
16
16
|
* - Variant-aware sanitization (EMAIL vs INAPP with different security levels)
|
|
17
17
|
* - Iframe sandbox uses "allow-scripts" only (no allow-same-origin for security)
|
|
18
|
-
* - Content delivered via
|
|
18
|
+
* - Content delivered via srcdoc attribute for CSP compliance (replaces blob URLs)
|
|
19
19
|
*
|
|
20
20
|
* Previous Security Issues (FIXED):
|
|
21
21
|
* - Removed insecure manual sanitizeJavaScript function that only escaped </script> tags
|
|
22
22
|
* - Replaced with robust DOMPurify-based sanitization for full XSS protection
|
|
23
23
|
* - Removed "allow-same-origin" from iframe sandbox to prevent same-origin access
|
|
24
|
-
* -
|
|
24
|
+
* - Replaced blob URLs with srcdoc to comply with CSP frame-src directives
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import React, { useMemo, useEffect, useRef } from 'react';
|
|
@@ -55,7 +55,7 @@ const PreviewPane = ({
|
|
|
55
55
|
className = '',
|
|
56
56
|
isFullscreenMode = false,
|
|
57
57
|
isModalContext = false,
|
|
58
|
-
layoutType = LAYOUT_TYPES.MODAL
|
|
58
|
+
layoutType = LAYOUT_TYPES.MODAL,
|
|
59
59
|
}) => {
|
|
60
60
|
const { content, layout, variant } = useEditorContext();
|
|
61
61
|
|
|
@@ -103,36 +103,25 @@ const PreviewPane = ({
|
|
|
103
103
|
return sanitizationResult.sanitized;
|
|
104
104
|
}, [contentValue, variant]);
|
|
105
105
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
URL.revokeObjectURL(blobUrlRef.current);
|
|
113
|
-
blobUrlRef.current = null;
|
|
106
|
+
// Use srcdoc instead of blob URLs for CSP compliance
|
|
107
|
+
// srcdoc allows embedding HTML directly in iframe without violating frame-src CSP directives
|
|
108
|
+
// This is more secure and CSP-compliant than blob URLs
|
|
109
|
+
const iframeContent = useMemo(() => {
|
|
110
|
+
if (!combinedContent) {
|
|
111
|
+
return null;
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
// srcdoc has a size limit (~2MB in some browsers), but for HTML preview content this should be sufficient
|
|
115
|
+
// If content is too large, we'll fall back to about:blank and log a warning
|
|
116
|
+
const MAX_SRCDOC_SIZE = 2000000; // 2MB limit
|
|
117
|
+
if (combinedContent.length > MAX_SRCDOC_SIZE) {
|
|
118
|
+
console.warn('PreviewPane: Content too large for srcdoc, using about:blank');
|
|
119
|
+
return null;
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
return
|
|
122
|
+
return combinedContent;
|
|
124
123
|
}, [combinedContent]);
|
|
125
124
|
|
|
126
|
-
// Cleanup blob URL on unmount
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
return () => {
|
|
129
|
-
if (blobUrlRef.current) {
|
|
130
|
-
URL.revokeObjectURL(blobUrlRef.current);
|
|
131
|
-
blobUrlRef.current = null;
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
}, []);
|
|
135
|
-
|
|
136
125
|
// Generate CSS classes based on view mode
|
|
137
126
|
const getIframeClasses = () => {
|
|
138
127
|
const baseClass = 'preview-pane__iframe';
|
|
@@ -154,15 +143,15 @@ const PreviewPane = ({
|
|
|
154
143
|
// Use layout.mobileWidth or fallback to 375px
|
|
155
144
|
const width = mobileWidth || 375;
|
|
156
145
|
baseStyles.width = `${width}px`;
|
|
157
|
-
baseStyles.height = '100%'; // Fill container height
|
|
158
146
|
}
|
|
159
|
-
|
|
147
|
+
baseStyles.height = '100%'; // Fill container height
|
|
160
148
|
return baseStyles;
|
|
161
149
|
};
|
|
162
150
|
|
|
163
151
|
const renderDeviceFrame = () => (
|
|
164
152
|
<iframe
|
|
165
|
-
src={
|
|
153
|
+
src={iframeContent ? undefined : 'about:blank'}
|
|
154
|
+
srcDoc={iframeContent || undefined}
|
|
166
155
|
title={intl.formatMessage(messages.htmlPreview)}
|
|
167
156
|
className={getIframeClasses()}
|
|
168
157
|
style={getPreviewStyles()}
|
|
@@ -173,17 +162,7 @@ const PreviewPane = ({
|
|
|
173
162
|
const renderEmptyState = () => (
|
|
174
163
|
<div className="preview-empty">
|
|
175
164
|
<div className="empty-content">
|
|
176
|
-
<
|
|
177
|
-
<p>{intl.formatMessage(messages.startTypingHtml)}</p>
|
|
178
|
-
<p className="preview-mode-info">
|
|
179
|
-
{intl.formatMessage(messages.previewMode)}: {
|
|
180
|
-
viewMode === PREVIEW_MODES.DESKTOP
|
|
181
|
-
? intl.formatMessage(messages.desktop)
|
|
182
|
-
: viewMode === PREVIEW_MODES.MOBILE
|
|
183
|
-
? intl.formatMessage(messages.mobile)
|
|
184
|
-
: intl.formatMessage(messages.mobileDevice)
|
|
185
|
-
}
|
|
186
|
-
</p>
|
|
165
|
+
<p className="preview-mode-info"></p>
|
|
187
166
|
</div>
|
|
188
167
|
</div>
|
|
189
168
|
);
|
|
@@ -212,7 +191,7 @@ const PreviewPane = ({
|
|
|
212
191
|
</CapRow>
|
|
213
192
|
|
|
214
193
|
<CapRow className="preview-pane__content">
|
|
215
|
-
{
|
|
194
|
+
{iframeContent ? renderDeviceFrame() : renderEmptyState()}
|
|
216
195
|
</CapRow>
|
|
217
196
|
</div>
|
|
218
197
|
);
|
|
@@ -223,7 +202,7 @@ PreviewPane.propTypes = {
|
|
|
223
202
|
className: PropTypes.string,
|
|
224
203
|
isFullscreenMode: PropTypes.bool,
|
|
225
204
|
isModalContext: PropTypes.bool,
|
|
226
|
-
layoutType: PropTypes.oneOf(Object.values(LAYOUT_TYPES))
|
|
205
|
+
layoutType: PropTypes.oneOf(Object.values(LAYOUT_TYPES)),
|
|
227
206
|
};
|
|
228
207
|
|
|
229
208
|
export default injectIntl(PreviewPane);
|
package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ValidationErrorDisplay Styles
|
|
3
|
+
*
|
|
4
|
+
* When collapsed, panel sticks to footer (header bar only visible).
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
@@ -9,10 +11,18 @@
|
|
|
9
11
|
flex-direction: column;
|
|
10
12
|
width: 100%;
|
|
11
13
|
margin-bottom: 1rem;
|
|
14
|
+
flex-shrink: 0;
|
|
12
15
|
|
|
13
16
|
// Ensure proper spacing when used in different contexts
|
|
14
17
|
&:last-child {
|
|
15
18
|
margin-bottom: 0;
|
|
19
|
+
padding-bottom: 0.25rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Collapsed: panel stays in footer as a slim bar (tabs + expand arrow only)
|
|
23
|
+
&--collapsed {
|
|
24
|
+
margin-bottom: 0;
|
|
25
|
+
padding-bottom: 0.25rem;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
// Integration with ErrorInfoNote component
|
|
@@ -23,9 +33,17 @@
|
|
|
23
33
|
// Responsive adjustments
|
|
24
34
|
@media (max-width: 768px) {
|
|
25
35
|
margin-bottom: 0.75rem;
|
|
36
|
+
|
|
37
|
+
&.validation-error-display--collapsed {
|
|
38
|
+
margin-bottom: 0;
|
|
39
|
+
}
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
@media (max-width: 576px) {
|
|
29
43
|
margin-bottom: 0.5rem;
|
|
44
|
+
|
|
45
|
+
&.validation-error-display--collapsed {
|
|
46
|
+
margin-bottom: 0;
|
|
47
|
+
}
|
|
30
48
|
}
|
|
31
49
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ValidationErrorDisplay - HTML Editor validation
|
|
2
|
+
* ValidationErrorDisplay - HTML Editor validation display
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Displays validation errors and warnings in two tabs (Errors, Warnings).
|
|
5
|
+
* Panel can be collapsed to a sticky footer bar; it does not disappear.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React from 'react';
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
9
|
import PropTypes from 'prop-types';
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
import { transformValidationToErrorInfo, hasValidationErrors } from '../../utils/validationAdapter';
|
|
15
|
-
import { HTML_EDITOR_VARIANTS } from '../../constants';
|
|
11
|
+
import { hasValidationErrors } from '../../utils/validationAdapter';
|
|
12
|
+
import ValidationTabs from '../ValidationTabs';
|
|
16
13
|
|
|
17
14
|
// Styles
|
|
18
15
|
import './_validationErrorDisplay.scss';
|
|
@@ -20,51 +17,59 @@ import './_validationErrorDisplay.scss';
|
|
|
20
17
|
/**
|
|
21
18
|
* ValidationErrorDisplay Component
|
|
22
19
|
*
|
|
23
|
-
* Displays validation
|
|
20
|
+
* Displays validation issues in Errors and Warnings tabs; collapse toggles visibility but panel stays in footer.
|
|
24
21
|
*/
|
|
25
22
|
const ValidationErrorDisplay = ({
|
|
26
23
|
validation,
|
|
27
|
-
variant = HTML_EDITOR_VARIANTS.EMAIL,
|
|
28
24
|
onErrorClick,
|
|
29
|
-
className = ''
|
|
25
|
+
className = '',
|
|
30
26
|
}) => {
|
|
31
|
-
|
|
32
|
-
if (!hasValidationErrors(validation)) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
27
|
+
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
const handleToggleCollapse = () => {
|
|
30
|
+
setIsCollapsed((prev) => !prev);
|
|
31
|
+
};
|
|
39
32
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
33
|
+
// Expand panel when user clicks redirection (so panel does not stay stuck to footer)
|
|
34
|
+
const handleExpand = () => {
|
|
35
|
+
setIsCollapsed(false);
|
|
43
36
|
};
|
|
44
37
|
|
|
38
|
+
if (!hasValidationErrors(validation)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
45
42
|
return (
|
|
46
|
-
<
|
|
47
|
-
className={`validation-error-display ${className}`}
|
|
43
|
+
<div
|
|
44
|
+
className={`validation-error-display ${isCollapsed ? 'validation-error-display--collapsed' : ''} ${className}`}
|
|
48
45
|
role="alert"
|
|
49
46
|
aria-live="polite"
|
|
50
47
|
aria-label="Validation errors"
|
|
51
48
|
>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
onErrorClick={
|
|
49
|
+
<ValidationTabs
|
|
50
|
+
validation={validation}
|
|
51
|
+
onErrorClick={onErrorClick}
|
|
52
|
+
isCollapsed={isCollapsed}
|
|
53
|
+
onToggleCollapse={handleToggleCollapse}
|
|
54
|
+
onExpand={handleExpand}
|
|
55
55
|
/>
|
|
56
|
-
</
|
|
56
|
+
</div>
|
|
57
57
|
);
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
ValidationErrorDisplay.propTypes = {
|
|
61
61
|
validation: PropTypes.shape({
|
|
62
62
|
isValidating: PropTypes.bool,
|
|
63
|
-
getAllIssues: PropTypes.func
|
|
63
|
+
getAllIssues: PropTypes.func,
|
|
64
64
|
}),
|
|
65
|
-
variant: PropTypes.oneOf(Object.values(HTML_EDITOR_VARIANTS)),
|
|
66
65
|
onErrorClick: PropTypes.func,
|
|
67
|
-
className: PropTypes.string
|
|
66
|
+
className: PropTypes.string,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
ValidationErrorDisplay.defaultProps = {
|
|
70
|
+
validation: null,
|
|
71
|
+
onErrorClick: null,
|
|
72
|
+
className: '',
|
|
68
73
|
};
|
|
69
74
|
|
|
70
75
|
export default ValidationErrorDisplay;
|
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
6
6
|
|
|
7
7
|
.validation-panel {
|
|
8
|
-
border: 1px solid
|
|
9
|
-
border-radius:
|
|
10
|
-
background:
|
|
8
|
+
border: 1px solid $CAP_COLOR_16;
|
|
9
|
+
border-radius: $CAP_SPACE_06;
|
|
10
|
+
background: $CAP_WHITE;
|
|
11
11
|
|
|
12
12
|
&--loading {
|
|
13
|
-
padding:
|
|
13
|
+
padding: $CAP_SPACE_16;
|
|
14
14
|
text-align: center;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
&--clean {
|
|
18
|
-
padding:
|
|
18
|
+
padding: $CAP_SPACE_16;
|
|
19
19
|
text-align: center;
|
|
20
20
|
background: #f6ffed;
|
|
21
21
|
border-color: #b7eb8f;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
display: flex;
|
|
26
26
|
align-items: center;
|
|
27
27
|
justify-content: center;
|
|
28
|
-
gap:
|
|
28
|
+
gap: $CAP_SPACE_08;
|
|
29
29
|
color: #666;
|
|
30
30
|
font-size: 14px;
|
|
31
31
|
}
|
|
@@ -34,36 +34,36 @@
|
|
|
34
34
|
display: flex;
|
|
35
35
|
align-items: center;
|
|
36
36
|
justify-content: center;
|
|
37
|
-
gap:
|
|
37
|
+
gap: $CAP_SPACE_08;
|
|
38
38
|
color: #52c41a;
|
|
39
39
|
font-size: 14px;
|
|
40
|
-
font-weight:
|
|
40
|
+
font-weight: $FONT_WEIGHT_MEDIUM;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
&__summary {
|
|
44
44
|
display: flex;
|
|
45
|
-
gap:
|
|
46
|
-
padding:
|
|
47
|
-
background:
|
|
48
|
-
border-bottom: 1px solid
|
|
49
|
-
border-radius:
|
|
45
|
+
gap: $CAP_SPACE_16;
|
|
46
|
+
padding: $CAP_SPACE_12 $CAP_SPACE_16;
|
|
47
|
+
background: $CAP_G21;
|
|
48
|
+
border-bottom: 1px solid $CAP_COLOR_16;
|
|
49
|
+
border-radius: $CAP_SPACE_06 $CAP_SPACE_06 0 0;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
&__summary-item {
|
|
53
53
|
display: flex;
|
|
54
54
|
align-items: center;
|
|
55
|
-
gap:
|
|
56
|
-
font-size:
|
|
55
|
+
gap: $CAP_SPACE_04;
|
|
56
|
+
font-size: $CAP_SPACE_12;
|
|
57
57
|
color: #666;
|
|
58
58
|
|
|
59
59
|
&--security {
|
|
60
60
|
color: #ff4d4f;
|
|
61
|
-
font-weight:
|
|
61
|
+
font-weight: $FONT_WEIGHT_MEDIUM;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
span:first-of-type {
|
|
65
65
|
font-weight: 600;
|
|
66
|
-
margin-left:
|
|
66
|
+
margin-left: $CAP_SPACE_02;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
border: none;
|
|
73
73
|
|
|
74
74
|
&:last-child {
|
|
75
|
-
border-radius: 0 0
|
|
75
|
+
border-radius: 0 0 $CAP_SPACE_06 $CAP_SPACE_06;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -123,8 +123,8 @@
|
|
|
123
123
|
&__title {
|
|
124
124
|
display: flex;
|
|
125
125
|
align-items: center;
|
|
126
|
-
gap:
|
|
127
|
-
font-weight:
|
|
126
|
+
gap: $CAP_SPACE_08;
|
|
127
|
+
font-weight: $FONT_WEIGHT_MEDIUM;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
&__issues {
|
|
@@ -133,41 +133,41 @@
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
&__sanitization {
|
|
136
|
-
border-top: 1px solid
|
|
136
|
+
border-top: 1px solid $CAP_COLOR_16;
|
|
137
137
|
background: #f9f9f9;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
&__sanitization-header {
|
|
141
141
|
display: flex;
|
|
142
142
|
align-items: center;
|
|
143
|
-
gap:
|
|
144
|
-
padding:
|
|
145
|
-
font-weight:
|
|
143
|
+
gap: $CAP_SPACE_08;
|
|
144
|
+
padding: $CAP_SPACE_12 $CAP_SPACE_16;
|
|
145
|
+
font-weight: $FONT_WEIGHT_MEDIUM;
|
|
146
146
|
color: #666;
|
|
147
147
|
font-size: 13px;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
&__sanitization-list {
|
|
151
|
-
padding: 0
|
|
151
|
+
padding: 0 $CAP_SPACE_16 $CAP_SPACE_12;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
&__sanitization-item {
|
|
155
|
-
padding:
|
|
156
|
-
font-size:
|
|
155
|
+
padding: $CAP_SPACE_04 0;
|
|
156
|
+
font-size: $CAP_SPACE_12;
|
|
157
157
|
color: #8c8c8c;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
.validation-issue {
|
|
162
162
|
display: flex;
|
|
163
|
-
gap:
|
|
164
|
-
padding:
|
|
163
|
+
gap: $CAP_SPACE_12;
|
|
164
|
+
padding: $CAP_SPACE_12 $CAP_SPACE_16;
|
|
165
165
|
border-bottom: 1px solid #f0f0f0;
|
|
166
166
|
cursor: pointer;
|
|
167
167
|
transition: background-color 0.2s;
|
|
168
168
|
|
|
169
169
|
&:hover {
|
|
170
|
-
background:
|
|
170
|
+
background: $CAP_G21;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
&:last-child {
|
|
@@ -189,6 +189,18 @@
|
|
|
189
189
|
&__icon {
|
|
190
190
|
flex-shrink: 0;
|
|
191
191
|
margin-top: 2px;
|
|
192
|
+
|
|
193
|
+
&--error {
|
|
194
|
+
color: #ff4d4f;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
&--info {
|
|
198
|
+
color: #1890ff;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
&--security {
|
|
202
|
+
color: #ff4d4f;
|
|
203
|
+
}
|
|
192
204
|
}
|
|
193
205
|
|
|
194
206
|
&__content {
|
|
@@ -200,14 +212,14 @@
|
|
|
200
212
|
font-size: 13px;
|
|
201
213
|
line-height: 1.4;
|
|
202
214
|
color: #262626;
|
|
203
|
-
margin-bottom:
|
|
215
|
+
margin-bottom: $CAP_SPACE_04;
|
|
204
216
|
word-break: break-word;
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
&__meta {
|
|
208
220
|
display: flex;
|
|
209
221
|
flex-wrap: wrap;
|
|
210
|
-
gap:
|
|
222
|
+
gap: $CAP_SPACE_08;
|
|
211
223
|
font-size: 11px;
|
|
212
224
|
color: #8c8c8c;
|
|
213
225
|
}
|
|
@@ -230,9 +242,9 @@
|
|
|
230
242
|
&__source {
|
|
231
243
|
display: flex;
|
|
232
244
|
align-items: center;
|
|
233
|
-
gap:
|
|
245
|
+
gap: $CAP_SPACE_04;
|
|
234
246
|
background: #f0f0f0;
|
|
235
|
-
padding:
|
|
247
|
+
padding: $CAP_SPACE_02 $CAP_SPACE_06;
|
|
236
248
|
border-radius: 3px;
|
|
237
249
|
}
|
|
238
250
|
}
|
|
@@ -21,13 +21,15 @@ import ShieldOutlined from '@ant-design/icons/ShieldOutlined';
|
|
|
21
21
|
import BugOutlined from '@ant-design/icons/BugOutlined';
|
|
22
22
|
import CodeOutlined from '@ant-design/icons/CodeOutlined';
|
|
23
23
|
import EyeInvisibleOutlined from '@ant-design/icons/EyeInvisibleOutlined';
|
|
24
|
-
import
|
|
24
|
+
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
25
25
|
|
|
26
26
|
import messages from './messages';
|
|
27
|
+
import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
|
|
27
28
|
import './_validationPanel.scss';
|
|
29
|
+
import { SEVERITY } from './constants';
|
|
30
|
+
import { ISSUE_SOURCES } from '../../utils/validationConstants';
|
|
28
31
|
|
|
29
32
|
const { Panel } = Collapse;
|
|
30
|
-
|
|
31
33
|
/**
|
|
32
34
|
* ValidationPanel Component
|
|
33
35
|
*/
|
|
@@ -37,7 +39,6 @@ const ValidationPanel = ({
|
|
|
37
39
|
onErrorClick,
|
|
38
40
|
showLineNumbers = true,
|
|
39
41
|
groupBySource = false,
|
|
40
|
-
variant = 'email'
|
|
41
42
|
}) => {
|
|
42
43
|
const [activeKeys, setActiveKeys] = useState(['errors', 'warnings']);
|
|
43
44
|
|
|
@@ -56,33 +57,35 @@ const ValidationPanel = ({
|
|
|
56
57
|
groups[source].push(issue);
|
|
57
58
|
return groups;
|
|
58
59
|
}, {});
|
|
59
|
-
} else {
|
|
60
|
-
return {
|
|
61
|
-
errors: allIssues.filter(issue => issue.severity === 'error'),
|
|
62
|
-
warnings: allIssues.filter(issue => issue.severity === 'warning'),
|
|
63
|
-
info: allIssues.filter(issue => issue.severity === 'info')
|
|
64
|
-
};
|
|
65
60
|
}
|
|
61
|
+
return {
|
|
62
|
+
errors: allIssues.filter((issue) => issue.severity === SEVERITY.ERROR),
|
|
63
|
+
warnings: allIssues.filter((issue) => issue.severity === SEVERITY.WARNING),
|
|
64
|
+
info: allIssues.filter((issue) => issue.severity === SEVERITY.INFO),
|
|
65
|
+
};
|
|
66
66
|
}, [validation, groupBySource]);
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
// Check if an issue is a blocking error (API error, Rule Group #1, or client-side Liquid validation errors)
|
|
69
|
+
const isBlockingError = (issue) => {
|
|
70
|
+
const { rule, source, severity } = issue || {};
|
|
71
|
+
// API errors are blocking
|
|
72
|
+
if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') {
|
|
73
|
+
return true;
|
|
72
74
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return <InfoCircleOutlined style={{ color: '#1890ff' }} />;
|
|
81
|
-
default:
|
|
82
|
-
return <BugOutlined style={{ color: '#666' }} />;
|
|
75
|
+
// Client-side Liquid validation errors are blocking (genuine syntax errors)
|
|
76
|
+
if (source === ISSUE_SOURCES.LIQUID && severity === SEVERITY.ERROR) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
// Rule Group #1 errors are blocking
|
|
80
|
+
if (BLOCKING_ERROR_RULE_IDS.includes(rule)) {
|
|
81
|
+
return true;
|
|
83
82
|
}
|
|
83
|
+
return false;
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
+
// Get icon for issue type
|
|
87
|
+
// Blocking errors use error-icon, warnings use warning
|
|
88
|
+
|
|
86
89
|
// Get source icon
|
|
87
90
|
const getSourceIcon = (source) => {
|
|
88
91
|
switch (source) {
|
|
@@ -106,7 +109,7 @@ const ValidationPanel = ({
|
|
|
106
109
|
line: issue.line,
|
|
107
110
|
column: issue.column || 1,
|
|
108
111
|
message: issue.message,
|
|
109
|
-
severity: issue.severity
|
|
112
|
+
severity: issue.severity,
|
|
110
113
|
});
|
|
111
114
|
}
|
|
112
115
|
};
|
|
@@ -125,9 +128,6 @@ const ValidationPanel = ({
|
|
|
125
128
|
}
|
|
126
129
|
}}
|
|
127
130
|
>
|
|
128
|
-
<div className="validation-issue__icon">
|
|
129
|
-
{getIssueIcon(issue.severity, issue.source)}
|
|
130
|
-
</div>
|
|
131
131
|
|
|
132
132
|
<div className="validation-issue__content">
|
|
133
133
|
<div className="validation-issue__message">
|
|
@@ -137,10 +137,12 @@ const ValidationPanel = ({
|
|
|
137
137
|
<div className="validation-issue__meta">
|
|
138
138
|
{showLineNumbers && issue.line && (
|
|
139
139
|
<span className="validation-issue__location">
|
|
140
|
-
<FormattedMessage
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
<FormattedMessage
|
|
141
|
+
{...messages.lineColumn}
|
|
142
|
+
values={{
|
|
143
|
+
line: issue.line,
|
|
144
|
+
column: issue.column || 1,
|
|
145
|
+
}} />
|
|
144
146
|
</span>
|
|
145
147
|
)}
|
|
146
148
|
|
|
@@ -162,17 +164,21 @@ const ValidationPanel = ({
|
|
|
162
164
|
);
|
|
163
165
|
|
|
164
166
|
// Render panel header with count
|
|
165
|
-
const renderPanelHeader = (title, count, severity) =>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
const renderPanelHeader = (title, count, severity, issues = []) =>
|
|
168
|
+
// Check if any issue in this group is a blocking error
|
|
169
|
+
// Use blocking error icon only if there are blocking errors, otherwise use warning icon
|
|
170
|
+
|
|
171
|
+
(
|
|
172
|
+
<div className="validation-panel__header">
|
|
173
|
+
<span className="validation-panel__title">
|
|
174
|
+
<FormattedMessage {...title} />
|
|
175
|
+
</span>
|
|
176
|
+
{count > 0 && (
|
|
177
|
+
<Badge count={count} style={{ backgroundColor: getSeverityColor(severity) }} />
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
;
|
|
176
182
|
|
|
177
183
|
// Get severity color
|
|
178
184
|
const getSeverityColor = (severity) => {
|
|
@@ -241,14 +247,14 @@ const ValidationPanel = ({
|
|
|
241
247
|
: messages[key] || { id: `htmlEditor.validation.${key}`, defaultMessage: key };
|
|
242
248
|
|
|
243
249
|
const severity = isSourceGroup
|
|
244
|
-
? (issues.find(i => i.severity ===
|
|
245
|
-
|
|
250
|
+
? (issues.find((i) => i.severity === SEVERITY.ERROR) ? SEVERITY.ERROR
|
|
251
|
+
: issues.find((i) => i.severity === SEVERITY.WARNING) ? SEVERITY.WARNING : SEVERITY.INFO)
|
|
246
252
|
: key;
|
|
247
253
|
|
|
248
254
|
return (
|
|
249
255
|
<Panel
|
|
250
256
|
key={key}
|
|
251
|
-
header={renderPanelHeader(title, issues.length, severity)}
|
|
257
|
+
header={renderPanelHeader(title, issues.length, severity, issues)}
|
|
252
258
|
className={`validation-panel__panel validation-panel__panel--${severity}`}
|
|
253
259
|
>
|
|
254
260
|
<div className="validation-panel__issues">
|
|
@@ -291,7 +297,7 @@ ValidationPanel.propTypes = {
|
|
|
291
297
|
onErrorClick: PropTypes.func,
|
|
292
298
|
showLineNumbers: PropTypes.bool,
|
|
293
299
|
groupBySource: PropTypes.bool,
|
|
294
|
-
variant: PropTypes.oneOf(['email', 'inapp'])
|
|
300
|
+
variant: PropTypes.oneOf(['email', 'inapp']),
|
|
295
301
|
};
|
|
296
302
|
|
|
297
303
|
export default ValidationPanel;
|