@jhits/plugin-newsletter 0.0.15 → 0.0.17
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/dist/api/email-utils.d.ts.map +1 -1
- package/dist/api/email-utils.js +45 -4
- package/dist/api/handlers/newsletters.d.ts.map +1 -1
- package/dist/api/handlers/newsletters.js +33 -16
- package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
- package/dist/api/handlers/send-newsletter.js +54 -6
- package/dist/api/handlers/settings.d.ts.map +1 -1
- package/dist/api/handlers/settings.js +51 -1
- package/dist/index.d.ts +27 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -122
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/blocks/BlockRenderer.js +14 -2
- package/dist/lib/email/EmailRenderer.d.ts +1 -0
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
- package/dist/lib/email/EmailRenderer.js +31 -19
- package/dist/lib/utils/config-resolver.d.ts +33 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +47 -0
- package/dist/registry/BlockRegistry.d.ts +9 -1
- package/dist/registry/BlockRegistry.d.ts.map +1 -1
- package/dist/registry/BlockRegistry.js +126 -8
- package/dist/state/EditorContext.d.ts +11 -1
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +23 -5
- package/dist/state/types.d.ts +12 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/newsletter.d.ts +4 -0
- package/dist/types/newsletter.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
- package/dist/views/NewsletterManager.d.ts.map +1 -1
- package/dist/views/NewsletterManager.js +87 -5
- package/dist/views/components/DomainPromptModal.d.ts +13 -0
- package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
- package/dist/views/components/DomainPromptModal.js +58 -0
- package/dist/views/components/NewsletterCard.d.ts +16 -0
- package/dist/views/components/NewsletterCard.d.ts.map +1 -0
- package/dist/views/components/NewsletterCard.js +94 -0
- package/dist/views/components/NewsletterGrid.d.ts +16 -0
- package/dist/views/components/NewsletterGrid.d.ts.map +1 -0
- package/dist/views/components/NewsletterGrid.js +13 -0
- package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
- package/dist/views/components/SendNewsletterModal.js +91 -22
- package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
- package/dist/views/components/SmtpSettingsModal.js +10 -0
- package/dist/views/components/TestEmailModal.d.ts.map +1 -1
- package/dist/views/components/TestEmailModal.js +86 -17
- package/package.json +53 -9
- package/src/api/email-utils.ts +53 -4
- package/src/api/handlers/newsletters.ts +40 -20
- package/src/api/handlers/send-newsletter.ts +65 -6
- package/src/api/handlers/settings.ts +60 -2
- package/src/index.tsx +49 -155
- package/src/lib/blocks/BlockRenderer.tsx +16 -2
- package/src/lib/email/EmailRenderer.tsx +31 -20
- package/src/lib/utils/config-resolver.ts +71 -0
- package/src/registry/BlockRegistry.tsx +255 -0
- package/src/state/EditorContext.tsx +43 -8
- package/src/state/types.ts +16 -0
- package/src/types/block.ts +10 -0
- package/src/types/newsletter.ts +5 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
- package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
- package/src/views/CanvasEditor/EditorBody.tsx +17 -13
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
- package/src/views/NewsletterManager.tsx +164 -6
- package/src/views/components/DomainPromptModal.tsx +160 -0
- package/src/views/components/NewsletterCard.tsx +212 -0
- package/src/views/components/NewsletterGrid.tsx +48 -0
- package/src/views/components/SendNewsletterModal.tsx +270 -184
- package/src/views/components/SmtpSettingsModal.tsx +11 -0
- package/src/views/components/TestEmailModal.tsx +235 -149
- package/src/registry/BlockRegistry.ts +0 -53
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useRef, useEffect, useState } from 'react';
|
|
3
|
+
import React, { useRef, useEffect, useState, useMemo } from 'react';
|
|
4
4
|
import { BlockWrapper } from '../BlockWrapper';
|
|
5
5
|
import { EditorBody } from '../EditorBody';
|
|
6
6
|
import { BlockRenderer } from '../../../lib/blocks/BlockRenderer';
|
|
7
|
+
import { useEditor } from '../../../state/EditorContext';
|
|
7
8
|
import { renderBlocksToEmail, generateNewsletterEmailHtml } from '../../../lib/email/EmailRenderer';
|
|
8
9
|
import type { Block } from '../../../types/block';
|
|
9
10
|
import type { NewsletterMetadata } from '../../../types/newsletter';
|
|
10
11
|
import type { useSlashCommand } from '../hooks/useSlashCommand';
|
|
11
12
|
|
|
12
|
-
// Add email-like styling for editor blocks
|
|
13
|
+
// Add email-like styling for editor blocks - Aligned with BotanicsAndYou brand styles
|
|
13
14
|
const emailEditorStyles = `
|
|
15
|
+
.email-editor-content {
|
|
16
|
+
/* Newsletter Brand Variable Overrides */
|
|
17
|
+
--content-text-color: #1a2e26;
|
|
18
|
+
--content-font-family: 'Georgia', serif;
|
|
19
|
+
--content-font-size: 18px;
|
|
20
|
+
--content-line-height: 1.625;
|
|
21
|
+
--content-heading-font: 'Georgia', serif;
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
.email-editor-content p,
|
|
15
25
|
.email-editor-content .text-md {
|
|
16
|
-
font-size:
|
|
17
|
-
line-height:
|
|
18
|
-
color:
|
|
19
|
-
margin: 0 0
|
|
20
|
-
font-family:
|
|
26
|
+
font-size: var(--content-font-size);
|
|
27
|
+
line-height: var(--content-line-height);
|
|
28
|
+
color: var(--content-text-color);
|
|
29
|
+
margin: 0 0 1.5rem 0;
|
|
30
|
+
font-family: var(--content-font-family);
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
.email-editor-content h1,
|
|
@@ -26,52 +36,59 @@ const emailEditorStyles = `
|
|
|
26
36
|
.email-editor-content h4,
|
|
27
37
|
.email-editor-content h5,
|
|
28
38
|
.email-editor-content h6 {
|
|
29
|
-
font-family: 'Georgia', serif
|
|
30
|
-
font-weight: bold
|
|
31
|
-
color: #1a2e26
|
|
32
|
-
margin:
|
|
33
|
-
line-height: 1.
|
|
39
|
+
font-family: 'Georgia', serif;
|
|
40
|
+
font-weight: bold;
|
|
41
|
+
color: #1a2e26;
|
|
42
|
+
margin: 2.5rem 0 1rem 0;
|
|
43
|
+
line-height: 1.2;
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
.email-editor-content h1 {
|
|
37
|
-
font-size:
|
|
47
|
+
font-size: 48px;
|
|
48
|
+
letter-spacing: -0.02em;
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
.email-editor-content h2 {
|
|
41
|
-
font-size:
|
|
52
|
+
font-size: 36px;
|
|
53
|
+
letter-spacing: -0.01em;
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
.email-editor-content h3 {
|
|
45
|
-
font-size:
|
|
57
|
+
font-size: 30px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.email-editor-content h4 {
|
|
61
|
+
font-size: 20px;
|
|
62
|
+
font-weight: 500;
|
|
46
63
|
}
|
|
47
64
|
|
|
48
|
-
.email-editor-content h4,
|
|
49
65
|
.email-editor-content h5,
|
|
50
66
|
.email-editor-content h6 {
|
|
51
|
-
font-size: 18px
|
|
67
|
+
font-size: 18px;
|
|
68
|
+
font-weight: 500;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
.email-editor-content ul,
|
|
55
72
|
.email-editor-content ol {
|
|
56
|
-
margin:
|
|
57
|
-
padding-left:
|
|
58
|
-
color: #1a2e26
|
|
59
|
-
font-size:
|
|
60
|
-
line-height: 1.
|
|
73
|
+
margin: 1.5rem 0;
|
|
74
|
+
padding-left: 1.5rem;
|
|
75
|
+
color: #1a2e26;
|
|
76
|
+
font-size: 18px;
|
|
77
|
+
line-height: 1.625;
|
|
78
|
+
font-family: 'Georgia', serif;
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
.email-editor-content li {
|
|
64
|
-
margin:
|
|
65
|
-
padding-left:
|
|
66
|
-
line-height: 1.8 !important;
|
|
67
|
-
color: #1a2e26 !important;
|
|
82
|
+
margin: 0.75rem 0;
|
|
83
|
+
padding-left: 0.25rem;
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
.email-editor-content img {
|
|
71
|
-
max-width: 100
|
|
72
|
-
height: auto
|
|
73
|
-
display: block
|
|
74
|
-
margin:
|
|
87
|
+
max-width: 100%;
|
|
88
|
+
height: auto;
|
|
89
|
+
display: block;
|
|
90
|
+
margin: 2rem 0;
|
|
91
|
+
border-radius: 12px;
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
.email-block-wrapper {
|
|
@@ -79,15 +96,20 @@ const emailEditorStyles = `
|
|
|
79
96
|
color: #1a2e26;
|
|
80
97
|
}
|
|
81
98
|
|
|
82
|
-
/* Ensure all blocks are visible */
|
|
99
|
+
/* Ensure all blocks are visible and match brand font */
|
|
100
|
+
.email-block-content {
|
|
101
|
+
font-family: 'Georgia', serif;
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
.email-block-content > * {
|
|
84
|
-
display: block
|
|
85
|
-
visibility: visible
|
|
86
|
-
opacity: 1
|
|
105
|
+
display: block;
|
|
106
|
+
visibility: visible;
|
|
107
|
+
opacity: 1;
|
|
87
108
|
}
|
|
88
109
|
|
|
89
|
-
/* Heading input styling to match preview */
|
|
90
|
-
.email-block-content input[type="text"]
|
|
110
|
+
/* Heading/Text input styling to match brand preview */
|
|
111
|
+
.email-block-content input[type="text"],
|
|
112
|
+
.email-block-content textarea {
|
|
91
113
|
font-family: 'Georgia', serif !important;
|
|
92
114
|
color: #1a2e26 !important;
|
|
93
115
|
background: transparent !important;
|
|
@@ -238,12 +260,45 @@ export function EditorCanvas({
|
|
|
238
260
|
onBlockMove,
|
|
239
261
|
slashCommand,
|
|
240
262
|
}: EditorCanvasProps) {
|
|
263
|
+
const { emailConfig: contextEmailConfig, unsubscribeTranslations: contextUnsubscribeTranslations } = useEditor();
|
|
264
|
+
|
|
265
|
+
// SMTP settings take priority over client config
|
|
266
|
+
const emailConfig = useMemo(() => {
|
|
267
|
+
let smtpLogoUrl = '';
|
|
268
|
+
let smtpUnsubscribeTranslations = {};
|
|
269
|
+
|
|
270
|
+
if (typeof window !== 'undefined') {
|
|
271
|
+
const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__?.['plugin-newsletter'];
|
|
272
|
+
if (pluginProps?.emailConfig?.logoUrl) {
|
|
273
|
+
smtpLogoUrl = pluginProps.emailConfig.logoUrl;
|
|
274
|
+
}
|
|
275
|
+
if (pluginProps?.unsubscribeTranslations) {
|
|
276
|
+
smtpUnsubscribeTranslations = pluginProps.unsubscribeTranslations;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
...contextEmailConfig,
|
|
282
|
+
logoUrl: smtpLogoUrl || contextEmailConfig?.logoUrl,
|
|
283
|
+
unsubscribeTranslations: {
|
|
284
|
+
en: 'Unsubscribe',
|
|
285
|
+
nl: 'Afmelden',
|
|
286
|
+
sv: 'Avanmälan',
|
|
287
|
+
...contextUnsubscribeTranslations,
|
|
288
|
+
...smtpUnsubscribeTranslations
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}, [contextEmailConfig, contextUnsubscribeTranslations]);
|
|
292
|
+
|
|
241
293
|
const titleRef = useRef<HTMLTextAreaElement>(null);
|
|
242
294
|
|
|
243
295
|
// Inject email-like styles for editor
|
|
244
296
|
useEffect(() => {
|
|
245
297
|
const styleId = 'email-editor-styles';
|
|
246
|
-
|
|
298
|
+
const existingStyle = document.getElementById(styleId);
|
|
299
|
+
if (existingStyle) {
|
|
300
|
+
existingStyle.textContent = emailEditorStyles;
|
|
301
|
+
} else {
|
|
247
302
|
const style = document.createElement('style');
|
|
248
303
|
style.id = styleId;
|
|
249
304
|
style.textContent = emailEditorStyles;
|
|
@@ -261,13 +316,13 @@ export function EditorCanvas({
|
|
|
261
316
|
|
|
262
317
|
return (
|
|
263
318
|
<div
|
|
264
|
-
className="flex-1 overflow-y-auto overflow-x-hidden pb-40 custom-scrollbar selection:bg-primary/20 dark:selection:bg-primary/30 min-h-0"
|
|
319
|
+
className="flex-1 overflow-y-auto overflow-x-hidden pb-40 custom-scrollbar selection:bg-primary/20 dark:selection:bg-primary/30 min-h-0 h-full max-h-full"
|
|
265
320
|
style={{
|
|
266
321
|
backgroundColor: backgroundColors
|
|
267
322
|
? (darkMode && backgroundColors.dark
|
|
268
323
|
? backgroundColors.dark
|
|
269
324
|
: backgroundColors.light)
|
|
270
|
-
: '#
|
|
325
|
+
: '#ffffff',
|
|
271
326
|
}}
|
|
272
327
|
>
|
|
273
328
|
{isPreviewMode ? (
|
|
@@ -348,45 +403,34 @@ export function EditorCanvas({
|
|
|
348
403
|
backgroundColor: '#ffffff',
|
|
349
404
|
}}>
|
|
350
405
|
{/* Email Header with Logo - Centered like in email */}
|
|
351
|
-
{
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
alt={emailConfig.logoAlt || 'Logo'}
|
|
373
|
-
style={{
|
|
374
|
-
width: '180px',
|
|
375
|
-
height: 'auto',
|
|
376
|
-
display: 'block',
|
|
377
|
-
margin: '0 auto',
|
|
378
|
-
}}
|
|
379
|
-
/>
|
|
380
|
-
</div>
|
|
381
|
-
) : null;
|
|
382
|
-
})()}
|
|
406
|
+
{emailConfig?.logoUrl ? (
|
|
407
|
+
<div style={{
|
|
408
|
+
padding: '40px 0 20px 0',
|
|
409
|
+
textAlign: 'center',
|
|
410
|
+
backgroundColor: '#ffffff',
|
|
411
|
+
display: 'flex',
|
|
412
|
+
justifyContent: 'center',
|
|
413
|
+
alignItems: 'center',
|
|
414
|
+
}}>
|
|
415
|
+
<img
|
|
416
|
+
src={emailConfig.logoUrl}
|
|
417
|
+
alt={emailConfig.logoAlt || 'Logo'}
|
|
418
|
+
style={{
|
|
419
|
+
width: '180px',
|
|
420
|
+
height: 'auto',
|
|
421
|
+
display: 'block',
|
|
422
|
+
margin: '0 auto',
|
|
423
|
+
}}
|
|
424
|
+
/>
|
|
425
|
+
</div>
|
|
426
|
+
) : null}
|
|
383
427
|
|
|
384
428
|
{/* Email Content Area */}
|
|
385
429
|
<div className="email-editor-content" style={{
|
|
386
430
|
fontFamily: "'Georgia', serif",
|
|
387
431
|
color: '#1a2e26',
|
|
388
|
-
lineHeight: '1.
|
|
389
|
-
fontSize: '
|
|
432
|
+
lineHeight: '1.625',
|
|
433
|
+
fontSize: '18px',
|
|
390
434
|
padding: '0 50px 40px 50px',
|
|
391
435
|
minHeight: '400px',
|
|
392
436
|
backgroundColor: '#ffffff',
|
|
@@ -414,9 +458,11 @@ export function EditorCanvas({
|
|
|
414
458
|
const slug = slugs[locale] || slugs.en;
|
|
415
459
|
const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
|
|
416
460
|
|
|
417
|
-
// Get unsubscribe text
|
|
418
|
-
const
|
|
419
|
-
const
|
|
461
|
+
// Get unsubscribe text (Priority: metadata manual override > global SMTP translation > hardcoded localized default)
|
|
462
|
+
const globalTranslations = (emailConfig as any).unsubscribeTranslations || {};
|
|
463
|
+
const hardcodedDefaults: Record<string, string> = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
|
|
464
|
+
const defaultText = globalTranslations[locale] || hardcodedDefaults[locale] || hardcodedDefaults.en;
|
|
465
|
+
const unsubscribeText = metadata?.unsubscribeText || defaultText;
|
|
420
466
|
|
|
421
467
|
return (
|
|
422
468
|
<>
|
|
@@ -452,31 +498,20 @@ export function EditorCanvas({
|
|
|
452
498
|
</div>
|
|
453
499
|
|
|
454
500
|
{/* Email Footer - Centered like in email */}
|
|
455
|
-
{
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
fontFamily: 'sans-serif',
|
|
470
|
-
fontSize: '10px',
|
|
471
|
-
color: '#a1a1aa',
|
|
472
|
-
letterSpacing: '1px',
|
|
473
|
-
borderTop: '1px solid #faf9f6',
|
|
474
|
-
backgroundColor: '#ffffff',
|
|
475
|
-
}}>
|
|
476
|
-
{emailConfig.footerText}
|
|
477
|
-
</div>
|
|
478
|
-
) : null;
|
|
479
|
-
})()}
|
|
501
|
+
{emailConfig?.footerText ? (
|
|
502
|
+
<div style={{
|
|
503
|
+
padding: '40px 50px',
|
|
504
|
+
textAlign: 'center',
|
|
505
|
+
fontFamily: 'sans-serif',
|
|
506
|
+
fontSize: '10px',
|
|
507
|
+
color: '#a1a1aa',
|
|
508
|
+
letterSpacing: '1px',
|
|
509
|
+
borderTop: '1px solid #faf9f6',
|
|
510
|
+
backgroundColor: '#ffffff',
|
|
511
|
+
}}>
|
|
512
|
+
{emailConfig.footerText}
|
|
513
|
+
</div>
|
|
514
|
+
) : null}
|
|
480
515
|
</div>
|
|
481
516
|
</div>
|
|
482
517
|
</div>
|
|
@@ -492,19 +527,40 @@ export function EditorCanvas({
|
|
|
492
527
|
* Matches the editor view structure for consistency
|
|
493
528
|
*/
|
|
494
529
|
function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: string; blocks: Block[]; siteId: string; locale: string; metadata?: NewsletterMetadata }) {
|
|
530
|
+
const { emailConfig: contextEmailConfig, unsubscribeTranslations: contextUnsubscribeTranslations } = useEditor();
|
|
531
|
+
|
|
532
|
+
// SMTP settings take priority over client config
|
|
533
|
+
const emailConfig = useMemo(() => {
|
|
534
|
+
let smtpLogoUrl = '';
|
|
535
|
+
let smtpUnsubscribeTranslations = {};
|
|
536
|
+
|
|
537
|
+
if (typeof window !== 'undefined') {
|
|
538
|
+
const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__?.['plugin-newsletter'];
|
|
539
|
+
if (pluginProps?.emailConfig?.logoUrl) {
|
|
540
|
+
smtpLogoUrl = pluginProps.emailConfig.logoUrl;
|
|
541
|
+
}
|
|
542
|
+
if (pluginProps?.unsubscribeTranslations) {
|
|
543
|
+
smtpUnsubscribeTranslations = pluginProps.unsubscribeTranslations;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
...contextEmailConfig,
|
|
549
|
+
logoUrl: smtpLogoUrl || contextEmailConfig?.logoUrl,
|
|
550
|
+
unsubscribeTranslations: {
|
|
551
|
+
en: 'Unsubscribe',
|
|
552
|
+
nl: 'Afmelden',
|
|
553
|
+
sv: 'Avanmälan',
|
|
554
|
+
...contextUnsubscribeTranslations,
|
|
555
|
+
...smtpUnsubscribeTranslations
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}, [contextEmailConfig, contextUnsubscribeTranslations]);
|
|
559
|
+
|
|
495
560
|
const [emailHtml, setEmailHtml] = useState<string>('');
|
|
496
561
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
497
562
|
|
|
498
563
|
useEffect(() => {
|
|
499
|
-
// Get email config from window global (set by initNewsletterPlugin)
|
|
500
|
-
let emailConfig: { logoUrl?: string; logoAlt?: string; footerText?: string } = {};
|
|
501
|
-
if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
|
|
502
|
-
const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
|
|
503
|
-
if (pluginProps?.emailConfig) {
|
|
504
|
-
emailConfig = pluginProps.emailConfig;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
564
|
// Resolve image IDs to filenames before generating HTML
|
|
509
565
|
const resolveImageBlocks = async () => {
|
|
510
566
|
const resolvedBlocks = await Promise.all(blocks.map(async (block) => {
|
|
@@ -550,6 +606,12 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
|
|
|
550
606
|
// Use placeholder email for preview
|
|
551
607
|
const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
|
|
552
608
|
|
|
609
|
+
// Get unsubscribe text (Priority: metadata manual override > global SMTP translation > hardcoded localized default)
|
|
610
|
+
const globalTranslations = (emailConfig as any).unsubscribeTranslations || {};
|
|
611
|
+
const hardcodedDefaults: Record<string, string> = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
|
|
612
|
+
const defaultText = globalTranslations[locale] || hardcodedDefaults[locale] || hardcodedDefaults.en;
|
|
613
|
+
const unsubscribeText = metadata?.unsubscribeText || defaultText;
|
|
614
|
+
|
|
553
615
|
// Generate email HTML with resolved image blocks
|
|
554
616
|
const html = generateNewsletterEmailHtml(
|
|
555
617
|
resolvedBlocks,
|
|
@@ -558,10 +620,11 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
|
|
|
558
620
|
siteId,
|
|
559
621
|
locale,
|
|
560
622
|
baseUrl,
|
|
561
|
-
logoUrl: emailConfig
|
|
562
|
-
logoAlt: emailConfig
|
|
563
|
-
footerText: emailConfig
|
|
623
|
+
logoUrl: emailConfig?.logoUrl,
|
|
624
|
+
logoAlt: emailConfig?.logoAlt,
|
|
625
|
+
footerText: emailConfig?.footerText,
|
|
564
626
|
unsubscribeUrl,
|
|
627
|
+
unsubscribeText,
|
|
565
628
|
}
|
|
566
629
|
);
|
|
567
630
|
setEmailHtml(html);
|
|
@@ -579,7 +642,7 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
|
|
|
579
642
|
};
|
|
580
643
|
|
|
581
644
|
resolveImageBlocks();
|
|
582
|
-
}, [title, blocks, siteId, locale, metadata]);
|
|
645
|
+
}, [title, blocks, siteId, locale, metadata, emailConfig]);
|
|
583
646
|
|
|
584
647
|
if (blocks.length === 0) {
|
|
585
648
|
return (
|
|
@@ -664,7 +727,7 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
|
|
|
664
727
|
maxHeight: '900px',
|
|
665
728
|
display: 'block',
|
|
666
729
|
}}
|
|
667
|
-
sandbox="allow-same-origin"
|
|
730
|
+
sandbox="allow-same-origin allow-scripts"
|
|
668
731
|
/>
|
|
669
732
|
</div>
|
|
670
733
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { Mail, Globe, Users } from 'lucide-react';
|
|
4
|
+
import { Mail, Globe, Users, Palette, Info } from 'lucide-react';
|
|
5
5
|
import type { NewsletterMetadata } from '../../../types/newsletter';
|
|
6
6
|
|
|
7
7
|
export interface EditorSidebarProps {
|
|
@@ -13,6 +13,8 @@ export interface EditorSidebarProps {
|
|
|
13
13
|
languages?: string[];
|
|
14
14
|
currentLanguage?: string;
|
|
15
15
|
onLanguageChange?: (language: string) => void;
|
|
16
|
+
globalUnsubscribeText?: string;
|
|
17
|
+
onGlobalUnsubscribeUpdate?: (text: string) => void;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export function EditorSidebar({
|
|
@@ -24,6 +26,8 @@ export function EditorSidebar({
|
|
|
24
26
|
languages = ['en'],
|
|
25
27
|
currentLanguage = 'en',
|
|
26
28
|
onLanguageChange,
|
|
29
|
+
globalUnsubscribeText = '',
|
|
30
|
+
onGlobalUnsubscribeUpdate,
|
|
27
31
|
}: EditorSidebarProps) {
|
|
28
32
|
return (
|
|
29
33
|
<div className="p-8 w-80 min-w-0 max-w-full space-y-12 overflow-y-auto max-h-full">
|
|
@@ -32,7 +36,7 @@ export function EditorSidebar({
|
|
|
32
36
|
<div className="flex items-center gap-3 mb-6">
|
|
33
37
|
<Mail size={14} className="text-neutral-500 dark:text-neutral-400" />
|
|
34
38
|
<label className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black">
|
|
35
|
-
|
|
39
|
+
Campaign Settings
|
|
36
40
|
</label>
|
|
37
41
|
</div>
|
|
38
42
|
<div className="space-y-4">
|
|
@@ -69,6 +73,57 @@ export function EditorSidebar({
|
|
|
69
73
|
</div>
|
|
70
74
|
</section>
|
|
71
75
|
|
|
76
|
+
{/* Global Branding Settings */}
|
|
77
|
+
<section className="pt-8 border-t border-neutral-200 dark:border-neutral-800">
|
|
78
|
+
<div className="flex items-center gap-3 mb-6">
|
|
79
|
+
<Palette size={14} className="text-neutral-500 dark:text-neutral-400" />
|
|
80
|
+
<label className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black">
|
|
81
|
+
Global Branding
|
|
82
|
+
</label>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="space-y-4">
|
|
85
|
+
<div className="bg-blue-50/50 dark:bg-blue-900/10 border border-blue-100 dark:border-blue-900/30 rounded-xl p-3 mb-4">
|
|
86
|
+
<div className="flex gap-2 text-blue-600 dark:text-blue-400">
|
|
87
|
+
<Info size={14} className="flex-shrink-0 mt-0.5" />
|
|
88
|
+
<p className="text-[9px] leading-relaxed font-medium">
|
|
89
|
+
Settings below apply to <span className="font-bold">ALL</span> newsletters sent in <span className="uppercase font-bold">{currentLanguage}</span>.
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
<div>
|
|
94
|
+
<label className="text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2">
|
|
95
|
+
Default Unsubscribe Text
|
|
96
|
+
</label>
|
|
97
|
+
<input
|
|
98
|
+
type="text"
|
|
99
|
+
value={globalUnsubscribeText}
|
|
100
|
+
onChange={(e) => onGlobalUnsubscribeUpdate?.(e.target.value)}
|
|
101
|
+
placeholder="e.g. Unsubscribe from this list"
|
|
102
|
+
className="w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text italic"
|
|
103
|
+
/>
|
|
104
|
+
<p className="text-[9px] text-neutral-400 dark:text-neutral-500 mt-2 leading-relaxed">
|
|
105
|
+
This updates your primary configuration for this language.
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div>
|
|
110
|
+
<label className="text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2">
|
|
111
|
+
Local Campaign Override
|
|
112
|
+
</label>
|
|
113
|
+
<input
|
|
114
|
+
type="text"
|
|
115
|
+
value={metadata.unsubscribeText || ''}
|
|
116
|
+
onChange={(e) => onMetadataUpdate({ unsubscribeText: e.target.value })}
|
|
117
|
+
placeholder="Optional override for this email only"
|
|
118
|
+
className="w-full px-3 py-2 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text"
|
|
119
|
+
/>
|
|
120
|
+
<p className="text-[9px] text-neutral-400 dark:text-neutral-500 mt-1">
|
|
121
|
+
Only use this if you need a specific text for this single campaign.
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</section>
|
|
126
|
+
|
|
72
127
|
{/* Recipient Filter */}
|
|
73
128
|
<section className="pt-8 border-t border-neutral-200 dark:border-neutral-800">
|
|
74
129
|
<div className="flex items-center gap-3 mb-6">
|
|
@@ -1,54 +1,15 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import { blockRegistry } from '../../../registry/BlockRegistry';
|
|
3
|
+
import { useEditor } from '../../../state/EditorContext';
|
|
3
4
|
|
|
4
5
|
export function useRegisteredBlocks() {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
return initial;
|
|
8
|
-
});
|
|
6
|
+
const { registeredBlockTypes } = useEditor();
|
|
7
|
+
const [registeredBlocks, setRegisteredBlocks] = useState(() => blockRegistry.getAll());
|
|
9
8
|
|
|
10
|
-
//
|
|
9
|
+
// Update local state whenever registeredBlockTypes changes in context
|
|
11
10
|
useEffect(() => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const currentBlocks = blockRegistry.getAll();
|
|
15
|
-
const hasChanged = currentBlocks.length !== registeredBlocks.length ||
|
|
16
|
-
currentBlocks.some((b, i) => b.type !== registeredBlocks[i]?.type) ||
|
|
17
|
-
registeredBlocks.some((b, i) => b.type !== currentBlocks[i]?.type);
|
|
18
|
-
|
|
19
|
-
if (hasChanged) {
|
|
20
|
-
setRegisteredBlocks([...currentBlocks]);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Initial check
|
|
25
|
-
checkBlocks();
|
|
26
|
-
|
|
27
|
-
// Poll for registry changes (blocks are registered asynchronously in useEffect)
|
|
28
|
-
let pollCount = 0;
|
|
29
|
-
const interval = setInterval(() => {
|
|
30
|
-
pollCount++;
|
|
31
|
-
checkBlocks();
|
|
32
|
-
// Stop polling after 5 seconds (25 checks at 200ms)
|
|
33
|
-
if (pollCount > 25) {
|
|
34
|
-
clearInterval(interval);
|
|
35
|
-
}
|
|
36
|
-
}, 200);
|
|
37
|
-
|
|
38
|
-
// Also check after delays to catch initial registrations
|
|
39
|
-
const timeouts = [
|
|
40
|
-
setTimeout(checkBlocks, 50),
|
|
41
|
-
setTimeout(checkBlocks, 100),
|
|
42
|
-
setTimeout(checkBlocks, 300),
|
|
43
|
-
setTimeout(checkBlocks, 500),
|
|
44
|
-
setTimeout(checkBlocks, 1000),
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
return () => {
|
|
48
|
-
clearInterval(interval);
|
|
49
|
-
timeouts.forEach(clearTimeout);
|
|
50
|
-
};
|
|
51
|
-
}, [registeredBlocks.length]);
|
|
11
|
+
setRegisteredBlocks(blockRegistry.getAll());
|
|
12
|
+
}, [registeredBlockTypes]);
|
|
52
13
|
|
|
53
14
|
return registeredBlocks;
|
|
54
15
|
}
|