@capillarytech/creatives-library 8.0.254 → 8.0.255-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/services/api.js +10 -0
  7. package/services/tests/api.test.js +34 -0
  8. package/utils/common.js +5 -0
  9. package/utils/commonUtils.js +28 -5
  10. package/utils/tests/commonUtil.test.js +224 -0
  11. package/utils/transformTemplateConfig.js +0 -10
  12. package/v2Components/CapDeviceContent/index.js +61 -56
  13. package/v2Components/CapTagList/index.js +6 -1
  14. package/v2Components/CapTagListWithInput/index.js +5 -1
  15. package/v2Components/CapTagListWithInput/messages.js +1 -1
  16. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  17. package/v2Components/ErrorInfoNote/constants.js +1 -0
  18. package/v2Components/ErrorInfoNote/index.js +457 -72
  19. package/v2Components/ErrorInfoNote/messages.js +36 -6
  20. package/v2Components/ErrorInfoNote/style.scss +282 -6
  21. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  22. package/v2Components/HtmlEditor/HTMLEditor.js +547 -94
  23. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
  24. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1358 -133
  25. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  26. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  27. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  28. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +22 -101
  29. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +149 -140
  30. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  31. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  32. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
  33. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  34. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  35. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  36. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  37. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  40. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  41. package/v2Components/HtmlEditor/components/PreviewPane/index.js +24 -34
  42. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  43. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  44. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +50 -34
  45. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  46. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +70 -41
  47. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  48. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +364 -0
  49. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  50. package/v2Components/HtmlEditor/constants.js +42 -20
  51. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  52. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +794 -0
  53. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  54. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  55. package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
  56. package/v2Components/HtmlEditor/index.js +1 -1
  57. package/v2Components/HtmlEditor/messages.js +95 -85
  58. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -45
  59. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  60. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  61. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  62. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +134 -102
  63. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  64. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  65. package/v2Components/HtmlEditor/utils/validationConstants.js +40 -0
  66. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  67. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  68. package/v2Components/TemplatePreview/index.js +47 -32
  69. package/v2Components/TemplatePreview/messages.js +4 -0
  70. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  71. package/v2Containers/BeeEditor/index.js +172 -90
  72. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  73. package/v2Containers/BeePopupEditor/constants.js +10 -0
  74. package/v2Containers/BeePopupEditor/index.js +194 -0
  75. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  76. package/v2Containers/CreativesContainer/SlideBoxContent.js +128 -51
  77. package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
  78. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  79. package/v2Containers/CreativesContainer/constants.js +1 -0
  80. package/v2Containers/CreativesContainer/index.js +239 -46
  81. package/v2Containers/CreativesContainer/messages.js +8 -0
  82. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +106 -0
  85. package/v2Containers/Email/actions.js +7 -0
  86. package/v2Containers/Email/constants.js +5 -1
  87. package/v2Containers/Email/index.js +234 -29
  88. package/v2Containers/Email/messages.js +32 -0
  89. package/v2Containers/Email/reducer.js +12 -1
  90. package/v2Containers/Email/sagas.js +61 -7
  91. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  92. package/v2Containers/Email/tests/reducer.test.js +46 -0
  93. package/v2Containers/Email/tests/sagas.test.js +320 -29
  94. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1285 -0
  95. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +207 -19
  96. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  97. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1870 -0
  98. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  99. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  100. package/v2Containers/EmailWrapper/constants.js +2 -0
  101. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +629 -77
  102. package/v2Containers/EmailWrapper/index.js +103 -23
  103. package/v2Containers/EmailWrapper/messages.js +61 -1
  104. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
  105. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
  106. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  107. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  108. package/v2Containers/InApp/actions.js +7 -0
  109. package/v2Containers/InApp/constants.js +20 -4
  110. package/v2Containers/InApp/index.js +802 -359
  111. package/v2Containers/InApp/index.scss +4 -3
  112. package/v2Containers/InApp/messages.js +7 -3
  113. package/v2Containers/InApp/reducer.js +21 -3
  114. package/v2Containers/InApp/sagas.js +29 -9
  115. package/v2Containers/InApp/selectors.js +25 -5
  116. package/v2Containers/InApp/tests/index.test.js +154 -50
  117. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  118. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  119. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  120. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  121. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  122. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  123. package/v2Containers/InAppWrapper/constants.js +16 -0
  124. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  125. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  126. package/v2Containers/InAppWrapper/index.js +148 -0
  127. package/v2Containers/InAppWrapper/messages.js +49 -0
  128. package/v2Containers/InappAdvance/index.js +1099 -0
  129. package/v2Containers/InappAdvance/index.scss +10 -0
  130. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  131. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  132. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  133. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  134. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  135. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  136. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  137. package/v2Containers/TagList/index.js +62 -19
  138. package/v2Containers/Templates/_templates.scss +60 -1
  139. package/v2Containers/Templates/index.js +89 -4
  140. package/v2Containers/Templates/messages.js +4 -0
  141. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  142. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  143. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -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, { useRef, useCallback, useMemo, useState } from 'react';
