@capillarytech/creatives-library 8.0.236-alpha.2 → 8.0.236-alpha.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.236-alpha.2",
4
+ "version": "8.0.236-alpha.4",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -1376,4 +1376,228 @@ describe("validateCarouselCards", () => {
1376
1376
  expect(result.isValid).toBe(true);
1377
1377
  });
1378
1378
  });
1379
+
1380
+ describe('extractContent BEE Editor Logic (L404-L412)', () => {
1381
+ it('extracts content from beeHtml as string', () => {
1382
+ const platformData = {
1383
+ title: 'Test Title',
1384
+ isBEEeditor: true,
1385
+ beeHtml: '<p>BEE HTML Content</p>',
1386
+ ctas: [
1387
+ { text: 'Click Here', actionLink: 'https://example.com' },
1388
+ ],
1389
+ };
1390
+
1391
+ const result = extractContent(platformData);
1392
+
1393
+ expect(result).toContain('Test Title');
1394
+ expect(result).toContain('<p>BEE HTML Content</p>');
1395
+ expect(result).toContain('Click Here');
1396
+ });
1397
+
1398
+ it('extracts content from beeHtml as object with value property', () => {
1399
+ const platformData = {
1400
+ title: 'Test Title',
1401
+ isBEEeditor: true,
1402
+ beeHtml: { value: '<p>BEE HTML from Object</p>' },
1403
+ ctas: [
1404
+ { text: 'Button Text' },
1405
+ ],
1406
+ };
1407
+
1408
+ const result = extractContent(platformData);
1409
+
1410
+ expect(result).toContain('Test Title');
1411
+ expect(result).toContain('<p>BEE HTML from Object</p>');
1412
+ expect(result).toContain('Button Text');
1413
+ });
1414
+
1415
+ it('handles beeHtml as object without value property', () => {
1416
+ const platformData = {
1417
+ title: 'Test Title',
1418
+ isBEEeditor: true,
1419
+ beeHtml: { someOtherProperty: 'data' },
1420
+ ctas: [],
1421
+ };
1422
+
1423
+ const result = extractContent(platformData);
1424
+
1425
+ // Should extract title and empty beeHtml (since value is undefined)
1426
+ expect(result).toContain('Test Title');
1427
+ expect(result).not.toContain('someOtherProperty');
1428
+ });
1429
+
1430
+ it('extracts ctas with text property', () => {
1431
+ const platformData = {
1432
+ title: 'Title',
1433
+ isBEEeditor: true,
1434
+ beeHtml: '<p>Content</p>',
1435
+ ctas: [
1436
+ { text: 'CTA Text 1' },
1437
+ { text: 'CTA Text 2' },
1438
+ ],
1439
+ };
1440
+
1441
+ const result = extractContent(platformData);
1442
+
1443
+ expect(result).toContain('CTA Text 1');
1444
+ expect(result).toContain('CTA Text 2');
1445
+ });
1446
+
1447
+ it('extracts ctas with actionLink when text is missing', () => {
1448
+ const platformData = {
1449
+ title: 'Title',
1450
+ isBEEeditor: true,
1451
+ beeHtml: '<p>Content</p>',
1452
+ ctas: [
1453
+ { actionLink: 'https://link1.com' },
1454
+ { actionLink: 'https://link2.com' },
1455
+ ],
1456
+ };
1457
+
1458
+ const result = extractContent(platformData);
1459
+
1460
+ expect(result).toContain('https://link1.com');
1461
+ expect(result).toContain('https://link2.com');
1462
+ });
1463
+
1464
+ it('filters out falsy values with filter(Boolean)', () => {
1465
+ const platformData = {
1466
+ title: '',
1467
+ isBEEeditor: true,
1468
+ beeHtml: null,
1469
+ ctas: [
1470
+ { text: null, actionLink: null },
1471
+ { text: 'Valid Text' },
1472
+ ],
1473
+ };
1474
+
1475
+ const result = extractContent(platformData);
1476
+
1477
+ // Should only contain 'Valid Text' after filtering
1478
+ expect(result).toBe('Valid Text');
1479
+ });
1480
+
1481
+ it('handles empty ctas array', () => {
1482
+ const platformData = {
1483
+ title: 'Title Only',
1484
+ isBEEeditor: true,
1485
+ beeHtml: '<p>BEE Content</p>',
1486
+ ctas: [],
1487
+ };
1488
+
1489
+ const result = extractContent(platformData);
1490
+
1491
+ expect(result).toContain('Title Only');
1492
+ expect(result).toContain('<p>BEE Content</p>');
1493
+ });
1494
+
1495
+ it('handles undefined ctas', () => {
1496
+ const platformData = {
1497
+ title: 'Title',
1498
+ isBEEeditor: true,
1499
+ beeHtml: '<p>Content</p>',
1500
+ ctas: undefined,
1501
+ };
1502
+
1503
+ const result = extractContent(platformData);
1504
+
1505
+ expect(result).toContain('Title');
1506
+ expect(result).toContain('<p>Content</p>');
1507
+ });
1508
+
1509
+ it('joins all content with spaces', () => {
1510
+ const platformData = {
1511
+ title: 'Title',
1512
+ isBEEeditor: true,
1513
+ beeHtml: 'HTML',
1514
+ ctas: [
1515
+ { text: 'CTA1' },
1516
+ { text: 'CTA2' },
1517
+ ],
1518
+ };
1519
+
1520
+ const result = extractContent(platformData);
1521
+
1522
+ expect(result).toBe('Title HTML CTA1 CTA2');
1523
+ });
1524
+
1525
+ it('falls back to regular content when not BEE editor', () => {
1526
+ const platformData = {
1527
+ title: 'Title',
1528
+ message: 'Message',
1529
+ isBEEeditor: false,
1530
+ beeHtml: '<p>Should be ignored</p>',
1531
+ ctas: [
1532
+ { text: 'CTA' },
1533
+ ],
1534
+ };
1535
+
1536
+ const result = extractContent(platformData);
1537
+
1538
+ expect(result).toContain('Title');
1539
+ expect(result).toContain('Message');
1540
+ expect(result).toContain('CTA');
1541
+ expect(result).not.toContain('<p>Should be ignored</p>');
1542
+ });
1543
+
1544
+ it('handles null beeHtml', () => {
1545
+ const platformData = {
1546
+ title: 'Title',
1547
+ isBEEeditor: true,
1548
+ beeHtml: null,
1549
+ ctas: [{ text: 'CTA' }],
1550
+ };
1551
+
1552
+ const result = extractContent(platformData);
1553
+
1554
+ expect(result).toContain('Title');
1555
+ expect(result).toContain('CTA');
1556
+ });
1557
+
1558
+ it('handles undefined beeHtml', () => {
1559
+ const platformData = {
1560
+ title: 'Title',
1561
+ isBEEeditor: true,
1562
+ beeHtml: undefined,
1563
+ ctas: [],
1564
+ };
1565
+
1566
+ const result = extractContent(platformData);
1567
+
1568
+ expect(result).toBe('Title');
1569
+ });
1570
+
1571
+ it('handles complex ctas with both text and actionLink', () => {
1572
+ const platformData = {
1573
+ title: 'Title',
1574
+ isBEEeditor: true,
1575
+ beeHtml: 'Content',
1576
+ ctas: [
1577
+ { text: 'CTA Text', actionLink: 'https://example.com' },
1578
+ ],
1579
+ };
1580
+
1581
+ const result = extractContent(platformData);
1582
+
1583
+ // Should prefer text over actionLink
1584
+ expect(result).toContain('CTA Text');
1585
+ expect(result).toContain('Title');
1586
+ expect(result).toContain('Content');
1587
+ });
1588
+
1589
+ it('handles empty string beeHtml', () => {
1590
+ const platformData = {
1591
+ title: 'Title',
1592
+ isBEEeditor: true,
1593
+ beeHtml: '',
1594
+ ctas: [{ text: 'CTA' }],
1595
+ };
1596
+
1597
+ const result = extractContent(platformData);
1598
+
1599
+ expect(result).toBe('Title CTA');
1600
+ });
1601
+ });
1379
1602
  });
