@capillarytech/creatives-library 8.0.259 → 8.0.260-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 +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 -34
- package/translations/en.json +3 -4
- package/utils/common.js +0 -12
- 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 -457
- package/v2Components/ErrorInfoNote/messages.js +6 -36
- package/v2Components/ErrorInfoNote/style.scss +6 -282
- package/v2Components/FormBuilder/index.js +4 -4
- package/v2Components/FormBuilder/tests/index.test.js +4 -13
- package/v2Components/HtmlEditor/HTMLEditor.js +94 -547
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1441
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +16 -27
- package/v2Components/HtmlEditor/_htmlEditor.scss +45 -108
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +102 -23
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +140 -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 +4 -4
- 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 +43 -22
- 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/_validationErrorDisplay.scss +0 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +34 -50
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +41 -70
- package/v2Components/HtmlEditor/constants.js +20 -42
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +16 -120
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
- package/v2Components/HtmlEditor/hooks/useValidation.js +53 -189
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +94 -92
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +45 -94
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +0 -134
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +41 -40
- package/v2Components/HtmlEditor/utils/htmlValidator.js +72 -71
- 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 -55
- package/v2Components/TemplatePreview/index.js +32 -47
- package/v2Components/TemplatePreview/messages.js +0 -4
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +0 -1
- package/v2Containers/BeeEditor/index.js +90 -172
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +3 -4
- package/v2Containers/CreativesContainer/SlideBoxContent.js +52 -128
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -163
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -2
- package/v2Containers/CreativesContainer/constants.js +0 -1
- package/v2Containers/CreativesContainer/index.js +46 -240
- 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 -106
- package/v2Containers/Email/actions.js +0 -7
- package/v2Containers/Email/constants.js +1 -5
- package/v2Containers/Email/index.js +30 -239
- 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/reducer.test.js +0 -46
- package/v2Containers/Email/tests/sagas.test.js +29 -320
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +21 -211
- 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 -65
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -594
- package/v2Containers/InApp/actions.js +0 -7
- package/v2Containers/InApp/constants.js +4 -20
- package/v2Containers/InApp/index.js +360 -804
- 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 +71 -152
- 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 +12 -39
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +6 -10
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +75 -102
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +54 -81
- package/v2Containers/MobilePushNew/index.js +2 -3
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +178 -262
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +12 -16
- package/v2Containers/SmsTrai/Edit/index.js +1 -2
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +111 -468
- 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/WebPush/Create/messages.js +8 -0
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +2 -2
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +3 -1
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +5 -1
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +5 -1
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +5 -1
- package/v2Containers/WebPush/Create/preview/preview.scss +7 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +734 -1306
- package/v2Components/ErrorInfoNote/constants.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -874
- package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +0 -6
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -255
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -364
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
- package/v2Components/HtmlEditor/utils/validationConstants.js +0 -40
- package/v2Containers/BeePopupEditor/_beePopupEditor.scss +0 -14
- package/v2Containers/BeePopupEditor/constants.js +0 -10
- package/v2Containers/BeePopupEditor/index.js +0 -194
- package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1285
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -1880
- 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 -151
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -23
- 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,10 +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';
|
|
45
|
-
import { ISSUE_SOURCES, LABEL_ISSUE_PATTERNS } from './utils/validationConstants';
|
|
43
|
+
import { HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT } from './constants';
|
|
46
44
|
|
|
47
45
|
// Styles
|
|
48
46
|
import './_htmlEditor.scss';
|
|
@@ -51,7 +49,7 @@ import './components/FullscreenModal/_fullscreenModal.scss';
|
|
|
51
49
|
// Messages
|
|
52
50
|
import messages from './messages';
|
|
53
51
|
|
|
54
|
-
const HTMLEditor =
|
|
52
|
+
const HTMLEditor = ({
|
|
55
53
|
intl,
|
|
56
54
|
variant = HTML_EDITOR_VARIANTS.EMAIL, // New prop: 'email' or 'inapp'
|
|
57
55
|
layoutType, // Layout type for InApp variant
|
|
@@ -63,33 +61,17 @@ const HTMLEditor = forwardRef(({
|
|
|
63
61
|
showFullscreenButton = true,
|
|
64
62
|
autoSave = true,
|
|
65
63
|
autoSaveInterval = 30000, // 30 seconds
|
|
66
|
-
// Tag-related props - tags are fetched and managed by parent component (EmailHTMLEditor, INAPP, etc.)
|
|
67
|
-
tags = [],
|
|
68
|
-
injectedTags = {},
|
|
69
|
-
location,
|
|
70
|
-
eventContextTags = [],
|
|
71
|
-
selectedOfferDetails = [],
|
|
72
|
-
channel,
|
|
73
|
-
userLocale = 'en',
|
|
74
|
-
moduleFilterEnabled = true,
|
|
75
|
-
onTagContextChange, // Parent component handles tag fetching
|
|
76
|
-
onTagSelect = null,
|
|
77
|
-
onContextChange = null,
|
|
78
|
-
globalActions = null,
|
|
79
|
-
isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
|
|
80
|
-
isFullMode = true, // Full mode vs library mode - controls layout and visibility
|
|
81
|
-
onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
|
|
82
|
-
onValidationChange = null, // Callback when validation state changes (for parent to track errors)
|
|
83
|
-
apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent { liquidErrors: [], standardErrors: [] }
|
|
84
64
|
...props
|
|
85
|
-
}
|
|
65
|
+
}) => {
|
|
86
66
|
// Separate refs for main and modal editors to avoid conflicts
|
|
87
67
|
const mainEditorRef = useRef(null);
|
|
88
68
|
const modalEditorRef = useRef(null);
|
|
89
69
|
const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
|
|
90
70
|
|
|
91
71
|
// Get the currently active editor ref based on fullscreen state
|
|
92
|
-
const getActiveEditorRef = useCallback(() =>
|
|
72
|
+
const getActiveEditorRef = useCallback(() => {
|
|
73
|
+
return isFullscreenModalOpen ? modalEditorRef : mainEditorRef;
|
|
74
|
+
}, [isFullscreenModalOpen]);
|
|
93
75
|
|
|
94
76
|
// Initialize custom hooks for state management - always call both hooks to follow Rules of Hooks
|
|
95
77
|
const isEmailVariant = variant === HTML_EDITOR_VARIANTS.EMAIL;
|
|
@@ -98,7 +80,7 @@ const HTMLEditor = forwardRef(({
|
|
|
98
80
|
autoSave: isEmailVariant ? autoSave : false,
|
|
99
81
|
autoSaveInterval,
|
|
100
82
|
onSave: isEmailVariant ? onSave : null,
|
|
101
|
-
onChange: isEmailVariant ? onContentChange : null
|
|
83
|
+
onChange: isEmailVariant ? onContentChange : null
|
|
102
84
|
};
|
|
103
85
|
|
|
104
86
|
const emailContent = useEditorContent(
|
|
@@ -115,7 +97,7 @@ const HTMLEditor = forwardRef(({
|
|
|
115
97
|
// Convert string content to device-specific format
|
|
116
98
|
inAppInitialContent = {
|
|
117
99
|
[DEVICE_TYPES.ANDROID]: initialContent,
|
|
118
|
-
[DEVICE_TYPES.IOS]: initialContent
|
|
100
|
+
[DEVICE_TYPES.IOS]: initialContent
|
|
119
101
|
};
|
|
120
102
|
} else {
|
|
121
103
|
// Use provided device-specific content
|
|
@@ -127,7 +109,7 @@ const HTMLEditor = forwardRef(({
|
|
|
127
109
|
autoSave: isInAppVariant ? autoSave : false,
|
|
128
110
|
autoSaveInterval,
|
|
129
111
|
onSave: isInAppVariant ? onSave : null,
|
|
130
|
-
onChange: isInAppVariant ? onContentChange : null
|
|
112
|
+
onChange: isInAppVariant ? onContentChange : null
|
|
131
113
|
};
|
|
132
114
|
|
|
133
115
|
const inAppContent = useInAppContent(inAppInitialContent, inAppOptions);
|
|
@@ -135,64 +117,6 @@ const HTMLEditor = forwardRef(({
|
|
|
135
117
|
// Use appropriate content hook based on variant
|
|
136
118
|
const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
|
|
137
119
|
|
|
138
|
-
// Update content when initialContent prop changes (for edit mode)
|
|
139
|
-
// This ensures the editor updates when template data loads
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
if (isEmailVariant && emailContent && initialContent !== undefined && initialContent !== null) {
|
|
142
|
-
// Only update if content is different to avoid unnecessary updates
|
|
143
|
-
if (emailContent.content !== initialContent) {
|
|
144
|
-
emailContent.updateContent(initialContent, true); // immediate update
|
|
145
|
-
}
|
|
146
|
-
} else if (isInAppVariant && inAppContent && initialContent !== undefined && initialContent !== null) {
|
|
147
|
-
// Handle InApp variant updates
|
|
148
|
-
const contentToUpdate = typeof initialContent === 'string'
|
|
149
|
-
? { [DEVICE_TYPES.ANDROID]: initialContent, [DEVICE_TYPES.IOS]: initialContent }
|
|
150
|
-
: initialContent;
|
|
151
|
-
if (inAppContent.updateContent) {
|
|
152
|
-
const currentContent = inAppContent.getDeviceContent?.(inAppContent.activeDevice);
|
|
153
|
-
const newContent = contentToUpdate[inAppContent.activeDevice] || contentToUpdate[DEVICE_TYPES.ANDROID] || '';
|
|
154
|
-
if (currentContent !== newContent) {
|
|
155
|
-
inAppContent.updateContent(newContent, true);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}, [initialContent, isEmailVariant, isInAppVariant]);
|
|
160
|
-
// Handle context change for tag API calls
|
|
161
|
-
// If variant is INAPP, use SMS layout; otherwise use the channel (EMAIL)
|
|
162
|
-
const handleContextChange = useCallback((contextData) => {
|
|
163
|
-
// If onContextChange is provided, use it instead of making our own API call
|
|
164
|
-
// This prevents duplicate API calls when parent component handles tag fetching
|
|
165
|
-
if (onContextChange) {
|
|
166
|
-
onContextChange(contextData);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Only make API call if onContextChange is not provided and globalActions is available
|
|
171
|
-
if (!globalActions || !location) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const { type } = location.query || {};
|
|
176
|
-
const tempData = (contextData || '').toLowerCase();
|
|
177
|
-
const isEmbedded = type === EMBEDDED;
|
|
178
|
-
const embedded = isEmbedded ? type : FULL;
|
|
179
|
-
const context = tempData === ALL ? DEFAULT : tempData;
|
|
180
|
-
|
|
181
|
-
// Determine layout: INAPP variant uses SMS, EMAIL variant uses EMAIL
|
|
182
|
-
const layout = variant === HTML_EDITOR_VARIANTS.INAPP ? SMS : EMAIL;
|
|
183
|
-
|
|
184
|
-
const query = {
|
|
185
|
-
layout,
|
|
186
|
-
type: TAG,
|
|
187
|
-
context,
|
|
188
|
-
embedded,
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
// Call the API via Redux action - this will trigger the saga which calls Api.fetchSchemaForEntity
|
|
192
|
-
// The API endpoint will be: /meta/TAG?query={...}
|
|
193
|
-
globalActions.fetchSchemaForEntity(query);
|
|
194
|
-
}, [variant, globalActions, location, onContextChange]);
|
|
195
|
-
|
|
196
120
|
// Destructure content properties for cleaner access throughout component
|
|
197
121
|
const {
|
|
198
122
|
activeDevice,
|
|
@@ -200,14 +124,14 @@ const HTMLEditor = forwardRef(({
|
|
|
200
124
|
switchDevice,
|
|
201
125
|
toggleContentSync,
|
|
202
126
|
getDeviceContent,
|
|
203
|
-
markAsSaved
|
|
127
|
+
markAsSaved
|
|
204
128
|
} = content || {};
|
|
205
129
|
|
|
206
130
|
const layout = useLayoutState({
|
|
207
131
|
splitSizes: [50, 50],
|
|
208
132
|
viewMode: 'desktop',
|
|
209
133
|
mobileWidth: 375,
|
|
210
|
-
isFullscreen: false
|
|
134
|
+
isFullscreen: false
|
|
211
135
|
});
|
|
212
136
|
|
|
213
137
|
// Get current content for validation based on variant
|
|
@@ -234,7 +158,7 @@ const HTMLEditor = forwardRef(({
|
|
|
234
158
|
'sanitizer.productionValidHtml': messages.sanitizer.productionValidHtml,
|
|
235
159
|
'sanitizer.productionSanitized': messages.sanitizer.productionSanitized,
|
|
236
160
|
'sanitizer.productionInlineCss': messages.sanitizer.productionInlineCss,
|
|
237
|
-
'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
|
|
161
|
+
'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
|
|
238
162
|
};
|
|
239
163
|
|
|
240
164
|
const messageObj = messageMap[messageKey];
|
|
@@ -255,7 +179,7 @@ const HTMLEditor = forwardRef(({
|
|
|
255
179
|
'validator.largeImageDetected': messages.validator.largeImageDetected,
|
|
256
180
|
'validator.unclosedCssRule': messages.validator.unclosedCssRule,
|
|
257
181
|
'validator.emptyCssRule': messages.validator.emptyCssRule,
|
|
258
|
-
'validator.cssValidationFailed': messages.validator.cssValidationFailed
|
|
182
|
+
'validator.cssValidationFailed': messages.validator.cssValidationFailed
|
|
259
183
|
};
|
|
260
184
|
|
|
261
185
|
const messageObj = messageMap[messageKey];
|
|
@@ -266,375 +190,57 @@ const HTMLEditor = forwardRef(({
|
|
|
266
190
|
enableRealTime: true,
|
|
267
191
|
debounceMs: 500,
|
|
268
192
|
enableSanitization: true,
|
|
269
|
-
securityLevel: 'standard'
|
|
270
|
-
apiValidationErrors, // Pass API validation errors to merge with client-side validation
|
|
193
|
+
securityLevel: 'standard'
|
|
271
194
|
}, formatSanitizerMessage, formatValidatorMessage);
|
|
272
195
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
getIssueCounts: () => {
|
|
279
|
-
// Check if validation is ready and has getAllIssues method
|
|
280
|
-
if (!validation || typeof validation.getAllIssues !== 'function') {
|
|
281
|
-
return {
|
|
282
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
const allIssues = validation.getAllIssues();
|
|
286
|
-
|
|
287
|
-
let htmlCount = 0;
|
|
288
|
-
let labelCount = 0;
|
|
289
|
-
let liquidCount = 0;
|
|
290
|
-
|
|
291
|
-
allIssues.forEach((issue) => {
|
|
292
|
-
const { source, rule, message } = issue;
|
|
293
|
-
const messageLower = (message || '').toLowerCase();
|
|
294
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
295
|
-
|
|
296
|
-
// Check if it's a Liquid issue
|
|
297
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
298
|
-
liquidCount++;
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Check if it's a Label (tag syntax) issue
|
|
303
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
304
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
305
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
if (isLabelIssue) {
|
|
309
|
-
labelCount++;
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
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;
|
|
312
201
|
|
|
313
|
-
|
|
314
|
-
|
|
202
|
+
if (!editor) {
|
|
203
|
+
CapNotification.warning({
|
|
204
|
+
message: intl.formatMessage(messages.labelInsertError),
|
|
205
|
+
description: intl.formatMessage(messages.editorNotReady),
|
|
206
|
+
duration: 3
|
|
315
207
|
});
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
html: htmlCount,
|
|
319
|
-
label: labelCount,
|
|
320
|
-
liquid: liquidCount,
|
|
321
|
-
total: allIssues.length,
|
|
322
|
-
};
|
|
323
|
-
},
|
|
324
|
-
getValidationState: () => ({
|
|
325
|
-
isValidating: validation?.isValidating || false,
|
|
326
|
-
hasErrors: validation?.hasBlockingErrors || false,
|
|
327
|
-
issueCounts: (() => {
|
|
328
|
-
if (!validation || typeof validation.getAllIssues !== 'function') {
|
|
329
|
-
return {
|
|
330
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
const allIssues = validation.getAllIssues();
|
|
334
|
-
// Use same logic as getIssueCounts
|
|
335
|
-
let htmlCount = 0;
|
|
336
|
-
let labelCount = 0;
|
|
337
|
-
let liquidCount = 0;
|
|
338
|
-
|
|
339
|
-
allIssues.forEach((issue) => {
|
|
340
|
-
const { source, rule, message } = issue;
|
|
341
|
-
const messageLower = (message || '').toLowerCase();
|
|
342
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
343
|
-
|
|
344
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
345
|
-
liquidCount++;
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
350
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
351
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
if (isLabelIssue) {
|
|
355
|
-
labelCount++;
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
htmlCount++;
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
return {
|
|
363
|
-
html: htmlCount,
|
|
364
|
-
label: labelCount,
|
|
365
|
-
liquid: liquidCount,
|
|
366
|
-
total: allIssues.length,
|
|
367
|
-
};
|
|
368
|
-
})(),
|
|
369
|
-
})
|
|
370
|
-
,
|
|
371
|
-
}), [validation, currentContent, apiValidationErrors]); // Include apiValidationErrors so ref methods return updated counts
|
|
372
|
-
|
|
373
|
-
// Use ref to store callback to avoid infinite loops (callback in deps would cause re-runs)
|
|
374
|
-
const onValidationChangeRef = useRef(onValidationChange);
|
|
375
|
-
useEffect(() => {
|
|
376
|
-
onValidationChangeRef.current = onValidationChange;
|
|
377
|
-
}, [onValidationChange]);
|
|
378
|
-
|
|
379
|
-
// Track last sent validation state to prevent duplicate updates
|
|
380
|
-
const lastSentValidationStateRef = useRef(null);
|
|
381
|
-
|
|
382
|
-
// Store validation ref to access current value without triggering re-renders
|
|
383
|
-
const validationRef = useRef(validation);
|
|
384
|
-
validationRef.current = validation;
|
|
385
|
-
|
|
386
|
-
// Extract STABLE primitive values from validation for dependency array
|
|
387
|
-
const isValidating = validation?.isValidating;
|
|
388
|
-
const validationTotalErrors = validation?.summary?.totalErrors || 0;
|
|
389
|
-
const validationTotalWarnings = validation?.summary?.totalWarnings || 0;
|
|
390
|
-
const validationLastValidated = validation?.lastValidated?.getTime?.() || null;
|
|
391
|
-
// Only Rule Group #1 (Input & Sanitization) blocks; use for UI gating (Done/Update/Preview/Test)
|
|
392
|
-
const validationHasBlockingErrors = validation?.hasBlockingErrors || false;
|
|
393
|
-
|
|
394
|
-
// Notify parent component when validation state changes
|
|
395
|
-
useEffect(() => {
|
|
396
|
-
if (!onValidationChangeRef.current) {
|
|
397
208
|
return;
|
|
398
209
|
}
|
|
399
210
|
|
|
400
|
-
//
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const calculateIssueCounts = () => {
|
|
407
|
-
const currentValidation = validationRef.current;
|
|
408
|
-
if (!currentValidation || typeof currentValidation.getAllIssues !== 'function') {
|
|
409
|
-
return {
|
|
410
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
const allIssues = currentValidation.getAllIssues();
|
|
414
|
-
|
|
415
|
-
let htmlCount = 0;
|
|
416
|
-
let labelCount = 0;
|
|
417
|
-
let liquidCount = 0;
|
|
418
|
-
|
|
419
|
-
allIssues.forEach((issue) => {
|
|
420
|
-
const { source, rule, message } = issue;
|
|
421
|
-
const messageLower = (message || '').toLowerCase();
|
|
422
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
423
|
-
|
|
424
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
425
|
-
liquidCount += 1;
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
430
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
431
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
if (isLabelIssue) {
|
|
435
|
-
labelCount += 1;
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
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
|
|
440
217
|
});
|
|
441
|
-
|
|
442
|
-
return {
|
|
443
|
-
html: htmlCount,
|
|
444
|
-
label: labelCount,
|
|
445
|
-
liquid: liquidCount,
|
|
446
|
-
total: allIssues.length,
|
|
447
|
-
};
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const issueCounts = calculateIssueCounts();
|
|
451
|
-
const isContentEmpty = !currentContent || currentContent.trim() === '';
|
|
452
|
-
|
|
453
|
-
// hasErrors = only Rule Group #1 (Input & Sanitization) – gates Done/Update/Preview/Test
|
|
454
|
-
const newState = {
|
|
455
|
-
isContentEmpty,
|
|
456
|
-
issueCounts,
|
|
457
|
-
validationComplete: true,
|
|
458
|
-
hasErrors: validationRef.current?.hasBlockingErrors ?? false,
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
// Only call callback if state actually changed (prevent infinite loops)
|
|
462
|
-
const lastState = lastSentValidationStateRef.current;
|
|
463
|
-
const hasChanged = !lastState
|
|
464
|
-
|| lastState.isContentEmpty !== newState.isContentEmpty
|
|
465
|
-
|| lastState.validationComplete !== newState.validationComplete
|
|
466
|
-
|| lastState.hasErrors !== newState.hasErrors
|
|
467
|
-
|| lastState.issueCounts?.total !== newState.issueCounts?.total
|
|
468
|
-
|| lastState.issueCounts?.html !== newState.issueCounts?.html
|
|
469
|
-
|| lastState.issueCounts?.label !== newState.issueCounts?.label
|
|
470
|
-
|| lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
|
|
471
|
-
|
|
472
|
-
if (hasChanged) {
|
|
473
|
-
lastSentValidationStateRef.current = newState;
|
|
474
|
-
onValidationChangeRef.current(newState);
|
|
475
|
-
}
|
|
476
|
-
}, [isValidating, validationTotalErrors, validationTotalWarnings, validationLastValidated, validationHasBlockingErrors, currentContent, apiValidationErrors]);
|
|
477
|
-
|
|
478
|
-
// Send initial state on mount to ensure parent has correct initial button state
|
|
479
|
-
const hasInitializedRef = useRef(false);
|
|
480
|
-
useEffect(() => {
|
|
481
|
-
if (hasInitializedRef.current || !onValidationChangeRef.current) {
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
hasInitializedRef.current = true;
|
|
485
|
-
|
|
486
|
-
// Send initial state with validationComplete=false to indicate validation pending
|
|
487
|
-
const isContentEmpty = !currentContent || currentContent.trim() === '';
|
|
488
|
-
onValidationChangeRef.current({
|
|
489
|
-
isContentEmpty,
|
|
490
|
-
issueCounts: {
|
|
491
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
492
|
-
},
|
|
493
|
-
validationComplete: false, // Validation hasn't run yet
|
|
494
|
-
hasErrors: false,
|
|
495
|
-
});
|
|
496
|
-
}, [currentContent]); // Only depend on currentContent to run on initial content load
|
|
497
|
-
|
|
498
|
-
// Force validation state recalculation when API validation errors change
|
|
499
|
-
// This ensures that API errors are included in issue counts and displayed in ValidationErrorDisplay
|
|
500
|
-
useEffect(() => {
|
|
501
|
-
if (!onValidationChangeRef.current) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Skip if still validating (wait for validation to complete)
|
|
506
|
-
if (validation?.isValidating) {
|
|
507
218
|
return;
|
|
508
219
|
}
|
|
509
220
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
const allIssues = validation.getAllIssues();
|
|
518
|
-
|
|
519
|
-
let htmlCount = 0;
|
|
520
|
-
let labelCount = 0;
|
|
521
|
-
let liquidCount = 0;
|
|
522
|
-
|
|
523
|
-
allIssues.forEach((issue) => {
|
|
524
|
-
const { source, rule, message } = issue;
|
|
525
|
-
const messageLower = (message || '').toLowerCase();
|
|
526
|
-
const ruleLower = (rule || '').toLowerCase();
|
|
527
|
-
|
|
528
|
-
if (source === ISSUE_SOURCES.LIQUID) {
|
|
529
|
-
liquidCount += 1;
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
534
|
-
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
535
|
-
|| ruleLower.includes(pattern.toLowerCase()),
|
|
536
|
-
);
|
|
537
|
-
|
|
538
|
-
if (isLabelIssue) {
|
|
539
|
-
labelCount += 1;
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
htmlCount += 1;
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
html: htmlCount,
|
|
548
|
-
label: labelCount,
|
|
549
|
-
liquid: liquidCount,
|
|
550
|
-
total: allIssues.length,
|
|
551
|
-
};
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
const issueCounts = calculateIssueCounts();
|
|
555
|
-
const isContentEmpty = !currentContent || currentContent.trim() === '';
|
|
556
|
-
|
|
557
|
-
const newState = {
|
|
558
|
-
isContentEmpty,
|
|
559
|
-
issueCounts,
|
|
560
|
-
validationComplete: true,
|
|
561
|
-
hasErrors: validation?.hasBlockingErrors ?? false,
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
const lastState = lastSentValidationStateRef.current;
|
|
565
|
-
const hasChanged = !lastState
|
|
566
|
-
|| lastState.hasErrors !== newState.hasErrors
|
|
567
|
-
|| lastState.issueCounts?.total !== newState.issueCounts?.total
|
|
568
|
-
|| lastState.issueCounts?.html !== newState.issueCounts?.html
|
|
569
|
-
|| lastState.issueCounts?.label !== newState.issueCounts?.label
|
|
570
|
-
|| lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
|
|
571
|
-
|
|
572
|
-
if (hasChanged) {
|
|
573
|
-
lastSentValidationStateRef.current = newState;
|
|
574
|
-
onValidationChangeRef.current(newState);
|
|
575
|
-
}
|
|
576
|
-
}, [apiValidationErrors, validation, currentContent, onValidationChangeRef]);
|
|
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);
|
|
577
226
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
// The actual insertion happens in CodeEditorPane.handleTagSelect
|
|
581
|
-
const handleLabelInsert = useCallback((label, position) => {
|
|
582
|
-
// If position is explicitly null, it means the editor wasn't ready when tag was selected
|
|
583
|
-
// In this case, CodeEditorPane couldn't insert the tag, so we should try here
|
|
584
|
-
if (position === null) {
|
|
585
|
-
// With injectIntl({ forwardRef: true }), ref points directly to CodeEditorPane
|
|
586
|
-
const activeEditorRef = getActiveEditorRef();
|
|
587
|
-
const editor = activeEditorRef.current;
|
|
588
|
-
|
|
589
|
-
if (!editor) {
|
|
590
|
-
CapNotification.warning({
|
|
591
|
-
message: intl.formatMessage(messages.labelInsertError),
|
|
592
|
-
description: intl.formatMessage(messages.editorNotReady),
|
|
593
|
-
duration: 3,
|
|
594
|
-
});
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
227
|
+
// Insert label at cursor position
|
|
228
|
+
editor.insertText(label, cursor);
|
|
597
229
|
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
CapNotification.error({
|
|
601
|
-
message: intl.formatMessage(messages.labelInsertError),
|
|
602
|
-
description: intl.formatMessage(messages.editorMethodNotAvailable),
|
|
603
|
-
duration: 4,
|
|
604
|
-
});
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
230
|
+
// Focus the editor if focus method is available
|
|
231
|
+
editor?.focus?.();
|
|
607
232
|
|
|
608
|
-
|
|
609
|
-
// Get current cursor position
|
|
610
|
-
const cursor = typeof editor?.getCursor === 'function' ? editor.getCursor() : 0;
|
|
611
|
-
|
|
612
|
-
// Insert label at cursor position
|
|
613
|
-
editor.insertText(label, cursor);
|
|
614
|
-
|
|
615
|
-
// Focus the editor if focus method is available
|
|
616
|
-
editor?.focus?.();
|
|
617
|
-
|
|
618
|
-
// Show success notification
|
|
619
|
-
CapNotification.success({
|
|
620
|
-
message: intl.formatMessage(messages.labelInserted),
|
|
621
|
-
description: intl.formatMessage(messages.labelInsertedDescription, { label }),
|
|
622
|
-
duration: 2,
|
|
623
|
-
});
|
|
624
|
-
} catch (error) {
|
|
625
|
-
CapNotification.error({
|
|
626
|
-
message: intl.formatMessage(messages.labelInsertError),
|
|
627
|
-
description: error.message,
|
|
628
|
-
duration: 4,
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
} else {
|
|
632
|
-
// Tag was already inserted by CodeEditorPane (position is a valid number)
|
|
633
|
-
// Just show success notification - no need to access editor
|
|
233
|
+
// Show success notification
|
|
634
234
|
CapNotification.success({
|
|
635
235
|
message: intl.formatMessage(messages.labelInserted),
|
|
636
236
|
description: intl.formatMessage(messages.labelInsertedDescription, { label }),
|
|
637
|
-
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
|
|
638
244
|
});
|
|
639
245
|
}
|
|
640
246
|
}, [intl, getActiveEditorRef]);
|
|
@@ -653,13 +259,13 @@ const HTMLEditor = forwardRef(({
|
|
|
653
259
|
|
|
654
260
|
CapNotification.success({
|
|
655
261
|
message: intl.formatMessage(messages.contentSaved),
|
|
656
|
-
duration: 2
|
|
262
|
+
duration: 2
|
|
657
263
|
});
|
|
658
264
|
} catch (error) {
|
|
659
265
|
CapNotification.error({
|
|
660
266
|
message: intl.formatMessage(messages.saveError),
|
|
661
267
|
description: error.message,
|
|
662
|
-
duration: 4
|
|
268
|
+
duration: 4
|
|
663
269
|
});
|
|
664
270
|
}
|
|
665
271
|
}, [content, onSave, intl, markAsSaved]);
|
|
@@ -670,27 +276,21 @@ const HTMLEditor = forwardRef(({
|
|
|
670
276
|
const editorInstance = activeEditorRef.current;
|
|
671
277
|
const { line, column = 1 } = error || {};
|
|
672
278
|
|
|
673
|
-
|
|
674
|
-
// This enables the buttons even if we can't navigate to a specific line
|
|
675
|
-
if (onErrorAcknowledged) {
|
|
676
|
-
onErrorAcknowledged();
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (editorInstance) {
|
|
279
|
+
if (editorInstance && line) {
|
|
680
280
|
try {
|
|
681
|
-
//
|
|
682
|
-
if (
|
|
281
|
+
// Access the CodeMirror view through the exposed ref methods
|
|
282
|
+
if (editorInstance?.navigateToLine) {
|
|
683
283
|
editorInstance.navigateToLine(line, column);
|
|
684
284
|
} else {
|
|
685
|
-
// For API errors without line numbers, just focus the editor
|
|
686
285
|
editorInstance?.focus?.();
|
|
286
|
+
// Fallback: just focus the editor if navigation isn't available
|
|
687
287
|
}
|
|
688
288
|
} catch (err) {
|
|
689
289
|
// Fallback: just focus the editor
|
|
690
290
|
editorInstance?.focus?.();
|
|
691
291
|
}
|
|
692
292
|
}
|
|
693
|
-
}, [getActiveEditorRef
|
|
293
|
+
}, [getActiveEditorRef]);
|
|
694
294
|
|
|
695
295
|
// Handle fullscreen modal
|
|
696
296
|
const handleOpenFullscreen = useCallback(() => {
|
|
@@ -707,7 +307,6 @@ const HTMLEditor = forwardRef(({
|
|
|
707
307
|
content,
|
|
708
308
|
layout,
|
|
709
309
|
validation,
|
|
710
|
-
isLiquidEnabled,
|
|
711
310
|
editorRef: getActiveEditorRef(),
|
|
712
311
|
handleLabelInsert,
|
|
713
312
|
handleSave,
|
|
@@ -720,14 +319,13 @@ const HTMLEditor = forwardRef(({
|
|
|
720
319
|
switchDevice,
|
|
721
320
|
toggleContentSync,
|
|
722
321
|
getDeviceContent,
|
|
723
|
-
layoutType
|
|
724
|
-
})
|
|
322
|
+
layoutType
|
|
323
|
+
})
|
|
725
324
|
}), [
|
|
726
325
|
variant,
|
|
727
326
|
content,
|
|
728
327
|
layout,
|
|
729
328
|
validation,
|
|
730
|
-
isLiquidEnabled,
|
|
731
329
|
getActiveEditorRef,
|
|
732
330
|
handleLabelInsert,
|
|
733
331
|
handleSave,
|
|
@@ -738,7 +336,7 @@ const HTMLEditor = forwardRef(({
|
|
|
738
336
|
switchDevice,
|
|
739
337
|
toggleContentSync,
|
|
740
338
|
getDeviceContent,
|
|
741
|
-
layoutType
|
|
339
|
+
layoutType
|
|
742
340
|
]);
|
|
743
341
|
|
|
744
342
|
// Loading state
|
|
@@ -750,14 +348,9 @@ const HTMLEditor = forwardRef(({
|
|
|
750
348
|
);
|
|
751
349
|
}
|
|
752
350
|
|
|
753
|
-
// Add library-mode class when not in full mode
|
|
754
|
-
// Note: isFullMode defaults to true, so library mode is when isFullMode === false
|
|
755
|
-
const isLibraryMode = isFullMode === false;
|
|
756
|
-
const editorClassName = `html-editor html-editor--${variant} ${isLibraryMode ? 'html-editor--library-mode' : ''} ${className}`.trim();
|
|
757
|
-
|
|
758
351
|
return (
|
|
759
352
|
<EditorProvider value={contextValue}>
|
|
760
|
-
<div className={
|
|
353
|
+
<div className={`html-editor html-editor--${variant} ${className}`} {...props}>
|
|
761
354
|
{/* Editor Toolbar - Conditional based on variant */}
|
|
762
355
|
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
763
356
|
<EditorToolbar
|
|
@@ -794,23 +387,19 @@ const HTMLEditor = forwardRef(({
|
|
|
794
387
|
ref={mainEditorRef}
|
|
795
388
|
readOnly={readOnly}
|
|
796
389
|
onLabelInsert={handleLabelInsert}
|
|
797
|
-
onErrorClick={handleValidationErrorClick}
|
|
798
|
-
tags={tags}
|
|
799
|
-
injectedTags={injectedTags}
|
|
800
|
-
location={location}
|
|
801
|
-
eventContextTags={eventContextTags}
|
|
802
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
803
|
-
channel={channel}
|
|
804
|
-
userLocale={userLocale}
|
|
805
|
-
moduleFilterEnabled={moduleFilterEnabled}
|
|
806
|
-
onTagContextChange={onTagContextChange}
|
|
807
|
-
onTagSelect={onTagSelect}
|
|
808
|
-
onContextChange={handleContextChange}
|
|
809
390
|
/>
|
|
810
391
|
|
|
811
392
|
{/* Preview Pane */}
|
|
812
393
|
<PreviewPane />
|
|
813
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
|
+
/>
|
|
814
403
|
</CapRow>
|
|
815
404
|
|
|
816
405
|
{/* Fullscreen Modal */}
|
|
@@ -822,17 +411,17 @@ const HTMLEditor = forwardRef(({
|
|
|
822
411
|
maskClosable={false}
|
|
823
412
|
centered
|
|
824
413
|
closable={false}
|
|
825
|
-
width="90vw"
|
|
414
|
+
width={"90vw"}
|
|
826
415
|
className="html-editor-fullscreen-modal"
|
|
827
416
|
>
|
|
828
|
-
<CapRow className=
|
|
417
|
+
<CapRow className="html-editor-fullscreen">
|
|
829
418
|
{/* Editor Toolbar - Conditional based on variant */}
|
|
830
419
|
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
831
420
|
<EditorToolbar
|
|
832
|
-
showFullscreenButton // Show fullscreen button in modal to allow closing
|
|
421
|
+
showFullscreenButton={true} // Show fullscreen button in modal to allow closing
|
|
833
422
|
onLabelInsert={handleLabelInsert}
|
|
834
423
|
onSave={handleSave}
|
|
835
|
-
isFullscreenMode
|
|
424
|
+
isFullscreenMode={true}
|
|
836
425
|
onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
|
|
837
426
|
/>
|
|
838
427
|
) : (
|
|
@@ -845,10 +434,10 @@ const HTMLEditor = forwardRef(({
|
|
|
845
434
|
onKeepContentSameChange={toggleContentSync}
|
|
846
435
|
/>
|
|
847
436
|
<EditorToolbar
|
|
848
|
-
showFullscreenButton // Show fullscreen button in modal to allow closing
|
|
437
|
+
showFullscreenButton={true} // Show fullscreen button in modal to allow closing
|
|
849
438
|
onLabelInsert={handleLabelInsert}
|
|
850
439
|
onSave={handleSave}
|
|
851
|
-
isFullscreenMode
|
|
440
|
+
isFullscreenMode={true}
|
|
852
441
|
onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
|
|
853
442
|
variant={variant}
|
|
854
443
|
showTitle={false} // Hide title in InApp variant
|
|
@@ -863,30 +452,28 @@ const HTMLEditor = forwardRef(({
|
|
|
863
452
|
<CodeEditorPane
|
|
864
453
|
ref={modalEditorRef}
|
|
865
454
|
readOnly={readOnly}
|
|
866
|
-
isFullscreenMode
|
|
455
|
+
isFullscreenMode={true}
|
|
867
456
|
onLabelInsert={handleLabelInsert}
|
|
868
|
-
onErrorClick={handleValidationErrorClick}
|
|
869
|
-
tags={tags}
|
|
870
|
-
injectedTags={injectedTags}
|
|
871
|
-
location={location}
|
|
872
|
-
eventContextTags={eventContextTags}
|
|
873
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
874
|
-
channel={channel}
|
|
875
|
-
userLocale={userLocale}
|
|
876
|
-
moduleFilterEnabled={moduleFilterEnabled}
|
|
877
|
-
onTagContextChange={onTagContextChange}
|
|
878
457
|
/>
|
|
879
458
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
+
/>
|
|
883
470
|
</CapRow>
|
|
884
471
|
</CapRow>
|
|
885
472
|
</CapModal>
|
|
886
473
|
</div>
|
|
887
474
|
</EditorProvider>
|
|
888
475
|
);
|
|
889
|
-
}
|
|
476
|
+
};
|
|
890
477
|
|
|
891
478
|
HTMLEditor.propTypes = {
|
|
892
479
|
intl: intlShape.isRequired,
|
|
@@ -894,7 +481,7 @@ HTMLEditor.propTypes = {
|
|
|
894
481
|
layoutType: PropTypes.string, // Layout type for InApp variant
|
|
895
482
|
initialContent: PropTypes.oneOfType([
|
|
896
483
|
PropTypes.string,
|
|
897
|
-
PropTypes.objectOf(PropTypes.string)
|
|
484
|
+
PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
|
|
898
485
|
]),
|
|
899
486
|
onSave: PropTypes.func,
|
|
900
487
|
onContentChange: PropTypes.func,
|
|
@@ -902,33 +489,11 @@ HTMLEditor.propTypes = {
|
|
|
902
489
|
readOnly: PropTypes.bool,
|
|
903
490
|
showFullscreenButton: PropTypes.bool,
|
|
904
491
|
autoSave: PropTypes.bool,
|
|
905
|
-
autoSaveInterval: PropTypes.number
|
|
906
|
-
// Tag-related props - tags are fetched and managed by parent component
|
|
907
|
-
tags: PropTypes.array,
|
|
908
|
-
injectedTags: PropTypes.object,
|
|
909
|
-
location: PropTypes.object,
|
|
910
|
-
eventContextTags: PropTypes.array,
|
|
911
|
-
selectedOfferDetails: PropTypes.array,
|
|
912
|
-
channel: PropTypes.string,
|
|
913
|
-
userLocale: PropTypes.string,
|
|
914
|
-
moduleFilterEnabled: PropTypes.bool,
|
|
915
|
-
onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
|
|
916
|
-
onTagSelect: PropTypes.func,
|
|
917
|
-
onContextChange: PropTypes.func, // Deprecated: use globalActions instead
|
|
918
|
-
globalActions: PropTypes.object,
|
|
919
|
-
isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
|
|
920
|
-
isFullMode: PropTypes.bool, // Full mode vs library mode
|
|
921
|
-
onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
|
|
922
|
-
onValidationChange: PropTypes.func, // Callback when validation state changes
|
|
923
|
-
apiValidationErrors: PropTypes.shape({
|
|
924
|
-
liquidErrors: PropTypes.arrayOf(PropTypes.string),
|
|
925
|
-
standardErrors: PropTypes.arrayOf(PropTypes.string),
|
|
926
|
-
}), // API validation errors from validateLiquidTemplateContent
|
|
492
|
+
autoSaveInterval: PropTypes.number
|
|
927
493
|
};
|
|
928
494
|
|
|
929
495
|
HTMLEditor.defaultProps = {
|
|
930
496
|
variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
|
|
931
|
-
layoutType: null,
|
|
932
497
|
initialContent: null, // Will use default from useEditorContent hook
|
|
933
498
|
onSave: null,
|
|
934
499
|
onContentChange: null,
|
|
@@ -936,26 +501,8 @@ HTMLEditor.defaultProps = {
|
|
|
936
501
|
readOnly: false,
|
|
937
502
|
showFullscreenButton: true,
|
|
938
503
|
autoSave: true,
|
|
939
|
-
autoSaveInterval: 30000
|
|
940
|
-
// Tag-related defaults - tags are fetched and managed by parent component
|
|
941
|
-
tags: [],
|
|
942
|
-
injectedTags: {},
|
|
943
|
-
location: null,
|
|
944
|
-
eventContextTags: [],
|
|
945
|
-
selectedOfferDetails: [],
|
|
946
|
-
channel: null,
|
|
947
|
-
userLocale: 'en',
|
|
948
|
-
moduleFilterEnabled: true,
|
|
949
|
-
onTagContextChange: null, // Parent component should provide this
|
|
950
|
-
onTagSelect: null,
|
|
951
|
-
onContextChange: null,
|
|
952
|
-
globalActions: null, // Redux actions for API calls
|
|
953
|
-
isLiquidEnabled: false,
|
|
954
|
-
isFullMode: true, // Default to full mode
|
|
955
|
-
onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
|
|
956
|
-
onValidationChange: null, // Callback when validation state changes
|
|
957
|
-
apiValidationErrors: null, // API validation errors from validateLiquidTemplateContent
|
|
504
|
+
autoSaveInterval: 30000
|
|
958
505
|
};
|
|
959
506
|
|
|
960
|
-
// Export with
|
|
961
|
-
export default injectIntl(HTMLEditor);
|
|
507
|
+
// Export with forwardRef to allow direct access to CodeEditorPane via ref
|
|
508
|
+
export default injectIntl(HTMLEditor, { forwardRef: true });
|