@capillarytech/creatives-library 8.0.246-alpha.0 → 8.0.246
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 +1 -2
- package/initialReducer.js +0 -2
- package/package.json +1 -1
- package/services/api.js +0 -10
- package/services/tests/api.test.js +0 -18
- package/utils/common.js +0 -5
- package/utils/commonUtils.js +5 -28
- package/utils/tests/commonUtil.test.js +0 -224
- package/utils/transformTemplateConfig.js +10 -0
- package/v2Components/CapDeviceContent/index.js +56 -61
- package/v2Components/CapTagList/index.js +1 -6
- package/v2Components/CapTagListWithInput/index.js +1 -5
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
- package/v2Components/ErrorInfoNote/index.js +72 -447
- package/v2Components/ErrorInfoNote/messages.js +0 -22
- package/v2Components/ErrorInfoNote/style.scss +4 -280
- package/v2Components/FormBuilder/tests/index.test.js +4 -13
- package/v2Components/HtmlEditor/HTMLEditor.js +94 -642
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1135
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +16 -27
- package/v2Components/HtmlEditor/_htmlEditor.scss +45 -108
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -13
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +139 -148
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +0 -9
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -22
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +6 -3
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +13 -11
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
- package/v2Components/HtmlEditor/constants.js +20 -29
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
- package/v2Components/HtmlEditor/hooks/useValidation.js +45 -150
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +85 -95
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +102 -134
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
- package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -66
- package/v2Components/MobilePushPreviewV2/index.js +7 -32
- package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
- package/v2Components/TemplatePreview/index.js +32 -47
- package/v2Components/TemplatePreview/messages.js +0 -4
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +0 -1
- package/v2Components/TestAndPreviewSlidebox/index.js +25 -31
- package/v2Containers/BeeEditor/index.js +90 -172
- package/v2Containers/CreativesContainer/SlideBoxContent.js +51 -128
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +12 -113
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -2
- package/v2Containers/CreativesContainer/constants.js +0 -1
- package/v2Containers/CreativesContainer/index.js +46 -238
- package/v2Containers/CreativesContainer/messages.js +0 -8
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +2 -11
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +50 -38
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -91
- package/v2Containers/Email/actions.js +0 -7
- package/v2Containers/Email/constants.js +1 -5
- package/v2Containers/Email/index.js +30 -229
- package/v2Containers/Email/messages.js +0 -32
- package/v2Containers/Email/reducer.js +1 -12
- package/v2Containers/Email/sagas.js +7 -61
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
- package/v2Containers/Email/tests/sagas.test.js +1 -1
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +15 -210
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
- package/v2Containers/EmailWrapper/constants.js +0 -2
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +77 -629
- package/v2Containers/EmailWrapper/index.js +23 -103
- package/v2Containers/EmailWrapper/messages.js +1 -61
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -509
- package/v2Containers/InApp/actions.js +0 -7
- package/v2Containers/InApp/constants.js +4 -20
- package/v2Containers/InApp/index.js +357 -801
- package/v2Containers/InApp/index.scss +3 -4
- package/v2Containers/InApp/messages.js +3 -7
- package/v2Containers/InApp/reducer.js +3 -21
- package/v2Containers/InApp/sagas.js +9 -29
- package/v2Containers/InApp/selectors.js +5 -25
- package/v2Containers/InApp/tests/index.test.js +50 -154
- package/v2Containers/InApp/tests/reducer.test.js +0 -34
- package/v2Containers/InApp/tests/sagas.test.js +9 -61
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +0 -3
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +0 -2
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +0 -2
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +0 -9
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -12
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -4
- package/v2Containers/TagList/index.js +19 -62
- package/v2Containers/Templates/_templates.scss +1 -60
- package/v2Containers/Templates/index.js +4 -89
- package/v2Containers/Templates/messages.js +0 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +0 -35
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -874
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -254
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -363
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +0 -630
- package/v2Containers/BeePopupEditor/constants.js +0 -10
- package/v2Containers/BeePopupEditor/index.js +0 -193
- package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1317
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -1605
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +0 -520
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -643
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
- package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
- package/v2Containers/InApp/tests/selectors.test.js +0 -612
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
- package/v2Containers/InAppWrapper/constants.js +0 -16
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
- package/v2Containers/InAppWrapper/index.js +0 -148
- package/v2Containers/InAppWrapper/messages.js +0 -49
- package/v2Containers/InappAdvance/index.js +0 -1099
- package/v2Containers/InappAdvance/index.scss +0 -10
- package/v2Containers/InappAdvance/tests/index.test.js +0 -448
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
* Note: Uses injectIntl with forwardRef to provide direct access to CodeEditorPane via ref
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import React, {
|
|
16
|
-
useRef, useCallback, useMemo, useState, useEffect, useImperativeHandle, forwardRef,
|
|
17
|
-
} from 'react';
|
|
15
|
+
import React, { useRef, useCallback, useMemo, useState } from 'react';
|
|
18
16
|
import PropTypes from 'prop-types';
|
|
17
|
+
import { Layout } from 'antd'; // Fallback - no Cap UI equivalent
|
|
19
18
|
import { injectIntl, intlShape } from 'react-intl';
|
|
20
19
|
|
|
21
20
|
// Cap UI Components (First Preference)
|
|
22
21
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
22
|
+
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
23
23
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
24
24
|
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
25
25
|
import CapModal from '@capillarytech/cap-ui-library/CapModal';
|
|
@@ -30,6 +30,7 @@ import DeviceToggle from './components/DeviceToggle';
|
|
|
30
30
|
import SplitContainer from './components/SplitContainer';
|
|
31
31
|
import CodeEditorPane from './components/CodeEditorPane';
|
|
32
32
|
import PreviewPane from './components/PreviewPane';
|
|
33
|
+
import ValidationErrorDisplay from './components/ValidationErrorDisplay';
|
|
33
34
|
import { EditorProvider } from './components/common/EditorContext';
|
|
34
35
|
|
|
35
36
|
// Hooks and utilities
|
|
@@ -39,9 +40,7 @@ import { useLayoutState } from './hooks/useLayoutState';
|
|
|
39
40
|
import { useValidation } from './hooks/useValidation';
|
|
40
41
|
|
|
41
42
|
// Constants
|
|
42
|
-
import {
|
|
43
|
-
HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT, TAG, EMBEDDED, DEFAULT, FULL, ALL, SMS, EMAIL,
|
|
44
|
-
} from './constants';
|
|
43
|
+
import { HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT } from './constants';
|
|
45
44
|
|
|
46
45
|
// Styles
|
|
47
46
|
import './_htmlEditor.scss';
|
|
@@ -50,7 +49,7 @@ import './components/FullscreenModal/_fullscreenModal.scss';
|
|
|
50
49
|
// Messages
|
|
51
50
|
import messages from './messages';
|
|
52
51
|
|
|
53
|
-
const HTMLEditor =
|
|
52
|
+
const HTMLEditor = ({
|
|
54
53
|
intl,
|
|
55
54
|
variant = HTML_EDITOR_VARIANTS.EMAIL, // New prop: 'email' or 'inapp'
|
|
56
55
|
layoutType, // Layout type for InApp variant
|
|
@@ -62,33 +61,17 @@ const HTMLEditor = forwardRef(({
|
|
|
62
61
|
showFullscreenButton = true,
|
|
63
62
|
autoSave = true,
|
|
64
63
|
autoSaveInterval = 30000, // 30 seconds
|
|
65
|
-
// Tag-related props - tags are fetched and managed by parent component (EmailHTMLEditor, INAPP, etc.)
|
|
66
|
-
tags = [],
|
|
67
|
-
injectedTags = {},
|
|
68
|
-
location,
|
|
69
|
-
eventContextTags = [],
|
|
70
|
-
selectedOfferDetails = [],
|
|
71
|
-
channel,
|
|
72
|
-
userLocale = 'en',
|
|
73
|
-
moduleFilterEnabled = true,
|
|
74
|
-
onTagContextChange, // Parent component handles tag fetching
|
|
75
|
-
onTagSelect = null,
|
|
76
|
-
onContextChange = null,
|
|
77
|
-
globalActions = null,
|
|
78
|
-
isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
|
|
79
|
-
isFullMode = true, // Full mode vs library mode - controls layout and visibility
|
|
80
|
-
onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
|
|
81
|
-
onValidationChange = null, // Callback when validation state changes (for parent to track errors)
|
|
82
|
-
apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent { liquidErrors: [], standardErrors: [] }
|
|
83
64
|
...props
|
|
84
|
-
}
|
|
65
|
+
}) => {
|
|
85
66
|
// Separate refs for main and modal editors to avoid conflicts
|
|
86
67
|
const mainEditorRef = useRef(null);
|
|
87
68
|
const modalEditorRef = useRef(null);
|
|
88
69
|
const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
|
|
89
70
|
|
|
90
71
|
// Get the currently active editor ref based on fullscreen state
|
|
91
|
-
const getActiveEditorRef = useCallback(() =>
|
|
72
|
+
const getActiveEditorRef = useCallback(() => {
|
|
73
|
+
return isFullscreenModalOpen ? modalEditorRef : mainEditorRef;
|
|
74
|
+
}, [isFullscreenModalOpen]);
|
|
92
75
|
|
|
93
76
|
// Initialize custom hooks for state management - always call both hooks to follow Rules of Hooks
|
|
94
77
|
const isEmailVariant = variant === HTML_EDITOR_VARIANTS.EMAIL;
|
|
@@ -97,7 +80,7 @@ const HTMLEditor = forwardRef(({
|
|
|
97
80
|
autoSave: isEmailVariant ? autoSave : false,
|
|
98
81
|
autoSaveInterval,
|
|
99
82
|
onSave: isEmailVariant ? onSave : null,
|
|
100
|
-
onChange: isEmailVariant ? onContentChange : null
|
|
83
|
+
onChange: isEmailVariant ? onContentChange : null
|
|
101
84
|
};
|
|
102
85
|
|
|
103
86
|
const emailContent = useEditorContent(
|
|
@@ -114,7 +97,7 @@ const HTMLEditor = forwardRef(({
|
|
|
114
97
|
// Convert string content to device-specific format
|
|
115
98
|
inAppInitialContent = {
|
|
116
99
|
[DEVICE_TYPES.ANDROID]: initialContent,
|
|
117
|
-
[DEVICE_TYPES.IOS]: initialContent
|
|
100
|
+
[DEVICE_TYPES.IOS]: initialContent
|
|
118
101
|
};
|
|
119
102
|
} else {
|
|
120
103
|
// Use provided device-specific content
|
|
@@ -126,7 +109,7 @@ const HTMLEditor = forwardRef(({
|
|
|
126
109
|
autoSave: isInAppVariant ? autoSave : false,
|
|
127
110
|
autoSaveInterval,
|
|
128
111
|
onSave: isInAppVariant ? onSave : null,
|
|
129
|
-
onChange: isInAppVariant ? onContentChange : null
|
|
112
|
+
onChange: isInAppVariant ? onContentChange : null
|
|
130
113
|
};
|
|
131
114
|
|
|
132
115
|
const inAppContent = useInAppContent(inAppInitialContent, inAppOptions);
|
|
@@ -134,64 +117,6 @@ const HTMLEditor = forwardRef(({
|
|
|
134
117
|
// Use appropriate content hook based on variant
|
|
135
118
|
const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
|
|
136
119
|
|
|
137
|
-
// Update content when initialContent prop changes (for edit mode)
|
|
138
|
-
// This ensures the editor updates when template data loads
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
if (isEmailVariant && emailContent && initialContent !== undefined && initialContent !== null) {
|
|
141
|
-
// Only update if content is different to avoid unnecessary updates
|
|
142
|
-
if (emailContent.content !== initialContent) {
|
|
143
|
-
emailContent.updateContent(initialContent, true); // immediate update
|
|
144
|
-
}
|
|
145
|
-
} else if (isInAppVariant && inAppContent && initialContent !== undefined && initialContent !== null) {
|
|
146
|
-
// Handle InApp variant updates
|
|
147
|
-
const contentToUpdate = typeof initialContent === 'string'
|
|
148
|
-
? { [DEVICE_TYPES.ANDROID]: initialContent, [DEVICE_TYPES.IOS]: initialContent }
|
|
149
|
-
: initialContent;
|
|
150
|
-
if (inAppContent.updateContent) {
|
|
151
|
-
const currentContent = inAppContent.getDeviceContent?.(inAppContent.activeDevice);
|
|
152
|
-
const newContent = contentToUpdate[inAppContent.activeDevice] || contentToUpdate[DEVICE_TYPES.ANDROID] || '';
|
|
153
|
-
if (currentContent !== newContent) {
|
|
154
|
-
inAppContent.updateContent(newContent, true);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}, [initialContent, isEmailVariant, isInAppVariant]);
|
|
159
|
-
// Handle context change for tag API calls
|
|
160
|
-
// If variant is INAPP, use SMS layout; otherwise use the channel (EMAIL)
|
|
161
|
-
const handleContextChange = useCallback((contextData) => {
|
|
162
|
-
// If onContextChange is provided, use it instead of making our own API call
|
|
163
|
-
// This prevents duplicate API calls when parent component handles tag fetching
|
|
164
|
-
if (onContextChange) {
|
|
165
|
-
onContextChange(contextData);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Only make API call if onContextChange is not provided and globalActions is available
|
|
170
|
-
if (!globalActions || !location) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const { type } = location.query || {};
|
|
175
|
-
const tempData = (contextData || '').toLowerCase();
|
|
176
|
-
const isEmbedded = type === EMBEDDED;
|
|
177
|
-
const embedded = isEmbedded ? type : FULL;
|
|
178
|
-
const context = tempData === ALL ? DEFAULT : tempData;
|
|
179
|
-
|
|
180
|
-
// Determine layout: INAPP variant uses SMS, EMAIL variant uses EMAIL
|
|
181
|
-
const layout = variant === HTML_EDITOR_VARIANTS.INAPP ? SMS : EMAIL;
|
|
182
|
-
|
|
183
|
-
const query = {
|
|
184
|
-
layout,
|
|
185
|
-
type: TAG,
|
|
186
|
-
context,
|
|
187
|
-
embedded,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// Call the API via Redux action - this will trigger the saga which calls Api.fetchSchemaForEntity
|
|
191
|
-
// The API endpoint will be: /meta/TAG?query={...}
|
|
192
|
-
globalActions.fetchSchemaForEntity(query);
|
|
193
|
-
}, [variant, globalActions, location, onContextChange]);
|
|
194
|
-
|
|
195
120
|
// Destructure content properties for cleaner access throughout component
|
|
196
121
|
const {
|
|
197
122
|
activeDevice,
|
|
@@ -199,14 +124,14 @@ const HTMLEditor = forwardRef(({
|
|
|
199
124
|
switchDevice,
|
|
200
125
|
toggleContentSync,
|
|
201
126
|
getDeviceContent,
|
|
202
|
-
markAsSaved
|
|
127
|
+
markAsSaved
|
|
203
128
|
} = content || {};
|
|
204
129
|
|
|
205
130
|
const layout = useLayoutState({
|
|
206
131
|
splitSizes: [50, 50],
|
|
207
132
|
viewMode: 'desktop',
|
|
208
133
|
mobileWidth: 375,
|
|
209
|
-
isFullscreen: false
|
|
134
|
+
isFullscreen: false
|
|
210
135
|
});
|
|
211
136
|
|
|
212
137
|
// Get current content for validation based on variant
|
|
@@ -233,7 +158,7 @@ const HTMLEditor = forwardRef(({
|
|
|
233
158
|
'sanitizer.productionValidHtml': messages.sanitizer.productionValidHtml,
|
|
234
159
|
'sanitizer.productionSanitized': messages.sanitizer.productionSanitized,
|
|
235
160
|
'sanitizer.productionInlineCss': messages.sanitizer.productionInlineCss,
|
|
236
|
-
'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
|
|
161
|
+
'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
|
|
237
162
|
};
|
|
238
163
|
|
|
239
164
|
const messageObj = messageMap[messageKey];
|
|
@@ -254,7 +179,7 @@ const HTMLEditor = forwardRef(({
|
|
|
254
179
|
'validator.largeImageDetected': messages.validator.largeImageDetected,
|
|
255
180
|
'validator.unclosedCssRule': messages.validator.unclosedCssRule,
|
|
256
181
|
'validator.emptyCssRule': messages.validator.emptyCssRule,
|
|
257
|
-
'validator.cssValidationFailed': messages.validator.cssValidationFailed
|
|
182
|
+
'validator.cssValidationFailed': messages.validator.cssValidationFailed
|
|
258
183
|
};
|
|
259
184
|
|
|
260
185
|
const messageObj = messageMap[messageKey];
|
|
@@ -265,471 +190,57 @@ const HTMLEditor = forwardRef(({
|
|
|
265
190
|
enableRealTime: true,
|
|
266
191
|
debounceMs: 500,
|
|
267
192
|
enableSanitization: true,
|
|
268
|
-
securityLevel: 'standard'
|
|
269
|
-
apiValidationErrors, // Pass API validation errors to merge with client-side validation
|
|
193
|
+
securityLevel: 'standard'
|
|
270
194
|
}, formatSanitizerMessage, formatValidatorMessage);
|
|
271
195
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
getIssueCounts: () => {
|
|
278
|
-
// Check if validation is ready and has getAllIssues method
|
|
279
|
-
if (!validation || typeof validation.getAllIssues !== 'function') {
|
|
280
|
-
return {
|
|
281
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
const allIssues = validation.getAllIssues();
|
|
285
|
-
const ISSUE_SOURCES = {
|
|
286
|
-
HTMLHINT: 'htmlhint',
|
|
287
|
-
CSS_VALIDATOR: 'css-validator',
|
|
288
|
-
CUSTOM: 'custom',
|
|
289
|
-
SECURITY: 'security',
|
|
290
|
-
LIQUID: 'liquid-validator',
|
|
291
|
-
};
|
|
292
|
-
const LABEL_ISSUE_PATTERNS = [
|
|
293
|
-
'tag must be paired',
|
|
294
|
-
'open tag match failed',
|
|
295
|
-
'closed tag match failed',
|
|
296
|
-
'unclosed',
|
|
297
|
-
'missing required',
|
|
298
|
-
'tag-pair',
|
|
299
|
-
'attr-value-not-empty',
|
|
300
|
-
'attr-no-duplication',
|
|
301
|
-
'tag-self-close',
|
|
302
|
-
'spec-char-escape',
|
|
303
|
-
'tagname-lowercase',
|
|
304
|
-
'attr-lowercase',
|
|
305
|
-
'src-not-empty',
|
|
306
|
-
'alt-require',
|
|
307
|
-
];
|
|
308
|
-
|
|
309
|
-
let htmlCount = 0;
|
|
310
|
-
let labelCount = 0;
|
|
311
|
-
let liquidCount = 0;
|
|
312
|
-
|
|
313
|
-
allIssues.forEach((issue) => {
|
|
314
|
-
const { source, rule, message } = issue;
|
|
315
|
-
const messageLower = (message || '').toLowerCase();
|
|
316
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
317
|
-
|
|
318
|
-
// Check if it's a Liquid issue
|
|
319
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
320
|
-
liquidCount++;
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Check if it's a Label (tag syntax) issue
|
|
325
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
326
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
327
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
if (isLabelIssue) {
|
|
331
|
-
labelCount++;
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
196
|
+
// Handle label insertion at cursor position
|
|
197
|
+
const handleLabelInsert = useCallback((label, position) => {
|
|
198
|
+
// With injectIntl({ forwardRef: true }), ref points directly to CodeEditorPane
|
|
199
|
+
const activeEditorRef = getActiveEditorRef();
|
|
200
|
+
const editor = activeEditorRef.current;
|
|
334
201
|
|
|
335
|
-
|
|
336
|
-
|
|
202
|
+
if (!editor) {
|
|
203
|
+
CapNotification.warning({
|
|
204
|
+
message: intl.formatMessage(messages.labelInsertError),
|
|
205
|
+
description: intl.formatMessage(messages.editorNotReady),
|
|
206
|
+
duration: 3
|
|
337
207
|
});
|
|
338
|
-
|
|
339
|
-
return {
|
|
340
|
-
html: htmlCount,
|
|
341
|
-
label: labelCount,
|
|
342
|
-
liquid: liquidCount,
|
|
343
|
-
total: allIssues.length,
|
|
344
|
-
};
|
|
345
|
-
},
|
|
346
|
-
getValidationState: () =>
|
|
347
|
-
// Return validation state including whether validation is complete
|
|
348
|
-
({
|
|
349
|
-
isValidating: validation?.isValidating || false,
|
|
350
|
-
hasErrors: validation?.hasErrors || false,
|
|
351
|
-
issueCounts: (() => {
|
|
352
|
-
if (!validation || typeof validation.getAllIssues !== 'function') {
|
|
353
|
-
return {
|
|
354
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
const allIssues = validation.getAllIssues();
|
|
358
|
-
// Use same logic as getIssueCounts
|
|
359
|
-
const ISSUE_SOURCES = {
|
|
360
|
-
HTMLHINT: 'htmlhint',
|
|
361
|
-
CSS_VALIDATOR: 'css-validator',
|
|
362
|
-
CUSTOM: 'custom',
|
|
363
|
-
SECURITY: 'security',
|
|
364
|
-
LIQUID: 'liquid-validator',
|
|
365
|
-
};
|
|
366
|
-
const LABEL_ISSUE_PATTERNS = [
|
|
367
|
-
'tag must be paired',
|
|
368
|
-
'open tag match failed',
|
|
369
|
-
'closed tag match failed',
|
|
370
|
-
'unclosed',
|
|
371
|
-
'missing required',
|
|
372
|
-
'tag-pair',
|
|
373
|
-
'attr-value-not-empty',
|
|
374
|
-
'attr-no-duplication',
|
|
375
|
-
'tag-self-close',
|
|
376
|
-
'spec-char-escape',
|
|
377
|
-
'tagname-lowercase',
|
|
378
|
-
'attr-lowercase',
|
|
379
|
-
'src-not-empty',
|
|
380
|
-
'alt-require',
|
|
381
|
-
];
|
|
382
|
-
|
|
383
|
-
let htmlCount = 0;
|
|
384
|
-
let labelCount = 0;
|
|
385
|
-
let liquidCount = 0;
|
|
386
|
-
|
|
387
|
-
allIssues.forEach((issue) => {
|
|
388
|
-
const { source, rule, message } = issue;
|
|
389
|
-
const messageLower = (message || '').toLowerCase();
|
|
390
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
391
|
-
|
|
392
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
393
|
-
liquidCount++;
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
398
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
399
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
if (isLabelIssue) {
|
|
403
|
-
labelCount++;
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
htmlCount++;
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
html: htmlCount,
|
|
412
|
-
label: labelCount,
|
|
413
|
-
liquid: liquidCount,
|
|
414
|
-
total: allIssues.length,
|
|
415
|
-
};
|
|
416
|
-
})(),
|
|
417
|
-
})
|
|
418
|
-
,
|
|
419
|
-
}), [validation, currentContent, apiValidationErrors]); // Include apiValidationErrors so ref methods return updated counts
|
|
420
|
-
|
|
421
|
-
// Use ref to store callback to avoid infinite loops (callback in deps would cause re-runs)
|
|
422
|
-
const onValidationChangeRef = useRef(onValidationChange);
|
|
423
|
-
useEffect(() => {
|
|
424
|
-
onValidationChangeRef.current = onValidationChange;
|
|
425
|
-
}, [onValidationChange]);
|
|
426
|
-
|
|
427
|
-
// Track last sent validation state to prevent duplicate updates
|
|
428
|
-
const lastSentValidationStateRef = useRef(null);
|
|
429
|
-
|
|
430
|
-
// Store validation ref to access current value without triggering re-renders
|
|
431
|
-
const validationRef = useRef(validation);
|
|
432
|
-
validationRef.current = validation;
|
|
433
|
-
|
|
434
|
-
// Extract STABLE primitive values from validation for dependency array
|
|
435
|
-
// This prevents the useEffect from running on every render due to validation object recreation
|
|
436
|
-
const isValidating = validation?.isValidating;
|
|
437
|
-
const validationTotalErrors = validation?.summary?.totalErrors || 0;
|
|
438
|
-
const validationTotalWarnings = validation?.summary?.totalWarnings || 0;
|
|
439
|
-
const validationLastValidated = validation?.lastValidated?.getTime?.() || null;
|
|
440
|
-
// Track total issues (errors + warnings + info) for detecting changes
|
|
441
|
-
const validationHasErrors = validation?.hasErrors || false;
|
|
442
|
-
|
|
443
|
-
// Notify parent component when validation state changes
|
|
444
|
-
useEffect(() => {
|
|
445
|
-
if (!onValidationChangeRef.current) {
|
|
446
208
|
return;
|
|
447
209
|
}
|
|
448
210
|
|
|
449
|
-
//
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const calculateIssueCounts = () => {
|
|
456
|
-
const currentValidation = validationRef.current;
|
|
457
|
-
if (!currentValidation || typeof currentValidation.getAllIssues !== 'function') {
|
|
458
|
-
return {
|
|
459
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
const allIssues = currentValidation.getAllIssues();
|
|
463
|
-
const ISSUE_SOURCES = {
|
|
464
|
-
HTMLHINT: 'htmlhint',
|
|
465
|
-
CSS_VALIDATOR: 'css-validator',
|
|
466
|
-
CUSTOM: 'custom',
|
|
467
|
-
SECURITY: 'security',
|
|
468
|
-
LIQUID: 'liquid-validator',
|
|
469
|
-
};
|
|
470
|
-
const LABEL_ISSUE_PATTERNS = [
|
|
471
|
-
'tag must be paired',
|
|
472
|
-
'open tag match failed',
|
|
473
|
-
'closed tag match failed',
|
|
474
|
-
'unclosed',
|
|
475
|
-
'missing required',
|
|
476
|
-
'tag-pair',
|
|
477
|
-
'attr-value-not-empty',
|
|
478
|
-
'attr-no-duplication',
|
|
479
|
-
'tag-self-close',
|
|
480
|
-
'spec-char-escape',
|
|
481
|
-
'tagname-lowercase',
|
|
482
|
-
'attr-lowercase',
|
|
483
|
-
'src-not-empty',
|
|
484
|
-
'alt-require',
|
|
485
|
-
];
|
|
486
|
-
|
|
487
|
-
let htmlCount = 0;
|
|
488
|
-
let labelCount = 0;
|
|
489
|
-
let liquidCount = 0;
|
|
490
|
-
|
|
491
|
-
allIssues.forEach((issue) => {
|
|
492
|
-
const { source, rule, message } = issue;
|
|
493
|
-
const messageLower = (message || '').toLowerCase();
|
|
494
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
495
|
-
|
|
496
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
497
|
-
liquidCount += 1;
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
502
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
503
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
if (isLabelIssue) {
|
|
507
|
-
labelCount += 1;
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
htmlCount += 1;
|
|
211
|
+
// Check if the required methods exist
|
|
212
|
+
if (typeof editor?.insertText !== 'function') {
|
|
213
|
+
CapNotification.error({
|
|
214
|
+
message: intl.formatMessage(messages.labelInsertError),
|
|
215
|
+
description: intl.formatMessage(messages.editorMethodNotAvailable),
|
|
216
|
+
duration: 4
|
|
512
217
|
});
|
|
513
|
-
|
|
514
|
-
return {
|
|
515
|
-
html: htmlCount,
|
|
516
|
-
label: labelCount,
|
|
517
|
-
liquid: liquidCount,
|
|
518
|
-
total: allIssues.length,
|
|
519
|
-
};
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
const issueCounts = calculateIssueCounts();
|
|
523
|
-
const isContentEmpty = !currentContent || currentContent.trim() === '';
|
|
524
|
-
|
|
525
|
-
// Create new state to compare
|
|
526
|
-
const newState = {
|
|
527
|
-
isContentEmpty,
|
|
528
|
-
issueCounts,
|
|
529
|
-
validationComplete: true,
|
|
530
|
-
hasErrors: issueCounts.total > 0,
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
// Only call callback if state actually changed (prevent infinite loops)
|
|
534
|
-
const lastState = lastSentValidationStateRef.current;
|
|
535
|
-
const hasChanged = !lastState
|
|
536
|
-
|| lastState.isContentEmpty !== newState.isContentEmpty
|
|
537
|
-
|| lastState.validationComplete !== newState.validationComplete
|
|
538
|
-
|| lastState.hasErrors !== newState.hasErrors
|
|
539
|
-
|| lastState.issueCounts?.total !== newState.issueCounts?.total
|
|
540
|
-
|| lastState.issueCounts?.html !== newState.issueCounts?.html
|
|
541
|
-
|| lastState.issueCounts?.label !== newState.issueCounts?.label
|
|
542
|
-
|| lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
|
|
543
|
-
|
|
544
|
-
if (hasChanged) {
|
|
545
|
-
lastSentValidationStateRef.current = newState;
|
|
546
|
-
onValidationChangeRef.current(newState);
|
|
547
|
-
}
|
|
548
|
-
}, [isValidating, validationTotalErrors, validationTotalWarnings, validationLastValidated, validationHasErrors, currentContent, apiValidationErrors]); // Include apiValidationErrors to trigger recalculation when API errors change
|
|
549
|
-
|
|
550
|
-
// Send initial state on mount to ensure parent has correct initial button state
|
|
551
|
-
const hasInitializedRef = useRef(false);
|
|
552
|
-
useEffect(() => {
|
|
553
|
-
if (hasInitializedRef.current || !onValidationChangeRef.current) {
|
|
554
218
|
return;
|
|
555
219
|
}
|
|
556
|
-
hasInitializedRef.current = true;
|
|
557
|
-
|
|
558
|
-
// Send initial state with validationComplete=false to indicate validation pending
|
|
559
|
-
const isContentEmpty = !currentContent || currentContent.trim() === '';
|
|
560
|
-
onValidationChangeRef.current({
|
|
561
|
-
isContentEmpty,
|
|
562
|
-
issueCounts: {
|
|
563
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
564
|
-
},
|
|
565
|
-
validationComplete: false, // Validation hasn't run yet
|
|
566
|
-
hasErrors: false,
|
|
567
|
-
});
|
|
568
|
-
}, [currentContent]); // Only depend on currentContent to run on initial content load
|
|
569
|
-
|
|
570
|
-
// Force validation state recalculation when API validation errors change
|
|
571
|
-
// This ensures that API errors are included in issue counts and displayed in ValidationErrorDisplay
|
|
572
|
-
useEffect(() => {
|
|
573
|
-
if (!onValidationChangeRef.current) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Skip if still validating (wait for validation to complete)
|
|
578
|
-
if (validation?.isValidating) {
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Recalculate issue counts including API errors
|
|
583
|
-
const calculateIssueCounts = () => {
|
|
584
|
-
if (!validation || typeof validation.getAllIssues !== 'function') {
|
|
585
|
-
return {
|
|
586
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
const allIssues = validation.getAllIssues();
|
|
590
|
-
const ISSUE_SOURCES = {
|
|
591
|
-
HTMLHINT: 'htmlhint',
|
|
592
|
-
CSS_VALIDATOR: 'css-validator',
|
|
593
|
-
CUSTOM: 'custom',
|
|
594
|
-
SECURITY: 'security',
|
|
595
|
-
LIQUID: 'liquid-validator',
|
|
596
|
-
};
|
|
597
|
-
const LABEL_ISSUE_PATTERNS = [
|
|
598
|
-
'tag must be paired',
|
|
599
|
-
'open tag match failed',
|
|
600
|
-
'closed tag match failed',
|
|
601
|
-
'unclosed',
|
|
602
|
-
'missing required',
|
|
603
|
-
'tag-pair',
|
|
604
|
-
'attr-value-not-empty',
|
|
605
|
-
'attr-no-duplication',
|
|
606
|
-
'tag-self-close',
|
|
607
|
-
'spec-char-escape',
|
|
608
|
-
'tagname-lowercase',
|
|
609
|
-
'attr-lowercase',
|
|
610
|
-
'src-not-empty',
|
|
611
|
-
'alt-require',
|
|
612
|
-
];
|
|
613
|
-
|
|
614
|
-
let htmlCount = 0;
|
|
615
|
-
let labelCount = 0;
|
|
616
|
-
let liquidCount = 0;
|
|
617
|
-
|
|
618
|
-
allIssues.forEach((issue) => {
|
|
619
|
-
const { source, rule, message } = issue;
|
|
620
|
-
const messageLower = (message || '').toLowerCase();
|
|
621
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
622
|
-
|
|
623
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
624
|
-
liquidCount += 1;
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
629
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
630
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
631
|
-
);
|
|
632
|
-
|
|
633
|
-
if (isLabelIssue) {
|
|
634
|
-
labelCount += 1;
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
htmlCount += 1;
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
return {
|
|
642
|
-
html: htmlCount,
|
|
643
|
-
label: labelCount,
|
|
644
|
-
liquid: liquidCount,
|
|
645
|
-
total: allIssues.length,
|
|
646
|
-
};
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
const issueCounts = calculateIssueCounts();
|
|
650
|
-
const isContentEmpty = !currentContent || currentContent.trim() === '';
|
|
651
|
-
|
|
652
|
-
const newState = {
|
|
653
|
-
isContentEmpty,
|
|
654
|
-
issueCounts,
|
|
655
|
-
validationComplete: true,
|
|
656
|
-
hasErrors: issueCounts.total > 0,
|
|
657
|
-
};
|
|
658
220
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|| lastState.issueCounts?.label !== newState.issueCounts?.label
|
|
665
|
-
|| lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
|
|
666
|
-
|
|
667
|
-
if (hasChanged) {
|
|
668
|
-
lastSentValidationStateRef.current = newState;
|
|
669
|
-
onValidationChangeRef.current(newState);
|
|
670
|
-
}
|
|
671
|
-
}, [apiValidationErrors, validation, currentContent, onValidationChangeRef]); // Trigger when API errors change
|
|
672
|
-
|
|
673
|
-
// Handle label insertion at cursor position
|
|
674
|
-
// Note: This is called for notification purposes only when tag is inserted via CodeEditorPane
|
|
675
|
-
// The actual insertion happens in CodeEditorPane.handleTagSelect
|
|
676
|
-
const handleLabelInsert = useCallback((label, position) => {
|
|
677
|
-
// If position is explicitly null, it means the editor wasn't ready when tag was selected
|
|
678
|
-
// In this case, CodeEditorPane couldn't insert the tag, so we should try here
|
|
679
|
-
if (position === null) {
|
|
680
|
-
// With injectIntl({ forwardRef: true }), ref points directly to CodeEditorPane
|
|
681
|
-
const activeEditorRef = getActiveEditorRef();
|
|
682
|
-
const editor = activeEditorRef.current;
|
|
683
|
-
|
|
684
|
-
if (!editor) {
|
|
685
|
-
CapNotification.warning({
|
|
686
|
-
message: intl.formatMessage(messages.labelInsertError),
|
|
687
|
-
description: intl.formatMessage(messages.editorNotReady),
|
|
688
|
-
duration: 3,
|
|
689
|
-
});
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// Check if the required methods exist
|
|
694
|
-
if (typeof editor?.insertText !== 'function') {
|
|
695
|
-
CapNotification.error({
|
|
696
|
-
message: intl.formatMessage(messages.labelInsertError),
|
|
697
|
-
description: intl.formatMessage(messages.editorMethodNotAvailable),
|
|
698
|
-
duration: 4,
|
|
699
|
-
});
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
// Get current cursor position
|
|
705
|
-
const cursor = typeof editor?.getCursor === 'function' ? editor.getCursor() : 0;
|
|
221
|
+
try {
|
|
222
|
+
// Get current cursor position or use provided position
|
|
223
|
+
const cursor = position !== undefined
|
|
224
|
+
? position
|
|
225
|
+
: (typeof editor?.getCursor === 'function' ? editor.getCursor() : 0);
|
|
706
226
|
|
|
707
|
-
|
|
708
|
-
|
|
227
|
+
// Insert label at cursor position
|
|
228
|
+
editor.insertText(label, cursor);
|
|
709
229
|
|
|
710
|
-
|
|
711
|
-
|
|
230
|
+
// Focus the editor if focus method is available
|
|
231
|
+
editor?.focus?.();
|
|
712
232
|
|
|
713
|
-
|
|
714
|
-
CapNotification.success({
|
|
715
|
-
message: intl.formatMessage(messages.labelInserted),
|
|
716
|
-
description: intl.formatMessage(messages.labelInsertedDescription, { label }),
|
|
717
|
-
duration: 2,
|
|
718
|
-
});
|
|
719
|
-
} catch (error) {
|
|
720
|
-
CapNotification.error({
|
|
721
|
-
message: intl.formatMessage(messages.labelInsertError),
|
|
722
|
-
description: error.message,
|
|
723
|
-
duration: 4,
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
} else {
|
|
727
|
-
// Tag was already inserted by CodeEditorPane (position is a valid number)
|
|
728
|
-
// Just show success notification - no need to access editor
|
|
233
|
+
// Show success notification
|
|
729
234
|
CapNotification.success({
|
|
730
235
|
message: intl.formatMessage(messages.labelInserted),
|
|
731
236
|
description: intl.formatMessage(messages.labelInsertedDescription, { label }),
|
|
732
|
-
duration: 2
|
|
237
|
+
duration: 2
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
CapNotification.error({
|
|
241
|
+
message: intl.formatMessage(messages.labelInsertError),
|
|
242
|
+
description: error.message,
|
|
243
|
+
duration: 4
|
|
733
244
|
});
|
|
734
245
|
}
|
|
735
246
|
}, [intl, getActiveEditorRef]);
|
|
@@ -748,13 +259,13 @@ const HTMLEditor = forwardRef(({
|
|
|
748
259
|
|
|
749
260
|
CapNotification.success({
|
|
750
261
|
message: intl.formatMessage(messages.contentSaved),
|
|
751
|
-
duration: 2
|
|
262
|
+
duration: 2
|
|
752
263
|
});
|
|
753
264
|
} catch (error) {
|
|
754
265
|
CapNotification.error({
|
|
755
266
|
message: intl.formatMessage(messages.saveError),
|
|
756
267
|
description: error.message,
|
|
757
|
-
duration: 4
|
|
268
|
+
duration: 4
|
|
758
269
|
});
|
|
759
270
|
}
|
|
760
271
|
}, [content, onSave, intl, markAsSaved]);
|
|
@@ -765,27 +276,21 @@ const HTMLEditor = forwardRef(({
|
|
|
765
276
|
const editorInstance = activeEditorRef.current;
|
|
766
277
|
const { line, column = 1 } = error || {};
|
|
767
278
|
|
|
768
|
-
|
|
769
|
-
// This enables the buttons even if we can't navigate to a specific line
|
|
770
|
-
if (onErrorAcknowledged) {
|
|
771
|
-
onErrorAcknowledged();
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
if (editorInstance) {
|
|
279
|
+
if (editorInstance && line) {
|
|
775
280
|
try {
|
|
776
|
-
//
|
|
777
|
-
if (
|
|
281
|
+
// Access the CodeMirror view through the exposed ref methods
|
|
282
|
+
if (editorInstance?.navigateToLine) {
|
|
778
283
|
editorInstance.navigateToLine(line, column);
|
|
779
284
|
} else {
|
|
780
|
-
// For API errors without line numbers, just focus the editor
|
|
781
285
|
editorInstance?.focus?.();
|
|
286
|
+
// Fallback: just focus the editor if navigation isn't available
|
|
782
287
|
}
|
|
783
288
|
} catch (err) {
|
|
784
289
|
// Fallback: just focus the editor
|
|
785
290
|
editorInstance?.focus?.();
|
|
786
291
|
}
|
|
787
292
|
}
|
|
788
|
-
}, [getActiveEditorRef
|
|
293
|
+
}, [getActiveEditorRef]);
|
|
789
294
|
|
|
790
295
|
// Handle fullscreen modal
|
|
791
296
|
const handleOpenFullscreen = useCallback(() => {
|
|
@@ -802,7 +307,6 @@ const HTMLEditor = forwardRef(({
|
|
|
802
307
|
content,
|
|
803
308
|
layout,
|
|
804
309
|
validation,
|
|
805
|
-
isLiquidEnabled,
|
|
806
310
|
editorRef: getActiveEditorRef(),
|
|
807
311
|
handleLabelInsert,
|
|
808
312
|
handleSave,
|
|
@@ -815,14 +319,13 @@ const HTMLEditor = forwardRef(({
|
|
|
815
319
|
switchDevice,
|
|
816
320
|
toggleContentSync,
|
|
817
321
|
getDeviceContent,
|
|
818
|
-
layoutType
|
|
819
|
-
})
|
|
322
|
+
layoutType
|
|
323
|
+
})
|
|
820
324
|
}), [
|
|
821
325
|
variant,
|
|
822
326
|
content,
|
|
823
327
|
layout,
|
|
824
328
|
validation,
|
|
825
|
-
isLiquidEnabled,
|
|
826
329
|
getActiveEditorRef,
|
|
827
330
|
handleLabelInsert,
|
|
828
331
|
handleSave,
|
|
@@ -833,7 +336,7 @@ const HTMLEditor = forwardRef(({
|
|
|
833
336
|
switchDevice,
|
|
834
337
|
toggleContentSync,
|
|
835
338
|
getDeviceContent,
|
|
836
|
-
layoutType
|
|
339
|
+
layoutType
|
|
837
340
|
]);
|
|
838
341
|
|
|
839
342
|
// Loading state
|
|
@@ -845,14 +348,9 @@ const HTMLEditor = forwardRef(({
|
|
|
845
348
|
);
|
|
846
349
|
}
|
|
847
350
|
|
|
848
|
-
// Add library-mode class when not in full mode
|
|
849
|
-
// Note: isFullMode defaults to true, so library mode is when isFullMode === false
|
|
850
|
-
const isLibraryMode = isFullMode === false;
|
|
851
|
-
const editorClassName = `html-editor html-editor--${variant} ${isLibraryMode ? 'html-editor--library-mode' : ''} ${className}`.trim();
|
|
852
|
-
|
|
853
351
|
return (
|
|
854
352
|
<EditorProvider value={contextValue}>
|
|
855
|
-
<div className={
|
|
353
|
+
<div className={`html-editor html-editor--${variant} ${className}`} {...props}>
|
|
856
354
|
{/* Editor Toolbar - Conditional based on variant */}
|
|
857
355
|
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
858
356
|
<EditorToolbar
|
|
@@ -889,23 +387,19 @@ const HTMLEditor = forwardRef(({
|
|
|
889
387
|
ref={mainEditorRef}
|
|
890
388
|
readOnly={readOnly}
|
|
891
389
|
onLabelInsert={handleLabelInsert}
|
|
892
|
-
onErrorClick={handleValidationErrorClick}
|
|
893
|
-
tags={tags}
|
|
894
|
-
injectedTags={injectedTags}
|
|
895
|
-
location={location}
|
|
896
|
-
eventContextTags={eventContextTags}
|
|
897
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
898
|
-
channel={channel}
|
|
899
|
-
userLocale={userLocale}
|
|
900
|
-
moduleFilterEnabled={moduleFilterEnabled}
|
|
901
|
-
onTagContextChange={onTagContextChange}
|
|
902
|
-
onTagSelect={onTagSelect}
|
|
903
|
-
onContextChange={handleContextChange}
|
|
904
390
|
/>
|
|
905
391
|
|
|
906
392
|
{/* Preview Pane */}
|
|
907
393
|
<PreviewPane />
|
|
908
394
|
</SplitContainer>
|
|
395
|
+
|
|
396
|
+
{/* Validation Display - Full Width Below Split Container */}
|
|
397
|
+
<ValidationErrorDisplay
|
|
398
|
+
validation={validation}
|
|
399
|
+
onErrorClick={handleValidationErrorClick}
|
|
400
|
+
variant={variant}
|
|
401
|
+
className="html-editor-validation"
|
|
402
|
+
/>
|
|
909
403
|
</CapRow>
|
|
910
404
|
|
|
911
405
|
{/* Fullscreen Modal */}
|
|
@@ -917,17 +411,17 @@ const HTMLEditor = forwardRef(({
|
|
|
917
411
|
maskClosable={false}
|
|
918
412
|
centered
|
|
919
413
|
closable={false}
|
|
920
|
-
width="90vw"
|
|
414
|
+
width={"90vw"}
|
|
921
415
|
className="html-editor-fullscreen-modal"
|
|
922
416
|
>
|
|
923
|
-
<CapRow className=
|
|
417
|
+
<CapRow className="html-editor-fullscreen">
|
|
924
418
|
{/* Editor Toolbar - Conditional based on variant */}
|
|
925
419
|
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
926
420
|
<EditorToolbar
|
|
927
|
-
showFullscreenButton // Show fullscreen button in modal to allow closing
|
|
421
|
+
showFullscreenButton={true} // Show fullscreen button in modal to allow closing
|
|
928
422
|
onLabelInsert={handleLabelInsert}
|
|
929
423
|
onSave={handleSave}
|
|
930
|
-
isFullscreenMode
|
|
424
|
+
isFullscreenMode={true}
|
|
931
425
|
onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
|
|
932
426
|
/>
|
|
933
427
|
) : (
|
|
@@ -940,10 +434,10 @@ const HTMLEditor = forwardRef(({
|
|
|
940
434
|
onKeepContentSameChange={toggleContentSync}
|
|
941
435
|
/>
|
|
942
436
|
<EditorToolbar
|
|
943
|
-
showFullscreenButton // Show fullscreen button in modal to allow closing
|
|
437
|
+
showFullscreenButton={true} // Show fullscreen button in modal to allow closing
|
|
944
438
|
onLabelInsert={handleLabelInsert}
|
|
945
439
|
onSave={handleSave}
|
|
946
|
-
isFullscreenMode
|
|
440
|
+
isFullscreenMode={true}
|
|
947
441
|
onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
|
|
948
442
|
variant={variant}
|
|
949
443
|
showTitle={false} // Hide title in InApp variant
|
|
@@ -958,30 +452,28 @@ const HTMLEditor = forwardRef(({
|
|
|
958
452
|
<CodeEditorPane
|
|
959
453
|
ref={modalEditorRef}
|
|
960
454
|
readOnly={readOnly}
|
|
961
|
-
isFullscreenMode
|
|
455
|
+
isFullscreenMode={true}
|
|
962
456
|
onLabelInsert={handleLabelInsert}
|
|
963
|
-
onErrorClick={handleValidationErrorClick}
|
|
964
|
-
tags={tags}
|
|
965
|
-
injectedTags={injectedTags}
|
|
966
|
-
location={location}
|
|
967
|
-
eventContextTags={eventContextTags}
|
|
968
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
969
|
-
channel={channel}
|
|
970
|
-
userLocale={userLocale}
|
|
971
|
-
moduleFilterEnabled={moduleFilterEnabled}
|
|
972
|
-
onTagContextChange={onTagContextChange}
|
|
973
457
|
/>
|
|
974
458
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
459
|
+
{/* Preview Pane */}
|
|
460
|
+
<PreviewPane isFullscreenMode={true} isModalContext={true} />
|
|
461
|
+
</SplitContainer>
|
|
462
|
+
|
|
463
|
+
{/* Validation Display in Modal */}
|
|
464
|
+
<ValidationErrorDisplay
|
|
465
|
+
validation={validation}
|
|
466
|
+
onErrorClick={handleValidationErrorClick}
|
|
467
|
+
variant={variant}
|
|
468
|
+
className="html-editor-validation"
|
|
469
|
+
/>
|
|
978
470
|
</CapRow>
|
|
979
471
|
</CapRow>
|
|
980
472
|
</CapModal>
|
|
981
473
|
</div>
|
|
982
474
|
</EditorProvider>
|
|
983
475
|
);
|
|
984
|
-
}
|
|
476
|
+
};
|
|
985
477
|
|
|
986
478
|
HTMLEditor.propTypes = {
|
|
987
479
|
intl: intlShape.isRequired,
|
|
@@ -989,7 +481,7 @@ HTMLEditor.propTypes = {
|
|
|
989
481
|
layoutType: PropTypes.string, // Layout type for InApp variant
|
|
990
482
|
initialContent: PropTypes.oneOfType([
|
|
991
483
|
PropTypes.string,
|
|
992
|
-
PropTypes.objectOf(PropTypes.string)
|
|
484
|
+
PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
|
|
993
485
|
]),
|
|
994
486
|
onSave: PropTypes.func,
|
|
995
487
|
onContentChange: PropTypes.func,
|
|
@@ -997,33 +489,11 @@ HTMLEditor.propTypes = {
|
|
|
997
489
|
readOnly: PropTypes.bool,
|
|
998
490
|
showFullscreenButton: PropTypes.bool,
|
|
999
491
|
autoSave: PropTypes.bool,
|
|
1000
|
-
autoSaveInterval: PropTypes.number
|
|
1001
|
-
// Tag-related props - tags are fetched and managed by parent component
|
|
1002
|
-
tags: PropTypes.array,
|
|
1003
|
-
injectedTags: PropTypes.object,
|
|
1004
|
-
location: PropTypes.object,
|
|
1005
|
-
eventContextTags: PropTypes.array,
|
|
1006
|
-
selectedOfferDetails: PropTypes.array,
|
|
1007
|
-
channel: PropTypes.string,
|
|
1008
|
-
userLocale: PropTypes.string,
|
|
1009
|
-
moduleFilterEnabled: PropTypes.bool,
|
|
1010
|
-
onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
|
|
1011
|
-
onTagSelect: PropTypes.func,
|
|
1012
|
-
onContextChange: PropTypes.func, // Deprecated: use globalActions instead
|
|
1013
|
-
globalActions: PropTypes.object,
|
|
1014
|
-
isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
|
|
1015
|
-
isFullMode: PropTypes.bool, // Full mode vs library mode
|
|
1016
|
-
onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
|
|
1017
|
-
onValidationChange: PropTypes.func, // Callback when validation state changes
|
|
1018
|
-
apiValidationErrors: PropTypes.shape({
|
|
1019
|
-
liquidErrors: PropTypes.arrayOf(PropTypes.string),
|
|
1020
|
-
standardErrors: PropTypes.arrayOf(PropTypes.string),
|
|
1021
|
-
}), // API validation errors from validateLiquidTemplateContent
|
|
492
|
+
autoSaveInterval: PropTypes.number
|
|
1022
493
|
};
|
|
1023
494
|
|
|
1024
495
|
HTMLEditor.defaultProps = {
|
|
1025
496
|
variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
|
|
1026
|
-
layoutType: null,
|
|
1027
497
|
initialContent: null, // Will use default from useEditorContent hook
|
|
1028
498
|
onSave: null,
|
|
1029
499
|
onContentChange: null,
|
|
@@ -1031,26 +501,8 @@ HTMLEditor.defaultProps = {
|
|
|
1031
501
|
readOnly: false,
|
|
1032
502
|
showFullscreenButton: true,
|
|
1033
503
|
autoSave: true,
|
|
1034
|
-
autoSaveInterval: 30000
|
|
1035
|
-
// Tag-related defaults - tags are fetched and managed by parent component
|
|
1036
|
-
tags: [],
|
|
1037
|
-
injectedTags: {},
|
|
1038
|
-
location: null,
|
|
1039
|
-
eventContextTags: [],
|
|
1040
|
-
selectedOfferDetails: [],
|
|
1041
|
-
channel: null,
|
|
1042
|
-
userLocale: 'en',
|
|
1043
|
-
moduleFilterEnabled: true,
|
|
1044
|
-
onTagContextChange: null, // Parent component should provide this
|
|
1045
|
-
onTagSelect: null,
|
|
1046
|
-
onContextChange: null,
|
|
1047
|
-
globalActions: null, // Redux actions for API calls
|
|
1048
|
-
isLiquidEnabled: false,
|
|
1049
|
-
isFullMode: true, // Default to full mode
|
|
1050
|
-
onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
|
|
1051
|
-
onValidationChange: null, // Callback when validation state changes
|
|
1052
|
-
apiValidationErrors: null, // API validation errors from validateLiquidTemplateContent
|
|
504
|
+
autoSaveInterval: 30000
|
|
1053
505
|
};
|
|
1054
506
|
|
|
1055
|
-
// Export with
|
|
1056
|
-
export default injectIntl(HTMLEditor);
|
|
507
|
+
// Export with forwardRef to allow direct access to CodeEditorPane via ref
|
|
508
|
+
export default injectIntl(HTMLEditor, { forwardRef: true });
|