@capillarytech/creatives-library 8.0.254 → 8.0.255-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.
- 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/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 +457 -72
- package/v2Components/ErrorInfoNote/messages.js +36 -6
- package/v2Components/ErrorInfoNote/style.scss +282 -6
- package/v2Components/FormBuilder/tests/index.test.js +13 -4
- package/v2Components/HtmlEditor/HTMLEditor.js +547 -94
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1358 -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 +22 -101
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +149 -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 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
- 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 +3 -6
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +24 -34
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +50 -34
- package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +70 -41
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +364 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
- package/v2Components/HtmlEditor/constants.js +42 -20
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +794 -0
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
- package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +95 -85
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -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 +134 -102
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
- package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
- package/v2Components/HtmlEditor/utils/validationConstants.js +40 -0
- package/v2Components/MobilePushPreviewV2/index.js +32 -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 +128 -51
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +239 -46
- 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 +106 -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 +1285 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +207 -19
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1870 -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 +629 -77
- package/v2Containers/EmailWrapper/index.js +103 -23
- package/v2Containers/EmailWrapper/messages.js +61 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
- 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 -359
- 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/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
|
@@ -48,13 +48,14 @@ export const CapTagListWithInput = (props) => {
|
|
|
48
48
|
showTagList = true,
|
|
49
49
|
showInput = true,
|
|
50
50
|
inputProps = {},
|
|
51
|
+
popoverPlacement,
|
|
51
52
|
} = props;
|
|
52
53
|
|
|
53
54
|
const { formatMessage } = intl;
|
|
54
55
|
|
|
55
56
|
return (
|
|
56
57
|
<CapColumn style={containerStyle}>
|
|
57
|
-
<CapRow
|
|
58
|
+
<CapRow align="middle" type="flex">
|
|
58
59
|
{showHeading && headingText && (
|
|
59
60
|
<CapHeading type={headingType} style={headingStyle}>
|
|
60
61
|
{headingText}
|
|
@@ -76,6 +77,7 @@ export const CapTagListWithInput = (props) => {
|
|
|
76
77
|
selectedOfferDetails={selectedOfferDetails}
|
|
77
78
|
eventContextTags={eventContextTags}
|
|
78
79
|
style={tagListStyle}
|
|
80
|
+
popoverPlacement={popoverPlacement}
|
|
79
81
|
/>
|
|
80
82
|
)}
|
|
81
83
|
</CapRow>
|
|
@@ -136,6 +138,7 @@ CapTagListWithInput.propTypes = {
|
|
|
136
138
|
showHeading: PropTypes.bool,
|
|
137
139
|
showTagList: PropTypes.bool,
|
|
138
140
|
showInput: PropTypes.bool,
|
|
141
|
+
popoverPlacement: PropTypes.string,
|
|
139
142
|
};
|
|
140
143
|
|
|
141
144
|
CapTagListWithInput.defaultProps = {
|
|
@@ -164,6 +167,7 @@ CapTagListWithInput.defaultProps = {
|
|
|
164
167
|
showTagList: true,
|
|
165
168
|
showInput: true,
|
|
166
169
|
inputProps: {},
|
|
170
|
+
popoverPlacement: undefined,
|
|
167
171
|
};
|
|
168
172
|
|
|
169
173
|
export default injectIntl(CapTagListWithInput);
|
|
@@ -4,6 +4,11 @@ import '@testing-library/jest-dom';
|
|
|
4
4
|
import { render, screen, fireEvent } from '../../../utils/test-utils';
|
|
5
5
|
import { CapWhatsappCTA } from '../index';
|
|
6
6
|
|
|
7
|
+
// Mock the missing reducer
|
|
8
|
+
jest.mock('@capillarytech/cap-ui-library/CapCollapsibleLeftNavigation/reducer', () => {
|
|
9
|
+
return (state = {}) => state;
|
|
10
|
+
}, { virtual: true });
|
|
11
|
+
|
|
7
12
|
const updateHandler = jest.fn();
|
|
8
13
|
const deleteHandler = jest.fn();
|
|
9
14
|
const initializeComponent = (
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LIQUID_DOC_URL = 'https://docs.capillarytech.com/docs/liquid-language-in-messages';
|
|
@@ -1,21 +1,445 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import PropTypes from
|
|
3
|
-
import CapRow from
|
|
4
|
-
import CapButton from
|
|
5
|
-
import CapIcon from
|
|
6
|
-
import CapLabel from
|
|
7
|
-
import
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
4
|
+
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
5
|
+
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
6
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
7
|
+
import CapTab from '@capillarytech/cap-ui-library/CapTab';
|
|
8
|
+
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
8
9
|
import {
|
|
9
10
|
FormattedMessage,
|
|
10
|
-
FormattedNumber,
|
|
11
11
|
injectIntl,
|
|
12
|
-
} from
|
|
13
|
-
import
|
|
14
|
-
import messages from
|
|
15
|
-
import { processErrors } from
|
|
16
|
-
import ErrorTypeRenderer from
|
|
17
|
-
import { ANDROID, GENERIC, IOS } from
|
|
12
|
+
} from 'react-intl';
|
|
13
|
+
import './style.scss';
|
|
14
|
+
import messages from './messages';
|
|
15
|
+
import { processErrors } from './utils';
|
|
16
|
+
import ErrorTypeRenderer from './ErrorTypeRenderer';
|
|
17
|
+
import { ANDROID, GENERIC, IOS } from '../../v2Containers/CreativesContainer/constants';
|
|
18
|
+
import { LABEL_ISSUE_PATTERNS, ERROR_TAB_KEYS } from '../HtmlEditor/utils/validationConstants';
|
|
19
|
+
import { SEVERITY } from '../HtmlEditor/components/ValidationPanel/constants';
|
|
20
|
+
import { LIQUID_DOC_URL } from './constants';
|
|
18
21
|
|
|
22
|
+
const { CapLabelInline } = CapLabel;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Categorize error messages into HTML, Label, and Liquid categories
|
|
26
|
+
*/
|
|
27
|
+
const categorizeErrorMessages = (standardErrors, liquidErrors) => {
|
|
28
|
+
const htmlIssues = [];
|
|
29
|
+
const labelIssues = [];
|
|
30
|
+
const liquidIssues = [];
|
|
31
|
+
|
|
32
|
+
// Process standard errors
|
|
33
|
+
(standardErrors || []).forEach((error, index) => {
|
|
34
|
+
const errorLower = (error || '').toLowerCase();
|
|
35
|
+
|
|
36
|
+
// Check if it's a Label (tag syntax) issue
|
|
37
|
+
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
38
|
+
(pattern) => errorLower.includes(pattern.toLowerCase()),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (isLabelIssue) {
|
|
42
|
+
labelIssues.push({
|
|
43
|
+
message: error,
|
|
44
|
+
severity: SEVERITY.ERROR,
|
|
45
|
+
index,
|
|
46
|
+
source: 'label',
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
htmlIssues.push({
|
|
50
|
+
message: error,
|
|
51
|
+
severity: SEVERITY.ERROR,
|
|
52
|
+
index,
|
|
53
|
+
source: 'html',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Process liquid errors
|
|
59
|
+
(liquidErrors || []).forEach((error, index) => {
|
|
60
|
+
liquidIssues.push({
|
|
61
|
+
message: error,
|
|
62
|
+
severity: SEVERITY.ERROR,
|
|
63
|
+
index,
|
|
64
|
+
source: 'liquid',
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { htmlIssues, labelIssues, liquidIssues };
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Clean error message by removing line/char and rule parts
|
|
73
|
+
* @param {string} message - Original error message
|
|
74
|
+
* @param {RegExpMatchArray|null} lineMatch - Line number match result
|
|
75
|
+
* @param {RegExpMatchArray|null} charMatch - Character number match result
|
|
76
|
+
* @param {RegExpMatchArray|null} ruleMatch - Rule name match result
|
|
77
|
+
* @returns {string} Cleaned message without line/char/rule information
|
|
78
|
+
*/
|
|
79
|
+
const cleanErrorMessage = (message, lineMatch, charMatch, ruleMatch) => {
|
|
80
|
+
let displayMessage = message;
|
|
81
|
+
if (lineMatch) {
|
|
82
|
+
displayMessage = displayMessage.replace(/Line\s+\d+,?\s*/gi, '');
|
|
83
|
+
}
|
|
84
|
+
if (charMatch) {
|
|
85
|
+
displayMessage = displayMessage.replace(/Char\s+\d+\.?\s*/gi, '');
|
|
86
|
+
}
|
|
87
|
+
if (ruleMatch) {
|
|
88
|
+
displayMessage = displayMessage.replace(/•\s*[a-z-]+$/i, '');
|
|
89
|
+
}
|
|
90
|
+
return displayMessage.trim();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get icon based on severity
|
|
95
|
+
*/
|
|
96
|
+
const getSeverityIcon = (severity) => {
|
|
97
|
+
if (severity === SEVERITY.WARNING) {
|
|
98
|
+
return <CapIcon type="alert-warning" className="error-info-note__icon error-info-note__icon--warning" />;
|
|
99
|
+
}
|
|
100
|
+
return <CapIcon type="warning-circle" className="error-info-note__icon error-info-note__icon--error" />;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Tab content component
|
|
105
|
+
*/
|
|
106
|
+
const TabContent = ({ issues, onErrorClick, intl }) => {
|
|
107
|
+
if (!issues || issues.length === 0) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const handleNavigateClick = (issue, e) => {
|
|
112
|
+
e.stopPropagation();
|
|
113
|
+
if (onErrorClick) {
|
|
114
|
+
onErrorClick(issue);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<CapRow className="error-info-note__content">
|
|
120
|
+
{issues.map((issue, index) => {
|
|
121
|
+
const { severity, message } = issue;
|
|
122
|
+
const key = `${message}-${index}`;
|
|
123
|
+
|
|
124
|
+
// Parse line and char from message if present (format: "... Line X, Char Y.")
|
|
125
|
+
const lineMatch = message.match(/Line\s+(\d+)/i);
|
|
126
|
+
const charMatch = message.match(/Char\s+(\d+)/i);
|
|
127
|
+
const line = lineMatch ? parseInt(lineMatch[1], 10) : null;
|
|
128
|
+
const char = charMatch ? parseInt(charMatch[1], 10) : null;
|
|
129
|
+
|
|
130
|
+
// Extract rule from message (format: "... • rule-name")
|
|
131
|
+
const ruleMatch = message.match(/•\s*([a-z-]+)$/i);
|
|
132
|
+
const rule = ruleMatch ? ruleMatch[1] : null;
|
|
133
|
+
|
|
134
|
+
// Clean message (remove line/char and rule parts for display)
|
|
135
|
+
const displayMessage = cleanErrorMessage(message, lineMatch, charMatch, ruleMatch);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<CapRow
|
|
139
|
+
key={key}
|
|
140
|
+
className={`error-info-note__item error-info-note__item--${severity || 'error'}`}
|
|
141
|
+
>
|
|
142
|
+
<CapRow className="error-info-note__item-icon">
|
|
143
|
+
{getSeverityIcon(severity)}
|
|
144
|
+
</CapRow>
|
|
145
|
+
<CapRow className="error-info-note__item-content">
|
|
146
|
+
<CapLabelInline type="label2" className="error-info-note__item-message">
|
|
147
|
+
{displayMessage}
|
|
148
|
+
</CapLabelInline>
|
|
149
|
+
{line && (
|
|
150
|
+
<CapLabelInline type="label2" className="error-info-note__item-location">
|
|
151
|
+
<FormattedMessage {...messages.line} />
|
|
152
|
+
{' '}
|
|
153
|
+
{line}
|
|
154
|
+
{char ? (
|
|
155
|
+
<>
|
|
156
|
+
,
|
|
157
|
+
{' '}
|
|
158
|
+
<FormattedMessage {...messages.char} />
|
|
159
|
+
{' '}
|
|
160
|
+
{char}
|
|
161
|
+
</>
|
|
162
|
+
) : ''}
|
|
163
|
+
.
|
|
164
|
+
</CapLabelInline>
|
|
165
|
+
)}
|
|
166
|
+
{rule && (
|
|
167
|
+
<CapLabelInline type="label2" className="error-info-note__item-rule">
|
|
168
|
+
•
|
|
169
|
+
{' '}
|
|
170
|
+
{rule}
|
|
171
|
+
</CapLabelInline>
|
|
172
|
+
)}
|
|
173
|
+
</CapRow>
|
|
174
|
+
{onErrorClick && (
|
|
175
|
+
<CapTooltip title={intl?.formatMessage ? intl.formatMessage(messages.navigateToError) : 'Go to error location'}>
|
|
176
|
+
<CapButton
|
|
177
|
+
type="flat"
|
|
178
|
+
className="error-info-note__item-navigate"
|
|
179
|
+
onClick={(e) => handleNavigateClick({ ...issue, line, column: char }, e)}
|
|
180
|
+
aria-label={intl?.formatMessage ? intl.formatMessage(messages.navigateToError) : 'Go to error location'}
|
|
181
|
+
>
|
|
182
|
+
<CapIcon type="redirection" />
|
|
183
|
+
</CapButton>
|
|
184
|
+
</CapTooltip>
|
|
185
|
+
)}
|
|
186
|
+
</CapRow>
|
|
187
|
+
);
|
|
188
|
+
})}
|
|
189
|
+
</CapRow>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
TabContent.propTypes = {
|
|
194
|
+
issues: PropTypes.array,
|
|
195
|
+
onErrorClick: PropTypes.func,
|
|
196
|
+
intl: PropTypes.object,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
TabContent.defaultProps = {
|
|
200
|
+
issues: [],
|
|
201
|
+
onErrorClick: null,
|
|
202
|
+
intl: null,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* ErrorInfoNote Component with Tabbed Interface
|
|
207
|
+
* @param {boolean} useLegacyDisplay - If true, uses simple list display instead of tabbed interface (for BEE Editor)
|
|
208
|
+
*/
|
|
209
|
+
export const ErrorInfoNote = (props) => {
|
|
210
|
+
const {
|
|
211
|
+
errorMessages,
|
|
212
|
+
onErrorClick,
|
|
213
|
+
onClose,
|
|
214
|
+
isLiquidEnabled = true,
|
|
215
|
+
intl,
|
|
216
|
+
useLegacyDisplay = false, // Use simple list display instead of tabs (for BEE Editor)
|
|
217
|
+
} = props;
|
|
218
|
+
|
|
219
|
+
const [isDismissed, setIsDismissed] = useState(false);
|
|
220
|
+
const [activeKey, setActiveKey] = useState(null);
|
|
221
|
+
|
|
222
|
+
const {
|
|
223
|
+
LIQUID_ERROR_MSG: rawLiquidErrors = [],
|
|
224
|
+
STANDARD_ERROR_MSG: rawStandardErrors = [],
|
|
225
|
+
} = errorMessages || {};
|
|
226
|
+
|
|
227
|
+
// Detect if platform-specific (ANDROID/IOS) or GENERIC
|
|
228
|
+
const isObject = typeof rawStandardErrors === 'object' && rawStandardErrors !== null;
|
|
229
|
+
const isNotArray = !Array.isArray(rawLiquidErrors);
|
|
230
|
+
const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawLiquidErrors);
|
|
231
|
+
|
|
232
|
+
// For platform-specific errors or when useLegacyDisplay is true, use the legacy renderer
|
|
233
|
+
if (hasPlatformKeys) {
|
|
234
|
+
// Process errors for both platforms - they use the same structure but different platform parameters
|
|
235
|
+
const createPlatformErrors = (platform) => ({
|
|
236
|
+
STANDARD: processErrors(rawStandardErrors, 'standard', platform, messages),
|
|
237
|
+
LIQUID: processErrors(rawLiquidErrors, 'liquid', platform, messages),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const androidErrors = createPlatformErrors(ANDROID);
|
|
241
|
+
const iosErrors = createPlatformErrors(IOS);
|
|
242
|
+
return (
|
|
243
|
+
<ErrorTypeRenderer
|
|
244
|
+
genericErrors={null}
|
|
245
|
+
androidErrors={androidErrors}
|
|
246
|
+
iosErrors={iosErrors}
|
|
247
|
+
ErrorSectionComponent={ErrorSection}
|
|
248
|
+
/>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// For BEE Editor (useLegacyDisplay=true), use simple list display without tabs
|
|
253
|
+
if (useLegacyDisplay) {
|
|
254
|
+
const standardErrors = Array.isArray(rawStandardErrors) ? rawStandardErrors : [];
|
|
255
|
+
const liquidErrors = Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [];
|
|
256
|
+
const hasStandardErrors = standardErrors.length > 0;
|
|
257
|
+
const hasLiquidErrors = liquidErrors.length > 0 && isLiquidEnabled;
|
|
258
|
+
|
|
259
|
+
if (!hasStandardErrors && !hasLiquidErrors) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<CapRow className="error-container error-container--legacy">
|
|
265
|
+
{hasStandardErrors && (
|
|
266
|
+
<ErrorSection
|
|
267
|
+
title={<FormattedMessage {...messages.standardErrorHeader} />}
|
|
268
|
+
errors={standardErrors}
|
|
269
|
+
liquidError={false}
|
|
270
|
+
/>
|
|
271
|
+
)}
|
|
272
|
+
{hasLiquidErrors && (
|
|
273
|
+
<ErrorSection
|
|
274
|
+
title={<FormattedMessage {...messages.dynamicErrorHeader} />}
|
|
275
|
+
errors={liquidErrors}
|
|
276
|
+
liquidError
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
</CapRow>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Categorize errors for tabbed interface
|
|
284
|
+
const { htmlIssues, labelIssues, liquidIssues } = useMemo(() => categorizeErrorMessages(
|
|
285
|
+
Array.isArray(rawStandardErrors) ? rawStandardErrors : [],
|
|
286
|
+
Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [],
|
|
287
|
+
), [rawStandardErrors, rawLiquidErrors]);
|
|
288
|
+
|
|
289
|
+
// Calculate counts
|
|
290
|
+
const htmlCount = htmlIssues.length;
|
|
291
|
+
const labelCount = labelIssues.length;
|
|
292
|
+
const liquidCount = liquidIssues.length;
|
|
293
|
+
// Include liquid issues in total count even when liquid is disabled
|
|
294
|
+
// This ensures liquid errors are shown when liquid content is detected but feature is disabled
|
|
295
|
+
const totalCount = htmlCount + labelCount + liquidCount;
|
|
296
|
+
|
|
297
|
+
// Set default active key
|
|
298
|
+
useMemo(() => {
|
|
299
|
+
if (!activeKey) {
|
|
300
|
+
if (htmlCount > 0) {
|
|
301
|
+
setActiveKey(ERROR_TAB_KEYS.HTML);
|
|
302
|
+
} else if (labelCount > 0) {
|
|
303
|
+
setActiveKey(ERROR_TAB_KEYS.LABEL);
|
|
304
|
+
} else if (liquidCount > 0) {
|
|
305
|
+
// Show liquid tab even when liquid is disabled if liquid content is detected
|
|
306
|
+
setActiveKey(ERROR_TAB_KEYS.LIQUID);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}, [htmlCount, labelCount, liquidCount, activeKey]);
|
|
310
|
+
|
|
311
|
+
// Handle close
|
|
312
|
+
const handleClose = () => {
|
|
313
|
+
setIsDismissed(true);
|
|
314
|
+
if (onClose) {
|
|
315
|
+
onClose();
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Handle liquid documentation click
|
|
320
|
+
const handleLiquidDocClick = () => {
|
|
321
|
+
window.open(
|
|
322
|
+
LIQUID_DOC_URL,
|
|
323
|
+
'_blank',
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Don't render if no issues or dismissed
|
|
328
|
+
if (totalCount === 0 || isDismissed) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Build tab panes (CapTab uses 'panes' with 'tab' and 'content' properties)
|
|
333
|
+
const tabPanes = [];
|
|
334
|
+
|
|
335
|
+
if (htmlCount > 0) {
|
|
336
|
+
tabPanes.push({
|
|
337
|
+
key: ERROR_TAB_KEYS.HTML,
|
|
338
|
+
tab: (
|
|
339
|
+
<CapLabelInline type="label2" className="error-info-note__tab-label">
|
|
340
|
+
<FormattedMessage {...messages.htmlIssues} />
|
|
341
|
+
<CapLabelInline type="label2" className="error-info-note__tab-count">
|
|
342
|
+
(
|
|
343
|
+
{htmlCount}
|
|
344
|
+
)
|
|
345
|
+
</CapLabelInline>
|
|
346
|
+
</CapLabelInline>
|
|
347
|
+
),
|
|
348
|
+
content: (
|
|
349
|
+
<TabContent
|
|
350
|
+
issues={htmlIssues}
|
|
351
|
+
onErrorClick={onErrorClick}
|
|
352
|
+
intl={intl}
|
|
353
|
+
/>
|
|
354
|
+
),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (labelCount > 0) {
|
|
359
|
+
tabPanes.push({
|
|
360
|
+
key: ERROR_TAB_KEYS.LABEL,
|
|
361
|
+
tab: (
|
|
362
|
+
<CapLabelInline type="label2" className="error-info-note__tab-label">
|
|
363
|
+
<FormattedMessage {...messages.labelIssues} />
|
|
364
|
+
<CapLabelInline type="label2" className="error-info-note__tab-count">
|
|
365
|
+
(
|
|
366
|
+
{labelCount}
|
|
367
|
+
)
|
|
368
|
+
</CapLabelInline>
|
|
369
|
+
</CapLabelInline>
|
|
370
|
+
),
|
|
371
|
+
content: (
|
|
372
|
+
<TabContent
|
|
373
|
+
issues={labelIssues}
|
|
374
|
+
onErrorClick={onErrorClick}
|
|
375
|
+
intl={intl}
|
|
376
|
+
/>
|
|
377
|
+
),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Show liquid issues tab even when liquid is disabled if liquid content is detected
|
|
382
|
+
// This allows users to see errors when they add liquid content but liquid feature is not enabled
|
|
383
|
+
if (liquidCount > 0) {
|
|
384
|
+
tabPanes.push({
|
|
385
|
+
key: ERROR_TAB_KEYS.LIQUID,
|
|
386
|
+
tab: (
|
|
387
|
+
<CapLabelInline type="label2" className="error-info-note__tab-label">
|
|
388
|
+
<FormattedMessage {...messages.liquidIssues} />
|
|
389
|
+
<CapLabelInline type="label2" className="error-info-note__tab-count">
|
|
390
|
+
(
|
|
391
|
+
{liquidCount}
|
|
392
|
+
)
|
|
393
|
+
</CapLabelInline>
|
|
394
|
+
</CapLabelInline>
|
|
395
|
+
),
|
|
396
|
+
content: (
|
|
397
|
+
<TabContent
|
|
398
|
+
issues={liquidIssues}
|
|
399
|
+
onErrorClick={onErrorClick}
|
|
400
|
+
intl={intl}
|
|
401
|
+
/>
|
|
402
|
+
),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<CapRow className="error-container error-container--tabs">
|
|
408
|
+
<CapRow className="error-info-note__header">
|
|
409
|
+
<CapTab
|
|
410
|
+
activeKey={activeKey || (tabPanes[0]?.key)}
|
|
411
|
+
onChange={setActiveKey}
|
|
412
|
+
panes={tabPanes}
|
|
413
|
+
className="error-info-note__tabs"
|
|
414
|
+
/>
|
|
415
|
+
<CapRow className="error-info-note__actions">
|
|
416
|
+
{activeKey === ERROR_TAB_KEYS.LIQUID && isLiquidEnabled && (
|
|
417
|
+
<CapButton
|
|
418
|
+
type="flat"
|
|
419
|
+
className="error-info-note__liquid-doc"
|
|
420
|
+
onClick={handleLiquidDocClick}
|
|
421
|
+
>
|
|
422
|
+
<FormattedMessage {...messages.liquidDoc} />
|
|
423
|
+
<CapIcon size="s" type="launch" />
|
|
424
|
+
</CapButton>
|
|
425
|
+
)}
|
|
426
|
+
<CapTooltip title={intl?.formatMessage ? intl.formatMessage(messages.closePanel) : 'Close validation panel'}>
|
|
427
|
+
<CapButton
|
|
428
|
+
type="flat"
|
|
429
|
+
className="error-info-note__close"
|
|
430
|
+
onClick={handleClose}
|
|
431
|
+
aria-label={intl?.formatMessage ? intl.formatMessage(messages.closePanel) : 'Close validation panel'}
|
|
432
|
+
>
|
|
433
|
+
<CapIcon type="close" />
|
|
434
|
+
</CapButton>
|
|
435
|
+
</CapTooltip>
|
|
436
|
+
</CapRow>
|
|
437
|
+
</CapRow>
|
|
438
|
+
</CapRow>
|
|
439
|
+
);
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Legacy ErrorSection component for platform-specific errors (backwards compatibility)
|
|
19
443
|
const ErrorSection = ({
|
|
20
444
|
title,
|
|
21
445
|
errors,
|
|
@@ -26,7 +450,7 @@ const ErrorSection = ({
|
|
|
26
450
|
{title && (
|
|
27
451
|
<CapRow
|
|
28
452
|
className={`error-header ${
|
|
29
|
-
!liquidError ?
|
|
453
|
+
!liquidError ? 'standard-error-header' : ''
|
|
30
454
|
}`}
|
|
31
455
|
>
|
|
32
456
|
<>
|
|
@@ -38,8 +462,7 @@ const ErrorSection = ({
|
|
|
38
462
|
<CapButton
|
|
39
463
|
type="flat"
|
|
40
464
|
className="add-btn"
|
|
41
|
-
onClick={() => window.open(
|
|
42
|
-
}
|
|
465
|
+
onClick={() => window.open('https://docs.capillarytech.com/docs/liquid-language-in-messages', '_blank')}
|
|
43
466
|
>
|
|
44
467
|
<FormattedMessage {...messages.liquidDoc} />
|
|
45
468
|
<CapIcon size="s" type="launch" />
|
|
@@ -53,21 +476,15 @@ const ErrorSection = ({
|
|
|
53
476
|
<CapLabel type="label2">{platformLabel}</CapLabel>
|
|
54
477
|
</CapRow>
|
|
55
478
|
)}
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
dataSource={errors}
|
|
60
|
-
renderItem={(error, index) => (
|
|
61
|
-
<CapList.Item>
|
|
479
|
+
<CapRow className="error-list-legacy">
|
|
480
|
+
{(errors || []).map((error) => (
|
|
481
|
+
<CapRow key={`${error}`} className="error-list-legacy__item">
|
|
62
482
|
<CapLabel type="label2" className="cap-list-v2-error-item">
|
|
63
|
-
<CapLabel type="label2">
|
|
64
|
-
<FormattedNumber value={index + 1} />.
|
|
65
|
-
</CapLabel>
|
|
66
483
|
<CapLabel type="label2">{error}</CapLabel>
|
|
67
484
|
</CapLabel>
|
|
68
|
-
</
|
|
69
|
-
)}
|
|
70
|
-
|
|
485
|
+
</CapRow>
|
|
486
|
+
))}
|
|
487
|
+
</CapRow>
|
|
71
488
|
</>
|
|
72
489
|
);
|
|
73
490
|
|
|
@@ -84,54 +501,16 @@ ErrorSection.defaultProps = {
|
|
|
84
501
|
platformLabel: null,
|
|
85
502
|
};
|
|
86
503
|
|
|
87
|
-
export const ErrorInfoNote = (props) => {
|
|
88
|
-
const { errorMessages } = props;
|
|
89
|
-
|
|
90
|
-
const {
|
|
91
|
-
LIQUID_ERROR_MSG: rawLiquidErrors = [],
|
|
92
|
-
STANDARD_ERROR_MSG: rawStandardErrors = [],
|
|
93
|
-
} = errorMessages || {};
|
|
94
|
-
|
|
95
|
-
// Detect if platform-specific (ANDROID/IOS) or GENERIC
|
|
96
|
-
const isObject = typeof rawStandardErrors === 'object' && rawStandardErrors !== null;
|
|
97
|
-
const isNotArray = !Array.isArray(rawLiquidErrors);
|
|
98
|
-
const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawLiquidErrors);
|
|
99
|
-
|
|
100
|
-
if (hasPlatformKeys) {
|
|
101
|
-
// Platform-specific
|
|
102
|
-
const androidErrors = {
|
|
103
|
-
STANDARD: processErrors(rawStandardErrors, 'standard', ANDROID, messages),
|
|
104
|
-
LIQUID: processErrors(rawLiquidErrors, 'liquid', ANDROID, messages),
|
|
105
|
-
};
|
|
106
|
-
const iosErrors = {
|
|
107
|
-
STANDARD: processErrors(rawStandardErrors, 'standard', IOS, messages),
|
|
108
|
-
LIQUID: processErrors(rawLiquidErrors, 'liquid', IOS, messages),
|
|
109
|
-
};
|
|
110
|
-
return (
|
|
111
|
-
<ErrorTypeRenderer
|
|
112
|
-
genericErrors={null}
|
|
113
|
-
androidErrors={androidErrors}
|
|
114
|
-
iosErrors={iosErrors}
|
|
115
|
-
ErrorSectionComponent={ErrorSection}
|
|
116
|
-
/>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
// GENERIC (not platform-specific)
|
|
120
|
-
const genericStandard = processErrors(rawStandardErrors, 'standard', null, messages);
|
|
121
|
-
const genericLiquid = processErrors(rawLiquidErrors, 'liquid', null, messages);
|
|
122
|
-
return (
|
|
123
|
-
<ErrorTypeRenderer
|
|
124
|
-
genericErrors={{ standard: genericStandard, liquid: genericLiquid }}
|
|
125
|
-
ErrorSectionComponent={ErrorSection}
|
|
126
|
-
/>
|
|
127
|
-
);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
504
|
ErrorInfoNote.defaultProps = {
|
|
131
505
|
errorMessages: {
|
|
132
506
|
LIQUID_ERROR_MSG: [],
|
|
133
507
|
STANDARD_ERROR_MSG: [],
|
|
134
508
|
},
|
|
509
|
+
onErrorClick: null,
|
|
510
|
+
onClose: null,
|
|
511
|
+
isLiquidEnabled: true,
|
|
512
|
+
intl: null,
|
|
513
|
+
useLegacyDisplay: false, // Use simple list display for BEE Editor
|
|
135
514
|
};
|
|
136
515
|
|
|
137
516
|
ErrorInfoNote.propTypes = {
|
|
@@ -153,5 +532,11 @@ ErrorInfoNote.propTypes = {
|
|
|
153
532
|
}),
|
|
154
533
|
]),
|
|
155
534
|
}),
|
|
535
|
+
onErrorClick: PropTypes.func,
|
|
536
|
+
onClose: PropTypes.func,
|
|
537
|
+
isLiquidEnabled: PropTypes.bool,
|
|
538
|
+
intl: PropTypes.object,
|
|
539
|
+
useLegacyDisplay: PropTypes.bool, // Use simple list display for BEE Editor
|
|
156
540
|
};
|
|
541
|
+
|
|
157
542
|
export default injectIntl(ErrorInfoNote);
|