@capillarytech/creatives-library 8.0.271 → 8.0.273

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 (153) 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/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
  9. package/tests/integration/TemplateCreation/api-response.js +31 -1
  10. package/tests/integration/TemplateCreation/msw-handler.js +2 -0
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +28 -5
  13. package/utils/imageUrlUpload.js +13 -14
  14. package/utils/tests/commonUtil.test.js +224 -0
  15. package/utils/tests/imageUrlUpload.test.js +298 -0
  16. package/utils/transformTemplateConfig.js +0 -10
  17. package/v2Components/CapDeviceContent/index.js +61 -56
  18. package/v2Components/CapTagList/index.js +6 -1
  19. package/v2Components/CapTagListWithInput/index.js +5 -1
  20. package/v2Components/CapTagListWithInput/messages.js +1 -1
  21. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  22. package/v2Components/ErrorInfoNote/constants.js +1 -0
  23. package/v2Components/ErrorInfoNote/index.js +402 -72
  24. package/v2Components/ErrorInfoNote/messages.js +32 -6
  25. package/v2Components/ErrorInfoNote/style.scss +278 -6
  26. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  27. package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
  28. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
  30. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  31. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  32. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  33. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
  34. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
  35. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  36. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  37. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
  38. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
  39. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  44. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  45. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
  46. package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
  47. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  48. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
  49. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
  50. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
  51. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  52. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
  53. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
  54. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
  55. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  56. package/v2Components/HtmlEditor/constants.js +45 -20
  57. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  58. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
  59. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  60. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  61. package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
  62. package/v2Components/HtmlEditor/index.js +1 -1
  63. package/v2Components/HtmlEditor/messages.js +102 -94
  64. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
  65. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  66. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  67. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  68. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
  69. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  70. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  71. package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
  72. package/v2Components/MobilePushPreviewV2/constants.js +6 -0
  73. package/v2Components/MobilePushPreviewV2/index.js +33 -7
  74. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  75. package/v2Components/TemplatePreview/index.js +47 -32
  76. package/v2Components/TemplatePreview/messages.js +4 -0
  77. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  78. package/v2Containers/BeeEditor/index.js +172 -90
  79. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  80. package/v2Containers/BeePopupEditor/constants.js +10 -0
  81. package/v2Containers/BeePopupEditor/index.js +194 -0
  82. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  83. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
  84. package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
  85. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  86. package/v2Containers/CreativesContainer/constants.js +1 -0
  87. package/v2Containers/CreativesContainer/index.js +251 -47
  88. package/v2Containers/CreativesContainer/messages.js +8 -0
  89. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  90. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
  92. package/v2Containers/Email/actions.js +7 -0
  93. package/v2Containers/Email/constants.js +5 -1
  94. package/v2Containers/Email/index.js +234 -29
  95. package/v2Containers/Email/messages.js +32 -0
  96. package/v2Containers/Email/reducer.js +12 -1
  97. package/v2Containers/Email/sagas.js +61 -7
  98. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  99. package/v2Containers/Email/tests/reducer.test.js +46 -0
  100. package/v2Containers/Email/tests/sagas.test.js +320 -29
  101. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
  102. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
  103. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  104. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2614 -0
  105. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  106. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  107. package/v2Containers/EmailWrapper/constants.js +2 -0
  108. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
  109. package/v2Containers/EmailWrapper/index.js +103 -23
  110. package/v2Containers/EmailWrapper/messages.js +65 -1
  111. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
  112. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
  113. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  114. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  115. package/v2Containers/InApp/actions.js +7 -0
  116. package/v2Containers/InApp/constants.js +20 -4
  117. package/v2Containers/InApp/index.js +802 -360
  118. package/v2Containers/InApp/index.scss +4 -3
  119. package/v2Containers/InApp/messages.js +7 -3
  120. package/v2Containers/InApp/reducer.js +21 -3
  121. package/v2Containers/InApp/sagas.js +29 -9
  122. package/v2Containers/InApp/selectors.js +25 -5
  123. package/v2Containers/InApp/tests/index.test.js +154 -50
  124. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  125. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  126. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  127. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  128. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  129. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  130. package/v2Containers/InAppWrapper/constants.js +16 -0
  131. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  132. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  133. package/v2Containers/InAppWrapper/index.js +148 -0
  134. package/v2Containers/InAppWrapper/messages.js +49 -0
  135. package/v2Containers/InappAdvance/index.js +1099 -0
  136. package/v2Containers/InappAdvance/index.scss +10 -0
  137. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  138. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  139. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  140. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  141. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  142. package/v2Containers/MobilePush/Create/index.js +1 -1
  143. package/v2Containers/MobilePush/Edit/index.js +10 -6
  144. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  145. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  146. package/v2Containers/TagList/index.js +62 -19
  147. package/v2Containers/Templates/_templates.scss +60 -1
  148. package/v2Containers/Templates/index.js +89 -4
  149. package/v2Containers/Templates/messages.js +4 -0
  150. package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
  151. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  152. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  153. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -8,33 +8,44 @@