1603
+
@@ -28,6 +28,7 @@
28
28
  padding: 0;
29
29
  min-height: 3.25rem;
30
30
  height: 3.25rem;
31
+ position: relative;
31
32
 
32
33
  // Right-align toolbar actions
33
34
  &__right {
@@ -128,6 +129,7 @@
128
129
  padding: 0;
129
130
  min-height: 3.25rem; // 52px = 3.25rem
130
131
  height: 3.25rem;
132
+ position: relative;
131
133
 
132
134
  // Right-align toolbar actions
133
135
  &__right {
@@ -198,7 +198,6 @@
198
198
  }
199
199
 
200
200
  .cm-gutters {
201
- background-color: var(--editor-gutter-bg, $CAP_G02);
202
201
  border-right: 0.0625rem solid var(--editor-border, $CAP_G04);
203
202
  color: var(--editor-gutter-foreground, $FONT_COLOR_02);
204
203
  }
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import React, {
12
- forwardRef, useImperativeHandle, useRef, useEffect,
12
+ forwardRef, useImperativeHandle, useRef, useEffect, useCallback,
13
13
  } from 'react';
14
14
  import PropTypes from 'prop-types';
15
15
 
@@ -17,13 +17,11 @@ import PropTypes from 'prop-types';
17
17
  import { EditorState } from '@codemirror/state';
18
18
  import { EditorView, lineNumbers, highlightActiveLine } from '@codemirror/view';
19
19
 
20
- // Import our comprehensive syntax highlighting solution
21
- import { injectIntl, intlShape } from 'react-intl';
22
- import CapRow from '@capillarytech/cap-ui-library/CapRow';
23
- import { createRobustExtensions } from '../../utils/properSyntaxHighlighting';
24
20
 
21
+ import { injectIntl, intlShape } from 'react-intl';
25
22
 
26
23
  // Messages
24
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
27
25
  import messages from '../../messages';
28
26
 
29
27
  // Cap UI Components
@@ -31,30 +29,36 @@ import messages from '../../messages';
31
29
  // Components
32
30
  import TagList from '../../../../v2Containers/TagList';
33
31
 
32
+ // Constants - removed unused imports since tag fetching is handled by parent
33
+
34
34
  // Context
35
35
  import { useEditorContext } from '../common/EditorContext';
36
36
 
37
37
  // Styles
38
38
  import './_codeEditorPane.scss';
39
39
 
40
+ // Define Theme and Highlighting inline to avoid "multiple instances of @codemirror/state" error
41
+
42
+
40
43
  // Legacy CodeMirrorEditor removed - using enhanced implementation only
41
44
 
42
45
  const CodeEditorPaneComponent = ({
43
46
  intl,
44
47
  readOnly = false,
45
48
  className = '',
46
- isFullscreenMode = false,
47
- onLabelInsert,
48
49
  forwardedRef,
49
- // Tags-related props
50
+ // Tag-related props - tags are fetched and managed by parent component
50
51
  tags = [],
51
52
  injectedTags = {},
52
- location = {},
53
- selectedOfferDetails = null,
54
- onTagSelect = null,
55
- onContextChange = null,
53
+ location,
54
+ eventContextTags = [],
55
+ selectedOfferDetails = [],
56
+ channel,
57
+ userLocale = 'en',
58
+ moduleFilterEnabled = true,
59
+ onTagContextChange,
56
60
  }) => {
57
- const { content, validation } = useEditorContext();
61
+ const { content } = useEditorContext();
58
62
  const { content: contentValue, updateContent } = content;
59
63
  const editorRef = useRef(null);
60
64
  const viewRef = useRef(null);
@@ -130,178 +134,85 @@ const CodeEditorPaneComponent = ({
130
134
 
131
135
  const handleTagSelect = (tagData) => {
132
136
  // Get the unified editor
133
- if (!viewRef.current) {
134
- console.warn('CodeEditorPane: Editor view not initialized. Cannot insert tag.');
135
- // Try to notify parent if onLabelInsert is available
136
- if (onLabelInsert) {
137
- // Extract tag text for notification purposes
138
- let tagText = '';
139
- if (typeof tagData === 'string') {
140
- tagText = tagData;
141
- } else if (tagData) {
142
- const {
143
- text, name, label, value,
144
- } = tagData;
145
- tagText = text || name || label || value || '';
137
+ if (viewRef.current) {
138
+ const view = viewRef.current;
139
+ const { state: { selection: { main: { head: pos } } } } = view;
140
+
141
+ // Extract tag text from the tagData object using destructuring
142
+ let tagText = '';
143
+ if (typeof tagData === 'string') {
144
+ tagText = tagData;
145
+ } else if (tagData) {
146
+ const {
147
+ text, name, label, value,
148
+ } = tagData;
149
+ tagText = text || name || label || value;
150
+ if (!tagText) {
151
+ console.warn('Invalid tag data:', tagData);
152
+ return;
146
153
  }
147
- const formattedTag = tagText ? `{{${tagText}}}` : '';
148
- // Call onLabelInsert with null position to indicate editor not ready
149
- onLabelInsert(formattedTag, null);
150
- }
151
- return;
152
- }
153
-
154
- const view = viewRef.current;
155
- const { state: { selection: { main: { head: pos } } } } = view;
156
-
157
- // Extract tag text from the tagData object using destructuring
158
- let tagText = '';
159
- if (typeof tagData === 'string') {
160
- tagText = tagData;
161
- } else if (tagData) {
162
- const {
163
- text, name, label, value,
164
- } = tagData;
165
- tagText = text || name || label || value;
166
- if (!tagText) {
154
+ } else {
167
155
  console.warn('Invalid tag data:', tagData);
168
156
  return;
169
157
  }
170
- } else {
171
- console.warn('Invalid tag data:', tagData);
172
- return;
173
- }
174
158
 
175
- // For unified HTML editor, insert as template variable
176
- const formattedTag = `{{${tagText}}}`;
159
+ // For unified HTML editor, insert as template variable
160
+ const formattedTag = `{{${tagText}}}`;
177
161
 
178
- // Insert the tag at cursor position
179
- view.dispatch({
180
- changes: { from: pos, insert: formattedTag },
181
- selection: { anchor: pos + formattedTag.length },
182
- });
162
+ // Insert the tag at cursor position directly
163
+ view.dispatch({
164
+ changes: { from: pos, insert: formattedTag },
165
+ selection: { anchor: pos + formattedTag.length },
166
+ });
183
167
 
184
- // Focus back to editor
185
- view.focus();
168
+ // Focus back to editor
169
+ view.focus();
186
170
 
187
- // Call the parent's handleLabelInsert for notification purposes only
188
- // The tag has already been inserted above, so this is just for parent component awareness
189
- if (onLabelInsert) {
190
- onLabelInsert(formattedTag, pos);
171
+ // Note: We don't call onLabelInsert here because:
172
+ // 1. The tag is already inserted directly into the editor
173
+ // 2. onLabelInsert (handleLabelInsert from HTMLEditor) would try to insert again
174
+ // 3. This causes "Editor method not available" error
175
+ // The direct insertion via view.dispatch is sufficient
191
176
  }
192
177
  };
193
178
 
179
+ // Handle tag context change - delegate to parent component
180
+ // Tags are fetched in parent components (EmailHTMLEditor, INAPP, etc.)
181
+ // This component just passes the context change event up
182
+ const handleTagContextChange = useCallback((data) => {
183
+ if (onTagContextChange) {
184
+ // Parent component handles tag fetching and updates
185
+ onTagContextChange(data);
186
+ }
187
+ // No fallback - tags must be managed by parent component
188
+ }, [onTagContextChange]);
189
+
194
190
  // Initialize CodeMirror effect
195
191
  useEffect(() => {
196
192
  if (editorRef.current && !viewRef.current) {
197
- try {
198
- // Debug: Check CodeMirror package instances
199
- console.log('[CodeEditorPane] CodeMirror instances check:', {
200
- EditorState,
201
- EditorStateConstructor: EditorState?.constructor?.name,
202
- EditorView,
203
- EditorViewConstructor: EditorView?.constructor?.name,
204
- lineNumbers: typeof lineNumbers,
205
- highlightActiveLine: typeof highlightActiveLine,
206
- });
207
-
208
- // Use the comprehensive extensions from properSyntaxHighlighting.js
209
- // This includes: html(), syntaxHighlighting(comprehensiveVSCodeTheme), cleanEditorTheme
210
- // Note: Webpack configuration ensures CodeMirror packages are in a single shared chunk
211
- // to prevent multiple instances that break instanceof checks
212
- const robustExtensions = createRobustExtensions();
213
-
214
- console.log('[CodeEditorPane] Robust extensions created:', {
215
- extensionsCount: robustExtensions.length,
216
- extensions: robustExtensions.map((ext) => {
217
- try {
218
- return typeof ext === 'function' ? 'function' : (ext?.constructor?.name || typeof ext);
219
- } catch (e) {
220
- return 'unknown';
221
- }
222
- }),
223
- });
224
-
225
- // Add additional extensions for line numbers, active line, and update listener
226
- // IMPORTANT: Ensure all extensions come from the same CodeMirror instance
227
- const lineNumbersExt = lineNumbers();
228
- const highlightActiveLineExt = highlightActiveLine();
229
- const updateListenerExt = EditorView.updateListener.of((update) => {
193
+ // Add additional extensions for line numbers, active line, and update listener
194
+ const extensions = [
195
+ lineNumbers(),
196
+ highlightActiveLine(),
197
+ // html(), // 1. HTML language support - TEMPORARILY DISABLED due to version conflict
198
+ // syntaxHighlighting(comprehensiveVSCodeTheme), // 2. Syntax highlighting - TEMPORARILY DISABLED
199
+ // cleanEditorTheme, // 3. Theme - TEMPORARILY DISABLED
200
+ EditorView.updateListener.of((update) => {
230
201
  if (update.docChanged) {
231
202
  updateContentRef.current(update.state.doc.toString());
232
203
  }
233
- });
234
-
235
- // Flatten any nested arrays in robustExtensions (shouldn't happen, but be safe)
236
- const flattenedRobustExtensions = robustExtensions.flat();
237
-
238
- const extensions = [
239
- lineNumbersExt,
240
- highlightActiveLineExt,
241
- ...flattenedRobustExtensions, // Spread the robust extensions (html, syntax highlighting, theme)
242
- updateListenerExt,
243
- ];
244
-
245
- console.log('[CodeEditorPane] All extensions prepared:', {
246
- totalExtensions: extensions.length,
247
- extensionTypes: extensions.map((ext) => {
248
- try {
249
- return typeof ext === 'function' ? 'function' : (ext?.constructor?.name || typeof ext);
250
- } catch (e) {
251
- return 'unknown';
252
- }
253
- }),
254
- extensionDetails: extensions.map((ext, idx) => {
255
- try {
256
- return {
257
- index: idx,
258
- type: typeof ext,
259
- constructor: ext?.constructor?.name,
260
- isArray: Array.isArray(ext),
261
- isFunction: typeof ext === 'function',
262
- hasTo: typeof ext?.to === 'function',
263
- hasOf: typeof ext?.of === 'function',
264
- // Check if it's a valid CodeMirror extension
265
- isExtension: ext && (typeof ext === 'function' || typeof ext?.to === 'function' || typeof ext?.of === 'function'),
266
- };
267
- } catch (e) {
268
- return { index: idx, error: e.message };
269
- }
270
- }),
271
- });
272
-
273
- // Validate extensions before creating EditorState
274
- const invalidExtensions = extensions.filter((ext) => {
275
- if (!ext) return true;
276
- if (typeof ext === 'function') return false; // Functions are valid
277
- if (typeof ext?.to === 'function') return false; // Extension objects with .to() are valid
278
- if (typeof ext?.of === 'function') return false; // Extension objects with .of() are valid
279
- if (Array.isArray(ext)) return true; // Arrays should be flattened
280
- return true; // Unknown type
281
- });
282
-
283
- if (invalidExtensions.length > 0) {
284
- console.error('[CodeEditorPane] Invalid extensions detected:', invalidExtensions);
285
- throw new Error(`Invalid CodeMirror extensions detected. This usually means multiple instances of @codemirror packages are loaded.`);
286
- }
204
+ }),
205
+ ];
287
206
 
288
- console.log('[CodeEditorPane] Creating EditorState...');
289
- const state = EditorState.create({
290
- doc: contentValue || '',
291
- extensions,
292
- });
293
- console.log('[CodeEditorPane] EditorState created successfully');
207
+ const state = EditorState.create({
208
+ doc: contentValue || '',
209
+ extensions,
210
+ });
294
211
 
295
- viewRef.current = new EditorView({
296
- state,
297
- parent: editorRef.current,
298
- });
299
- } catch (error) {
300
- // Log error for debugging - this should not happen if webpack config is correct
301
- console.error('Error initializing CodeMirror editor:', error);
302
- // Re-throw to prevent silent failures
303
- throw error;
304
- }
212
+ viewRef.current = new EditorView({
213
+ state,
214
+ parent: editorRef.current,
215
+ });
305
216
  }
306
217
 
307
218
  return () => {
@@ -343,28 +254,19 @@ const CodeEditorPaneComponent = ({
343
254
  <TagList
344
255
  key="html-editor-taglist"
345
256
  label={intl.formatMessage(messages.addLabel)}
346
- onTagSelect={(tag) => {
347
- // Always use handleTagSelect to insert tag at cursor position
348
- handleTagSelect(tag);
349
- // Also call parent's onTagSelect callback if provided
350
- if (onTagSelect) {
351
- onTagSelect(tag);
352
- }
353
- }}
354
- onContextChange={(context) => {
355
- if (onContextChange) {
356
- onContextChange(context);
357
- }
358
- }}
257
+ onTagSelect={handleTagSelect}
258
+ onContextChange={handleTagContextChange}
359
259
  className="tag-list-trigger"
360
- tags={tags} // Use passed tags from parent component
361
- injectedTags={injectedTags} // Use passed injectedTags from parent component
362
- location={location} // Use passed location for context
363
- selectedOfferDetails={selectedOfferDetails} // Use passed selectedOfferDetails
364
- moduleFilterEnabled={
365
- location && location?.query && location?.query?.type !== "embedded"
366
- }
260
+ tags={tags}
261
+ injectedTags={injectedTags}
262
+ moduleFilterEnabled={moduleFilterEnabled}
263
+ userLocale={userLocale}
264
+ channel={channel}
367
265
  disabled={readOnly}
266
+ location={location}
267
+ selectedOfferDetails={selectedOfferDetails}
268
+ eventContextTags={eventContextTags}
269
+ popoverPlacement="rightTop"
368
270
  />
369
271
  </CapRow>
370
272
  </div>
@@ -387,6 +289,16 @@ CodeEditorPane.propTypes = {
387
289
  className: PropTypes.string,
388
290
  isFullscreenMode: PropTypes.bool,
389
291
  onLabelInsert: PropTypes.func,
292
+ // Tag-related props - tags are fetched and managed by parent component
293
+ tags: PropTypes.array,
294
+ injectedTags: PropTypes.object,
295
+ location: PropTypes.object,
296
+ eventContextTags: PropTypes.array,
297
+ selectedOfferDetails: PropTypes.array,
298
+ channel: PropTypes.string,
299
+ userLocale: PropTypes.string,
300
+ moduleFilterEnabled: PropTypes.bool,
301
+ onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
390
302
  };
391
303
 
392
304
  // Export with injectIntl - ref forwarding is handled by forwardRef wrapper
@@ -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;
@@ -49,6 +49,7 @@ 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;