15
+ import React, {
16
+ useRef, useCallback, useMemo, useState, useEffect, useImperativeHandle, forwardRef,
17
+ } from 'react';
16
18
  import PropTypes from 'prop-types';
17
- import { Layout } from 'antd'; // Fallback - no Cap UI equivalent
18
19
  import { injectIntl, intlShape } from 'react-intl';
19
20
 
20
21
  // Cap UI Components (First Preference)
21
22
  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,7 +30,6 @@ 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';
34
33
  import { EditorProvider } from './components/common/EditorContext';
35
34
 
36
35
  // Hooks and utilities
@@ -40,7 +39,10 @@ import { useLayoutState } from './hooks/useLayoutState';
40
39
  import { useValidation } from './hooks/useValidation';
41
40
 
42
41
  // Constants
43
- import { HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT } from './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';
44
46
 
45
47
  // Styles
46
48
  import './_htmlEditor.scss';
@@ -49,7 +51,7 @@ import './components/FullscreenModal/_fullscreenModal.scss';
49
51
  // Messages
50
52
  import messages from './messages';
51
53
 
52
- const HTMLEditor = ({
54
+ const HTMLEditor = forwardRef(({
53
55
  intl,
54
56
  variant = HTML_EDITOR_VARIANTS.EMAIL, // New prop: 'email' or 'inapp'
55
57
  layoutType, // Layout type for InApp variant
@@ -61,17 +63,33 @@ const HTMLEditor = ({
61
63
  showFullscreenButton = true,
62
64
  autoSave = true,
63
65
  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: [] }
64
84
  ...props
65
- }) => {
85
+ }, ref) => {
66
86
  // Separate refs for main and modal editors to avoid conflicts
67
87
  const mainEditorRef = useRef(null);
68
88
  const modalEditorRef = useRef(null);
69
89
  const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
70
90
 
71
91
  // Get the currently active editor ref based on fullscreen state
72
- const getActiveEditorRef = useCallback(() => {
73
- return isFullscreenModalOpen ? modalEditorRef : mainEditorRef;
74
- }, [isFullscreenModalOpen]);
92
+ const getActiveEditorRef = useCallback(() => isFullscreenModalOpen ? modalEditorRef : mainEditorRef, [isFullscreenModalOpen]);
75
93
 
76
94
  // Initialize custom hooks for state management - always call both hooks to follow Rules of Hooks
77
95
  const isEmailVariant = variant === HTML_EDITOR_VARIANTS.EMAIL;
@@ -80,7 +98,7 @@ const HTMLEditor = ({
80
98
  autoSave: isEmailVariant ? autoSave : false,
81
99
  autoSaveInterval,
82
100
  onSave: isEmailVariant ? onSave : null,
83
- onChange: isEmailVariant ? onContentChange : null
101
+ onChange: isEmailVariant ? onContentChange : null,
84
102
  };
85
103
 
86
104
  const emailContent = useEditorContent(
@@ -97,7 +115,7 @@ const HTMLEditor = ({
97
115
  // Convert string content to device-specific format
98
116
  inAppInitialContent = {
99
117
  [DEVICE_TYPES.ANDROID]: initialContent,
100
- [DEVICE_TYPES.IOS]: initialContent
118
+ [DEVICE_TYPES.IOS]: initialContent,
101
119
  };
102
120
  } else {
103
121
  // Use provided device-specific content
@@ -109,7 +127,7 @@ const HTMLEditor = ({
109
127
  autoSave: isInAppVariant ? autoSave : false,
110
128
  autoSaveInterval,
111
129
  onSave: isInAppVariant ? onSave : null,
112
- onChange: isInAppVariant ? onContentChange : null
130
+ onChange: isInAppVariant ? onContentChange : null,
113
131
  };
114
132
 
115
133
  const inAppContent = useInAppContent(inAppInitialContent, inAppOptions);
@@ -117,6 +135,64 @@ const HTMLEditor = ({
117
135
  // Use appropriate content hook based on variant
118
136
  const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
119
137
 
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
+
120
196
  // Destructure content properties for cleaner access throughout component
121
197
  const {
122
198
  activeDevice,
@@ -124,14 +200,14 @@ const HTMLEditor = ({
124
200
  switchDevice,
125
201
  toggleContentSync,
126
202
  getDeviceContent,
127
- markAsSaved
203
+ markAsSaved,
128
204
  } = content || {};
129
205
 
130
206
  const layout = useLayoutState({
131
207
  splitSizes: [50, 50],
132
208
  viewMode: 'desktop',
133
209
  mobileWidth: 375,
134
- isFullscreen: false
210
+ isFullscreen: false,
135
211
  });
136
212
 
137
213
  // Get current content for validation based on variant
@@ -158,7 +234,7 @@ const HTMLEditor = ({
158
234
  'sanitizer.productionValidHtml': messages.sanitizer.productionValidHtml,
159
235
  'sanitizer.productionSanitized': messages.sanitizer.productionSanitized,
160
236
  'sanitizer.productionInlineCss': messages.sanitizer.productionInlineCss,
161
- 'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
237
+ 'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent,
162
238
  };
163
239
 
164
240
  const messageObj = messageMap[messageKey];
@@ -179,7 +255,7 @@ const HTMLEditor = ({
179
255
  'validator.largeImageDetected': messages.validator.largeImageDetected,
180
256
  'validator.unclosedCssRule': messages.validator.unclosedCssRule,
181
257
  'validator.emptyCssRule': messages.validator.emptyCssRule,
182
- 'validator.cssValidationFailed': messages.validator.cssValidationFailed
258
+ 'validator.cssValidationFailed': messages.validator.cssValidationFailed,
183
259
  };
184
260
 
185
261
  const messageObj = messageMap[messageKey];
@@ -190,57 +266,375 @@ const HTMLEditor = ({
190
266
  enableRealTime: true,
191
267
  debounceMs: 500,
192
268
  enableSanitization: true,
193
- securityLevel: 'standard'
269
+ securityLevel: 'standard',
270
+ apiValidationErrors, // Pass API validation errors to merge with client-side validation
194
271
  }, formatSanitizerMessage, formatValidatorMessage);
195
272
 
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;
273
+ // Expose validation and content state via ref
274
+ useImperativeHandle(ref, () => ({
275
+ getValidation: () => validation,
276
+ getContent: () => currentContent,
277
+ isContentEmpty: () => !currentContent || currentContent.trim() === '',
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
+ }
201
312
 
202
- if (!editor) {
203
- CapNotification.warning({
204
- message: intl.formatMessage(messages.labelInsertError),
205
- description: intl.formatMessage(messages.editorNotReady),
206
- duration: 3
313
+ // Default to HTML issues
314
+ htmlCount++;
207
315
  });
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) {
208
397
  return;
209
398
  }
210
399
 
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
400
+ // Skip if still validating (wait for validation to complete)
401
+ if (isValidating) {
402
+ return;
403
+ }
404
+
405
+ // Calculate issue counts from validation using ref (avoid stale closure)
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;
217
440
  });
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) {
218
502
  return;
219
503
  }
220
504
 
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);
505
+ // Skip if still validating (wait for validation to complete)
506
+ if (validation?.isValidating) {
507
+ return;
508
+ }
226
509
 
227
- // Insert label at cursor position
228
- editor.insertText(label, cursor);
510
+ // Recalculate issue counts including API errors
511
+ const calculateIssueCounts = () => {
512
+ if (!validation || typeof validation.getAllIssues !== 'function') {
513
+ return {
514
+ html: 0, label: 0, liquid: 0, total: 0,
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();
229
527
 
230
- // Focus the editor if focus method is available
231
- editor?.focus?.();
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
+ );
232
537
 
233
- // Show success notification
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]);
577
+
578
+ // Handle label insertion at cursor position
579
+ // Note: This is called for notification purposes only when tag is inserted via CodeEditorPane
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
+ }
597
+
598
+ // Check if the required methods exist
599
+ if (typeof editor?.insertText !== 'function') {
600
+ CapNotification.error({
601
+ message: intl.formatMessage(messages.labelInsertError),
602
+ description: intl.formatMessage(messages.editorMethodNotAvailable),
603
+ duration: 4,
604
+ });
605
+ return;
606
+ }
607
+
608
+ try {
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
234
634
  CapNotification.success({
235
635
  message: intl.formatMessage(messages.labelInserted),
236
636
  description: intl.formatMessage(messages.labelInsertedDescription, { label }),
237
- duration: 2
238
- });
239
- } catch (error) {
240
- CapNotification.error({
241
- message: intl.formatMessage(messages.labelInsertError),
242
- description: error.message,
243
- duration: 4
637
+ duration: 2,
244
638
  });
245
639
  }
246
640
  }, [intl, getActiveEditorRef]);
@@ -259,13 +653,13 @@ const HTMLEditor = ({
259
653
 
260
654
  CapNotification.success({
261
655
  message: intl.formatMessage(messages.contentSaved),
262
- duration: 2
656
+ duration: 2,
263
657
  });
264
658
  } catch (error) {
265
659
  CapNotification.error({
266
660
  message: intl.formatMessage(messages.saveError),
267
661
  description: error.message,
268
- duration: 4
662
+ duration: 4,
269
663
  });
270
664
  }
271
665
  }, [content, onSave, intl, markAsSaved]);
@@ -276,21 +670,27 @@ const HTMLEditor = ({
276
670
  const editorInstance = activeEditorRef.current;
277
671
  const { line, column = 1 } = error || {};
278
672
 
279
- if (editorInstance && line) {
673
+ // Notify parent that user acknowledged errors by clicking redirection icon
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) {
280
680
  try {
281
- // Access the CodeMirror view through the exposed ref methods
282
- if (editorInstance?.navigateToLine) {
681
+ // If line number exists, navigate to it; otherwise just focus the editor
682
+ if (line && editorInstance?.navigateToLine) {
283
683
  editorInstance.navigateToLine(line, column);
284
684
  } else {
685
+ // For API errors without line numbers, just focus the editor
285
686
  editorInstance?.focus?.();
286
- // Fallback: just focus the editor if navigation isn't available
287
687
  }
288
688
  } catch (err) {
289
689
  // Fallback: just focus the editor
290
690
  editorInstance?.focus?.();
291
691
  }
292
692
  }
293
- }, [getActiveEditorRef]);
693
+ }, [getActiveEditorRef, onErrorAcknowledged]);
294
694
 
295
695
  // Handle fullscreen modal
296
696
  const handleOpenFullscreen = useCallback(() => {
@@ -307,6 +707,7 @@ const HTMLEditor = ({
307
707
  content,
308
708
  layout,
309
709
  validation,
710
+ isLiquidEnabled,
310
711
  editorRef: getActiveEditorRef(),
311
712
  handleLabelInsert,
312
713
  handleSave,
@@ -319,13 +720,14 @@ const HTMLEditor = ({
319
720
  switchDevice,
320
721
  toggleContentSync,
321
722
  getDeviceContent,
322
- layoutType
323
- })
723
+ layoutType,
724
+ }),
324
725
  }), [
325
726
  variant,
326
727
  content,
327
728
  layout,
328
729
  validation,
730
+ isLiquidEnabled,
329
731
  getActiveEditorRef,
330
732
  handleLabelInsert,
331
733
  handleSave,
@@ -336,7 +738,7 @@ const HTMLEditor = ({
336
738
  switchDevice,
337
739
  toggleContentSync,
338
740
  getDeviceContent,
339
- layoutType
741
+ layoutType,
340
742
  ]);
341
743
 
342
744
  // Loading state
@@ -348,9 +750,14 @@ const HTMLEditor = ({
348
750
  );
349
751
  }
350
752
 
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
+
351
758
  return (
352
759
  <EditorProvider value={contextValue}>
353
- <div className={`html-editor html-editor--${variant} ${className}`} {...props}>
760
+ <div className={editorClassName} {...props}>
354
761
  {/* Editor Toolbar - Conditional based on variant */}
355
762
  {variant === HTML_EDITOR_VARIANTS.EMAIL ? (
356
763
  <EditorToolbar
@@ -387,19 +794,23 @@ const HTMLEditor = ({
387
794
  ref={mainEditorRef}
388
795
  readOnly={readOnly}
389
796
  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}
390
809
  />
391
810
 
392
811
  {/* Preview Pane */}
393
812
  <PreviewPane />
394
813
  </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
- />
403
814
  </CapRow>
404
815
 
405
816
  {/* Fullscreen Modal */}
@@ -411,17 +822,17 @@ const HTMLEditor = ({
411
822
  maskClosable={false}
412
823
  centered
413
824
  closable={false}
414
- width={"90vw"}
825
+ width="90vw"
415
826
  className="html-editor-fullscreen-modal"
416
827
  >
417
- <CapRow className="html-editor-fullscreen">
828
+ <CapRow className={`html-editor-fullscreen html-editor-fullscreen--${variant} ${isLibraryMode ? 'html-editor-fullscreen--library-mode' : ''}`}>
418
829
  {/* Editor Toolbar - Conditional based on variant */}
419
830
  {variant === HTML_EDITOR_VARIANTS.EMAIL ? (
420
831
  <EditorToolbar
421
- showFullscreenButton={true} // Show fullscreen button in modal to allow closing
832
+ showFullscreenButton // Show fullscreen button in modal to allow closing
422
833
  onLabelInsert={handleLabelInsert}
423
834
  onSave={handleSave}
424
- isFullscreenMode={true}
835
+ isFullscreenMode
425
836
  onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
426
837
  />
427
838
  ) : (
@@ -434,10 +845,10 @@ const HTMLEditor = ({
434
845
  onKeepContentSameChange={toggleContentSync}
435
846
  />
436
847
  <EditorToolbar
437
- showFullscreenButton={true} // Show fullscreen button in modal to allow closing
848
+ showFullscreenButton // Show fullscreen button in modal to allow closing
438
849
  onLabelInsert={handleLabelInsert}
439
850
  onSave={handleSave}
440
- isFullscreenMode={true}
851
+ isFullscreenMode
441
852
  onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
442
853
  variant={variant}
443
854
  showTitle={false} // Hide title in InApp variant
@@ -452,28 +863,30 @@ const HTMLEditor = ({
452
863
  <CodeEditorPane
453
864
  ref={modalEditorRef}
454
865
  readOnly={readOnly}
455
- isFullscreenMode={true}
866
+ isFullscreenMode
456
867
  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}
457
878
  />
458
879
 
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
- />
880
+ {/* Preview Pane */}
881
+ <PreviewPane isFullscreenMode isModalContext />
882
+ </SplitContainer>
470
883
  </CapRow>
471
884
  </CapRow>
472
885
  </CapModal>
473
886
  </div>
474
887
  </EditorProvider>
475
888
  );
476
- };
889
+ });
477
890
 
478
891
  HTMLEditor.propTypes = {
479
892
  intl: intlShape.isRequired,
@@ -481,7 +894,7 @@ HTMLEditor.propTypes = {
481
894
  layoutType: PropTypes.string, // Layout type for InApp variant
482
895
  initialContent: PropTypes.oneOfType([
483
896
  PropTypes.string,
484
- PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
897
+ PropTypes.objectOf(PropTypes.string), // Per-device content for INAPP variant
485
898
  ]),
486
899
  onSave: PropTypes.func,
487
900
  onContentChange: PropTypes.func,
@@ -489,11 +902,33 @@ HTMLEditor.propTypes = {
489
902
  readOnly: PropTypes.bool,
490
903
  showFullscreenButton: PropTypes.bool,
491
904
  autoSave: PropTypes.bool,
492
- autoSaveInterval: PropTypes.number
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
493
927
  };
494
928
 
495
929
  HTMLEditor.defaultProps = {
496
930
  variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
931
+ layoutType: null,
497
932
  initialContent: null, // Will use default from useEditorContent hook
498
933
  onSave: null,
499
934
  onContentChange: null,
@@ -501,8 +936,26 @@ HTMLEditor.defaultProps = {
501
936
  readOnly: false,
502
937
  showFullscreenButton: true,
503
938
  autoSave: true,
504
- autoSaveInterval: 30000
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
505
958
  };
506
959
 
507
- // Export with forwardRef to allow direct access to CodeEditorPane via ref
508
- export default injectIntl(HTMLEditor, { forwardRef: true });
960
+ // Export with injectIntl - HTMLEditor now uses forwardRef internally
961
+ export default injectIntl(HTMLEditor);