8
8
  * - Theme support with proper styling
9
9
  */
10
10
 
11
- import React, { forwardRef, useImperativeHandle, useRef, useEffect, useState } from 'react';
11
+ import React, {
12
+ forwardRef, useImperativeHandle, useRef, useEffect, useCallback,
13
+ } from 'react';
12
14
  import PropTypes from 'prop-types';
13
15
 
14
16
  // CodeMirror 6 imports
15
17
  import { EditorState } from '@codemirror/state';
16
- import { EditorView, lineNumbers, highlightActiveLine } from '@codemirror/view';
18
+ import {
19
+ EditorView, lineNumbers, highlightActiveLine, placeholder,
20
+ } from '@codemirror/view';
17
21
 
18
- // Import our comprehensive syntax highlighting solution
19
- import { createRobustExtensions } from '../../utils/properSyntaxHighlighting';
22
+ // Define Theme and Highlighting inline to avoid "multiple instances of @codemirror/state" error
20
23
 
21
24
 
22
25
  import { injectIntl, intlShape } from 'react-intl';
23
26
 
24
27
  // Messages
28
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
29
+ import { createRobustExtensions } from '../../utils/properSyntaxHighlighting';
25
30
  import messages from '../../messages';
26
31
 
27
32
  // Cap UI Components
28
- import CapRow from '@capillarytech/cap-ui-library/CapRow';
29
33
 
30
34
  // Components
31
35
  import TagList from '../../../../v2Containers/TagList';
36
+ import ValidationErrorDisplay from '../ValidationErrorDisplay';
37
+
38
+ // Constants - removed unused imports since tag fetching is handled by parent
32
39
 
33
40
  // Context
34
41
  import { useEditorContext } from '../common/EditorContext';
35
42
 
36
43
  // Styles
37
44
  import './_codeEditorPane.scss';
45
+ import { INAPP } from '../../../../constants/unified';
46
+
47
+ // Define Theme and Highlighting inline to avoid "multiple instances of @codemirror/state" error
48
+
38
49
 
39
50
  // Legacy CodeMirrorEditor removed - using enhanced implementation only
40
51
 
@@ -42,12 +53,25 @@ const CodeEditorPaneComponent = ({
42
53
  intl,
43
54
  readOnly = false,
44
55
  className = '',
45
- isFullscreenMode = false,
46
- onLabelInsert,
47
- forwardedRef
56
+ forwardedRef,
57
+ // Tag-related props - tags are fetched and managed by parent component
58
+ tags = [],
59
+ injectedTags = {},
60
+ location,
61
+ eventContextTags = [],
62
+ selectedOfferDetails = [],
63
+ channel,
64
+ userLocale = 'en',
65
+ moduleFilterEnabled = true,
66
+ onTagContextChange,
67
+ // Validation props
68
+ onErrorClick,
48
69
  }) => {
49
- const { content, validation } = useEditorContext();
50
- const { content: contentValue, updateContent } = content;
70
+ const context = useEditorContext();
71
+ const {
72
+ content, validation, variant, isLiquidEnabled,
73
+ } = context || {};
74
+ const { content: contentValue, updateContent } = content || {};
51
75
  const editorRef = useRef(null);
52
76
  const viewRef = useRef(null);
53
77
 
@@ -61,7 +85,7 @@ const CodeEditorPaneComponent = ({
61
85
  get view() {
62
86
  return viewRef.current;
63
87
  },
64
- viewRef: viewRef, // For compatibility with existing code
88
+ viewRef, // For compatibility with existing code
65
89
 
66
90
  focus: () => {
67
91
  if (viewRef.current) {
@@ -75,7 +99,7 @@ const CodeEditorPaneComponent = ({
75
99
  const pos = position !== undefined ? position : head;
76
100
  view.dispatch({
77
101
  changes: { from: pos, insert: text },
78
- selection: { anchor: pos + text.length }
102
+ selection: { anchor: pos + text.length },
79
103
  });
80
104
  } else {
81
105
  throw new Error('CodeMirror view not initialized');
@@ -88,9 +112,7 @@ const CodeEditorPaneComponent = ({
88
112
  }
89
113
  return 0;
90
114
  },
91
- getValue: () => {
92
- return contentValue || '';
93
- },
115
+ getValue: () => contentValue || '',
94
116
  setValue: (value) => {
95
117
  updateContent(value);
96
118
  },
@@ -107,7 +129,7 @@ const CodeEditorPaneComponent = ({
107
129
 
108
130
  view.dispatch({
109
131
  selection: { anchor: pos },
110
- effects: EditorView.scrollIntoView(pos, { y: 'center' })
132
+ effects: EditorView.scrollIntoView(pos, { y: 'center' }),
111
133
  });
112
134
  view.focus();
113
135
  } catch (error) {
@@ -117,7 +139,7 @@ const CodeEditorPaneComponent = ({
117
139
  }
118
140
  }
119
141
  }
120
- }
142
+ },
121
143
  }), [contentValue]);
122
144
 
123
145
  // Note: handleContentChange removed - using updateContentRef directly in CodeMirror
@@ -133,7 +155,9 @@ const CodeEditorPaneComponent = ({
133
155
  if (typeof tagData === 'string') {
134
156
  tagText = tagData;
135
157
  } else if (tagData) {
136
- const { text, name, label, value } = tagData;
158
+ const {
159
+ text, name, label, value,
160
+ } = tagData;
137
161
  tagText = text || name || label || value;
138
162
  if (!tagText) {
139
163
  console.warn('Invalid tag data:', tagData);
@@ -147,49 +171,69 @@ const CodeEditorPaneComponent = ({
147
171
  // For unified HTML editor, insert as template variable
148
172
  const formattedTag = `{{${tagText}}}`;
149
173
 
150
- // Insert the tag at cursor position
174
+ // Insert the tag at cursor position directly
151
175
  view.dispatch({
152
176
  changes: { from: pos, insert: formattedTag },
153
- selection: { anchor: pos + formattedTag.length }
177
+ selection: { anchor: pos + formattedTag.length },
154
178
  });
155
179
 
156
180
  // Focus back to editor
157
181
  view.focus();
158
182
 
159
- // Call the parent's handleLabelInsert if available
160
- if (onLabelInsert) {
161
- onLabelInsert(formattedTag, pos);
162
- }
183
+ // Note: We don't call onLabelInsert here because:
184
+ // 1. The tag is already inserted directly into the editor
185
+ // 2. onLabelInsert (handleLabelInsert from HTMLEditor) would try to insert again
186
+ // 3. This causes "Editor method not available" error
187
+ // The direct insertion via view.dispatch is sufficient
163
188
  }
164
189
  };
165
190
 
191
+ // Handle tag context change - delegate to parent component
192
+ // Tags are fetched in parent components (EmailHTMLEditor, INAPP, etc.)
193
+ // This component just passes the context change event up
194
+ const handleTagContextChange = useCallback((data) => {
195
+ if (onTagContextChange) {
196
+ // Parent component handles tag fetching and updates
197
+ onTagContextChange(data);
198
+ }
199
+ // No fallback - tags must be managed by parent component
200
+ }, [onTagContextChange]);
201
+
166
202
  // Initialize CodeMirror effect
167
203
  useEffect(() => {
168
- if (editorRef.current && !viewRef.current) {
169
- // Use the comprehensive extensions from properSyntaxHighlighting.js
170
- // This includes: html(), syntaxHighlighting(comprehensiveVSCodeTheme), cleanEditorTheme
171
- const robustExtensions = createRobustExtensions();
172
-
173
- // Add additional extensions for line numbers, active line, and update listener
174
- const extensions = [
175
- lineNumbers(),
176
- highlightActiveLine(),
177
- ...robustExtensions, // Spread the robust extensions (html, syntax highlighting, theme)
178
- EditorView.updateListener.of((update) => {
179
- if (update.docChanged) {
180
- updateContentRef.current(update.state.doc.toString());
181
- }
182
- })
183
- ];
204
+ // Ensure editorRef.current is a valid DOM element before initializing CodeMirror
205
+ if (editorRef.current && editorRef.current instanceof HTMLElement && !viewRef.current) {
206
+ // Determine placeholder text based on channel
207
+ let placeholderText = intl.formatMessage(messages.editorPlaceholderEmail); // Default to email
208
+ if (channel === INAPP || channel === INAPP.toUpperCase()) {
209
+ placeholderText = intl.formatMessage(messages.editorPlaceholderInapp);
210
+ }
211
+ // Use the comprehensive extensions from properSyntaxHighlighting.js
212
+ // This includes: html(), syntaxHighlighting(comprehensiveVSCodeTheme), cleanEditorTheme
213
+ const robustExtensions = createRobustExtensions();
214
+
215
+ // Add additional extensions for line numbers, active line, placeholder, line wrapping, and update listener
216
+ const extensions = [
217
+ lineNumbers(),
218
+ highlightActiveLine(),
219
+ placeholder(placeholderText),
220
+ EditorView.lineWrapping, // Enable soft-wrapping of long lines
221
+ ...robustExtensions, // Spread the robust extensions (html, syntax highlighting, theme)
222
+ EditorView.updateListener.of((update) => {
223
+ if (update.docChanged) {
224
+ updateContentRef.current(update.state.doc.toString());
225
+ }
226
+ }),
227
+ ];
184
228
 
185
229
  const state = EditorState.create({
186
230
  doc: contentValue || '',
187
- extensions
231
+ extensions,
188
232
  });
189
233
 
190
234
  viewRef.current = new EditorView({
191
235
  state,
192
- parent: editorRef.current
236
+ parent: editorRef.current,
193
237
  });
194
238
  }
195
239
 
@@ -204,116 +248,70 @@ const CodeEditorPaneComponent = ({
204
248
  viewRef.current = null;
205
249
  }
206
250
  };
207
- }, []);
251
+ }, [channel, intl]);
208
252
 
209
253
  // Update editor content when content changes
210
254
  useEffect(() => {
211
- if (viewRef.current && contentValue !== viewRef.current.state.doc.toString()) {
255
+ if (viewRef.current && contentValue && contentValue !== viewRef.current.state.doc.toString()) {
212
256
  const { current: view } = viewRef;
213
257
  const { state: { doc: { length } } } = view;
214
258
  view.dispatch({
215
259
  changes: {
216
260
  from: 0,
217
261
  to: length,
218
- insert: contentValue || ''
219
- }
262
+ insert: contentValue || '',
263
+ },
220
264
  });
221
265
  }
222
266
  }, [contentValue]);
223
267
 
224
- return (
225
- <CapRow className={`code-editor-pane ${className}`}>
226
- {/* Unified Code Editor with Floating Add Label Button */}
227
- <CapRow className="code-editor-pane__content">
228
- <div className="codemirror-wrapper">
229
- <div ref={editorRef} className="codemirror-editor" />
230
- {/* Floating Add Label Button */}
231
- <CapRow className="code-editor-pane__actions">
232
- <TagList
233
- key="html-editor-taglist"
234
- label={intl.formatMessage(messages.addLabel)}
235
- onTagSelect={handleTagSelect}
236
- onContextChange={(context) => {
237
- }}
238
- className="tag-list-trigger"
239
- tags={[]} // Empty initially - TagList will fetch from API
240
- injectedTags={{
241
- // Add common HTML/Email specific tags as fallback
242
- 'Customer Info': {
243
- name: 'Customer Info',
244
- desc: 'Customer information tags',
245
- resolved: true,
246
- 'tag-header': true,
247
- subtags: {
248
- 'customer.firstName': {
249
- name: 'First Name',
250
- desc: 'Customer first name',
251
- resolved: true
252
- },
253
- 'customer.lastName': {
254
- name: 'Last Name',
255
- desc: 'Customer last name',
256
- resolved: true
257
- },
258
- 'customer.email': {
259
- name: 'Email',
260
- desc: 'Customer email address',
261
- resolved: true
262
- },
263
- 'customer.phone': {
264
- name: 'Phone',
265
- desc: 'Customer phone number',
266
- resolved: true
267
- }
268
- }
269
- },
270
- 'Common Tags': {
271
- name: 'Common Tags',
272
- desc: 'Commonly used template tags',
273
- resolved: true,
274
- 'tag-header': true,
275
- subtags: {
276
- 'organization.name': {
277
- name: 'Organization Name',
278
- desc: 'Organization name',
279
- resolved: true
280
- },
281
- 'currentDate': {
282
- name: 'Current Date',
283
- desc: 'Current date',
284
- resolved: true
285
- },
286
- 'unsubscribeLink': {
287
- name: 'Unsubscribe Link',
288
- desc: 'Unsubscribe link',
289
- resolved: true
290
- }
291
- }
292
- }
293
- }}
294
- moduleFilterEnabled={true}
295
- userLocale="en"
296
- channel="email"
297
- disabled={readOnly}
298
- location={{
299
- query: {
300
- type: 'html-editor' // Identify the context
301
- }
302
- }}
303
- selectedOfferDetails={[]}
304
- eventContextTags={[]}
305
- />
306
- </CapRow>
307
- </div>
308
- </CapRow>
309
-
310
- </CapRow>
311
- );
268
+ return (
269
+ <CapRow className={`code-editor-pane ${className}`}>
270
+ {/* Code Editor Content - Stacked vertically with validation */}
271
+ <div className="code-editor-pane__wrapper">
272
+ {/* Code Editor with Floating Add Label Button */}
273
+ <div className="codemirror-wrapper">
274
+ <div ref={editorRef} className="codemirror-editor" />
275
+ {/* Floating Add Label Button */}
276
+ <CapRow className="code-editor-pane__actions">
277
+ <TagList
278
+ key="html-editor-taglist"
279
+ label={intl.formatMessage(messages.addLabel)}
280
+ onTagSelect={handleTagSelect}
281
+ onContextChange={handleTagContextChange}
282
+ className="tag-list-trigger"
283
+ tags={tags}
284
+ injectedTags={injectedTags}
285
+ moduleFilterEnabled={moduleFilterEnabled}
286
+ userLocale={userLocale}
287
+ channel={channel}
288
+ disabled={readOnly}
289
+ location={location}
290
+ selectedOfferDetails={selectedOfferDetails}
291
+ eventContextTags={eventContextTags}
292
+ popoverPlacement="rightTop"
293
+ />
294
+ </CapRow>
295
+ </div>
296
+
297
+ {/* Validation Error Display - Below editor, always visible */}
298
+ <ValidationErrorDisplay
299
+ validation={validation}
300
+ onErrorClick={onErrorClick}
301
+ isLiquidEnabled={isLiquidEnabled}
302
+ className="code-editor-pane__validation"
303
+ />
304
+ </div>
305
+ </CapRow>
306
+ );
312
307
  };
313
308
 
314
- // Create the forwardRef wrapper
309
+ // Apply injectIntl to the component first, then wrap with forwardRef
310
+ const CodeEditorPaneWithIntl = injectIntl(CodeEditorPaneComponent);
311
+
312
+ // Create the forwardRef wrapper that forwards ref to the intl-wrapped component
315
313
  const CodeEditorPane = forwardRef((props, ref) => (
316
- <CodeEditorPaneComponent {...props} forwardedRef={ref} />
314
+ <CodeEditorPaneWithIntl {...props} forwardedRef={ref} />
317
315
  ));
318
316
 
319
317
  CodeEditorPane.displayName = 'CodeEditorPane';
@@ -323,9 +321,19 @@ CodeEditorPane.propTypes = {
323
321
  readOnly: PropTypes.bool,
324
322
  className: PropTypes.string,
325
323
  isFullscreenMode: PropTypes.bool,
326
- onLabelInsert: PropTypes.func
324
+ onLabelInsert: PropTypes.func,
325
+ onErrorClick: PropTypes.func,
326
+ // Tag-related props - tags are fetched and managed by parent component
327
+ tags: PropTypes.array,
328
+ injectedTags: PropTypes.object,
329
+ location: PropTypes.object,
330
+ eventContextTags: PropTypes.array,
331
+ selectedOfferDetails: PropTypes.array,
332
+ channel: PropTypes.string,
333
+ userLocale: PropTypes.string,
334
+ moduleFilterEnabled: PropTypes.bool,
335
+ onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
327
336
  };
328
337
 
329
- // Export with injectIntl - ref forwarding is handled by forwardRef wrapper
330
- export default injectIntl(CodeEditorPane);
331
-
338
+ // Export the forwardRef-wrapped component
339
+ export default CodeEditorPane;
@@ -9,7 +9,7 @@
9
9
  .html-editor .device-toggle {
10
10
  display: flex;
11
11
  align-items: center;
12
- gap: 1rem;
12
+ margin-left: 2.5rem;
13
13
  padding: 0;
14
14
  background-color: $CAP_G10;
15
15
  border-radius: 0.25rem 0.25rem 0 0;
@@ -220,6 +220,7 @@
220
220
 
221
221
  // Integration with editor toolbar
222
222
  .html-editor.html-editor--inapp {
223
+ margin-top: 4%;
223
224
  .editor-toolbar {
224
225
  padding: 0 1rem 0 0; // Remove left padding to align with device toggle
225
226
  background-color: $CAP_G10;
@@ -32,7 +32,7 @@ const DeviceToggle = ({
32
32
  onDeviceChange,
33
33
  keepContentSame = false,
34
34
  onKeepContentSameChange,
35
- className = ''
35
+ className = '',
36
36
  }) => {
37
37
  const handleDeviceClick = (device) => {
38
38
  if (onDeviceChange && device !== activeDevice) {
@@ -97,7 +97,7 @@ DeviceToggle.propTypes = {
97
97
  onDeviceChange: PropTypes.func,
98
98
  keepContentSame: PropTypes.bool,
99
99
  onKeepContentSameChange: PropTypes.func,
100
- className: PropTypes.string
100
+ className: PropTypes.string,
101
101
  };
102
102
 
103
103
  DeviceToggle.defaultProps = {
@@ -105,7 +105,7 @@ DeviceToggle.defaultProps = {
105
105
  onDeviceChange: null,
106
106
  keepContentSame: false,
107
107
  onKeepContentSameChange: null,
108
- className: ''
108
+ className: '',
109
109
  };
110
110
 
111
111
  export default injectIntl(DeviceToggle);
@@ -15,11 +15,11 @@
15
15
  border-radius: 0.25rem 0.25rem 0 0;
16
16
  box-sizing: border-box;
17
17
  flex-shrink: 0;
18
+ position: relative;
18
19
 
19
20
  &__left {
20
21
  display: flex;
21
22
  align-items: center;
22
- gap: 1rem;
23
23
  flex-shrink: 0;
24
24
  }
25
25
 
@@ -110,4 +110,12 @@
110
110
  display: none;
111
111
  }
112
112
  }
113
+ }
114
+
115
+ // Library mode override for InApp variant - Position absolute for toolbar
116
+ // These selectors target the toolbar when inside library mode InApp variant
117
+ .html-editor--inapp.html-editor--library-mode .html-editor__header .editor-toolbar,
118
+ .html-editor--inapp.html-editor--library-mode .html-editor__header .editor-toolbar.editor-toolbar,
119
+ .html-editor--inapp.html-editor--library-mode .html-editor__header .ant-layout-header.editor-toolbar {
120
+ position: absolute;
113
121
  }
@@ -12,17 +12,19 @@
12
12
  import React from 'react';
13
13
  import PropTypes from 'prop-types';
14
14
  import { Layout, Typography } from 'antd';
15
- import { injectIntl, intlShape } from 'react-intl';
15
+ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
16
16
 
17
17
  import CapButton from '@capillarytech/cap-ui-library/CapButton';
18
18
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
19
19
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
20
+ import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
21
+ import { FONT_COLOR_01, CAP_SPACE_08 } from '@capillarytech/cap-ui-library/styled/variables';
20
22
 
21
23
  // Component imports
22
24
  import { useEditorContext } from '../common/EditorContext';
23
25
 
24
26
  // Constants
25
- import { HTML_EDITOR_VARIANTS } from '../../constants';
27
+ import { HTML_EDITOR_DOC_URL, HTML_EDITOR_VARIANTS } from '../../constants';
26
28
 
27
29
  // Styles
28
30
  import './_editorToolbar.scss';
@@ -42,7 +44,7 @@ const EditorToolbar = ({
42
44
  className = '',
43
45
  isFullscreenMode = false,
44
46
  variant = HTML_EDITOR_VARIANTS.EMAIL,
45
- showTitle = true
47
+ showTitle = true,
46
48
  }) => {
47
49
  // Context hooks - enabling for Step 10 testing
48
50
  const { layout, content } = useEditorContext();
@@ -66,6 +68,29 @@ const EditorToolbar = ({
66
68
  : intl.formatMessage(messages.htmlEditor)
67
69
  }
68
70
  </Text>
71
+ <CapTooltipWithInfo
72
+ title={(
73
+ <FormattedMessage
74
+ {...messages.htmlEditorTooltip}
75
+ values={{
76
+ docLink: (
77
+ <a
78
+ href={HTML_EDITOR_DOC_URL}
79
+ target="_blank"
80
+ rel="noopener noreferrer"
81
+ >
82
+ <FormattedMessage {...messages.viewDocumentation} />
83
+ </a>
84
+ ),
85
+ }}
86
+ />
87
+ )}
88
+ infoIconProps={{
89
+ style: { marginLeft: CAP_SPACE_08, color: FONT_COLOR_01 },
90
+ }}
91
+ autoAdjustOverflow
92
+ placement="top"
93
+ />
69
94
  </CapRow>
70
95
  )}
71
96
 
@@ -81,7 +106,7 @@ const EditorToolbar = ({
81
106
  )}
82
107
  aria-pressed={isFullscreenMode}
83
108
  >
84
- <CapIcon type={isFullscreenMode ? "minimizer" : "expander"} size="m" />
109
+ <CapIcon type={isFullscreenMode ? "collapse2" : "expander"} size="m" />
85
110
  </CapButton>
86
111
  )}
87
112
  </CapRow>
@@ -98,7 +123,7 @@ EditorToolbar.propTypes = {
98
123
  className: PropTypes.string,
99
124
  isFullscreenMode: PropTypes.bool,
100
125
  variant: PropTypes.oneOf(Object.values(HTML_EDITOR_VARIANTS)),
101
- showTitle: PropTypes.bool
126
+ showTitle: PropTypes.bool,
102
127
  };
103
128
 
104
129
  EditorToolbar.defaultProps = {
@@ -109,7 +134,7 @@ EditorToolbar.defaultProps = {
109
134
  className: '',
110
135
  isFullscreenMode: false,
111
136
  variant: HTML_EDITOR_VARIANTS.EMAIL,
112
- showTitle: true
137
+ showTitle: true,
113
138
  };
114
139
 
115
140
  export default injectIntl(EditorToolbar);
@@ -49,9 +49,31 @@ body .ant-modal-mask+.ant-modal-wrap .ant-modal.html-editor-fullscreen-modal .an
49
49
  padding: 0;
50
50
  min-height: 3.25rem;
51
51
  height: 3.25rem;
52
+ position: relative;
52
53
 
53
54
  &__right {
54
55
  margin-left: auto;
55
56
  }
56
57
  }
58
+ }
59
+
60
+ // Library mode overrides for INAPP variant - Position absolute for toolbar
61
+ // These selectors match the high-specificity patterns above but include library-mode class
62
+ .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
63
+ .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
64
+ .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
65
+ .html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
66
+ .html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
67
+ .html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
68
+ .html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .editor-toolbar,
69
+ .html-editor__header.html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .editor-toolbar,
70
+ .html-editor__header.html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .editor-toolbar.editor-toolbar,
71
+ .html-editor__header.html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .ant-layout-header.editor-toolbar,
72
+ .ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
73
+ .ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
74
+ .ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
75
+ .ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
76
+ .ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
77
+ .ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar {
78
+ position: absolute;
57
79
  }
@@ -14,9 +14,7 @@ const DeviceFrame = ({
14
14
  className = '',
15
15
  ...props
16
16
  }) => {
17
- const getFrameAsset = () => {
18
- return device === DEVICE_TYPES.IOS ? iosFrame : androidFrame;
19
- };
17
+ const getFrameAsset = () => device === DEVICE_TYPES.IOS ? iosFrame : androidFrame;
20
18
 
21
19
  const getFrameStyles = () => {
22
20
  const baseStyles = {
@@ -26,14 +24,13 @@ const DeviceFrame = ({
26
24
  backgroundRepeat: 'no-repeat',
27
25
  backgroundPosition: 'center',
28
26
  backgroundSize: 'contain',
29
- filter: 'drop-shadow(0 12px 48px rgba(0, 0, 0, 0.25)) brightness(1.05) contrast(1.1)'
30
27
  };
31
28
 
32
29
  // Unified dimensions - both devices use the same size since assets are identical
33
30
  return {
34
31
  ...baseStyles,
35
32
  width: '450px',
36
- height: '920px'
33
+ height: '920px',
37
34
  };
38
35
  };
39
36
 
@@ -45,7 +42,7 @@ const DeviceFrame = ({
45
42
  {...props}
46
43
  >
47
44
 
48
- {children}
45
+ {children}
49
46
 
50
47
  </div>
51
48
  );
@@ -54,7 +51,7 @@ const DeviceFrame = ({
54
51
  DeviceFrame.propTypes = {
55
52
  device: PropTypes.oneOf(Object.values(DEVICE_TYPES)),
56
53
  children: PropTypes.node,
57
- className: PropTypes.string
54
+ className: PropTypes.string,
58
55
  };
59
56
 
60
57
  export default DeviceFrame;