@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.
Files changed (90) hide show
  1. package/dist/api/email-utils.d.ts.map +1 -1
  2. package/dist/api/email-utils.js +45 -4
  3. package/dist/api/handlers/newsletters.d.ts.map +1 -1
  4. package/dist/api/handlers/newsletters.js +33 -16
  5. package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
  6. package/dist/api/handlers/send-newsletter.js +54 -6
  7. package/dist/api/handlers/settings.d.ts.map +1 -1
  8. package/dist/api/handlers/settings.js +51 -1
  9. package/dist/index.d.ts +27 -10
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +15 -122
  12. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  13. package/dist/lib/blocks/BlockRenderer.js +14 -2
  14. package/dist/lib/email/EmailRenderer.d.ts +1 -0
  15. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  16. package/dist/lib/email/EmailRenderer.js +31 -19
  17. package/dist/lib/utils/config-resolver.d.ts +33 -0
  18. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  19. package/dist/lib/utils/config-resolver.js +47 -0
  20. package/dist/registry/BlockRegistry.d.ts +9 -1
  21. package/dist/registry/BlockRegistry.d.ts.map +1 -1
  22. package/dist/registry/BlockRegistry.js +126 -8
  23. package/dist/state/EditorContext.d.ts +11 -1
  24. package/dist/state/EditorContext.d.ts.map +1 -1
  25. package/dist/state/EditorContext.js +23 -5
  26. package/dist/state/types.d.ts +12 -0
  27. package/dist/state/types.d.ts.map +1 -1
  28. package/dist/types/block.d.ts +9 -0
  29. package/dist/types/block.d.ts.map +1 -1
  30. package/dist/types/newsletter.d.ts +4 -0
  31. package/dist/types/newsletter.d.ts.map +1 -1
  32. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  33. package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
  34. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  35. package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
  36. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  37. package/dist/views/CanvasEditor/EditorBody.js +1 -1
  38. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  39. package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
  40. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
  41. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  42. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  43. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
  44. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
  45. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
  46. package/dist/views/NewsletterManager.d.ts.map +1 -1
  47. package/dist/views/NewsletterManager.js +87 -5
  48. package/dist/views/components/DomainPromptModal.d.ts +13 -0
  49. package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
  50. package/dist/views/components/DomainPromptModal.js +58 -0
  51. package/dist/views/components/NewsletterCard.d.ts +16 -0
  52. package/dist/views/components/NewsletterCard.d.ts.map +1 -0
  53. package/dist/views/components/NewsletterCard.js +94 -0
  54. package/dist/views/components/NewsletterGrid.d.ts +16 -0
  55. package/dist/views/components/NewsletterGrid.d.ts.map +1 -0
  56. package/dist/views/components/NewsletterGrid.js +13 -0
  57. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
  58. package/dist/views/components/SendNewsletterModal.js +91 -22
  59. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
  60. package/dist/views/components/SmtpSettingsModal.js +10 -0
  61. package/dist/views/components/TestEmailModal.d.ts.map +1 -1
  62. package/dist/views/components/TestEmailModal.js +86 -17
  63. package/package.json +53 -9
  64. package/src/api/email-utils.ts +53 -4
  65. package/src/api/handlers/newsletters.ts +40 -20
  66. package/src/api/handlers/send-newsletter.ts +65 -6
  67. package/src/api/handlers/settings.ts +60 -2
  68. package/src/index.tsx +49 -155
  69. package/src/lib/blocks/BlockRenderer.tsx +16 -2
  70. package/src/lib/email/EmailRenderer.tsx +31 -20
  71. package/src/lib/utils/config-resolver.ts +71 -0
  72. package/src/registry/BlockRegistry.tsx +255 -0
  73. package/src/state/EditorContext.tsx +43 -8
  74. package/src/state/types.ts +16 -0
  75. package/src/types/block.ts +10 -0
  76. package/src/types/newsletter.ts +5 -0
  77. package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
  78. package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
  79. package/src/views/CanvasEditor/EditorBody.tsx +17 -13
  80. package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
  81. package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
  82. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
  83. package/src/views/NewsletterManager.tsx +164 -6
  84. package/src/views/components/DomainPromptModal.tsx +160 -0
  85. package/src/views/components/NewsletterCard.tsx +212 -0
  86. package/src/views/components/NewsletterGrid.tsx +48 -0
  87. package/src/views/components/SendNewsletterModal.tsx +270 -184
  88. package/src/views/components/SmtpSettingsModal.tsx +11 -0
  89. package/src/views/components/TestEmailModal.tsx +235 -149
  90. package/src/registry/BlockRegistry.ts +0 -53
package/dist/index.js CHANGED
@@ -13,96 +13,19 @@ import { SettingsView } from './views/SettingsView';
13
13
  import { NewsletterManagerView } from './views/NewsletterManager';
14
14
  import { NewsletterEditorView } from './views/NewsletterEditor';
15
15
  import { editorStateToAPI } from './lib/mappers/apiMapper';
16
+ import { resolvePluginConfig } from './lib/utils/config-resolver';
16
17
  /**
17
18
  * Main Router Component
18
19
  * Handles routing within the newsletter plugin
19
- *
20
- * Client Handshake:
21
- * - Client apps can pass customBlocks via props
22
- * - Or via window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].customBlocks
23
- * - The EditorProvider will automatically register these blocks
24
20
  */
25
21
  export default function NewsletterPlugin(props) {
26
- const { subPath, siteId, locale, customBlocks: propsCustomBlocks, darkMode: propsDarkMode, backgroundColors: propsBackgroundColors } = props;
27
- // Get custom blocks from props or window global (client app injection point)
28
- const customBlocks = useMemo(() => {
29
- // First, try props
30
- if (propsCustomBlocks && propsCustomBlocks.length > 0) {
31
- return propsCustomBlocks;
32
- }
33
- // Fallback to window global (for client app injection)
34
- if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
35
- const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
36
- if (pluginProps?.customBlocks) {
37
- return pluginProps.customBlocks;
38
- }
39
- }
40
- return [];
41
- }, [propsCustomBlocks]);
42
- // Get dark mode setting from props, localStorage (dev settings), or window global
43
- // Priority: localStorage (dev) > props > window global > default
44
- const darkMode = useMemo(() => {
45
- // First, check localStorage for dev settings (highest priority for dev)
46
- if (typeof window !== 'undefined') {
47
- try {
48
- const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
49
- if (saved) {
50
- const config = JSON.parse(saved);
51
- if (config.darkMode !== undefined) {
52
- return config.darkMode;
53
- }
54
- }
55
- }
56
- catch (e) {
57
- // Ignore localStorage errors
58
- }
59
- }
60
- // Then try props
61
- if (propsDarkMode !== undefined) {
62
- return propsDarkMode;
63
- }
64
- // Fallback to window global if prop not provided
65
- if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
66
- const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
67
- if (pluginProps?.darkMode !== undefined) {
68
- return pluginProps.darkMode;
69
- }
70
- }
71
- return true; // Default to dark mode enabled
72
- }, [propsDarkMode]);
73
- // Get background colors from props, localStorage (dev settings), or window global
74
- // Priority: localStorage (dev) > props > window global
75
- const backgroundColors = useMemo(() => {
76
- // First, check localStorage for dev settings (highest priority for dev)
77
- if (typeof window !== 'undefined') {
78
- try {
79
- const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
80
- if (saved) {
81
- const config = JSON.parse(saved);
82
- if (config.backgroundColors) {
83
- return config.backgroundColors;
84
- }
85
- }
86
- }
87
- catch (e) {
88
- // Ignore localStorage errors
89
- }
90
- }
91
- // Then try props
92
- if (propsBackgroundColors) {
93
- return propsBackgroundColors;
94
- }
95
- // Fallback to window global
96
- if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
97
- const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
98
- if (pluginProps?.backgroundColors) {
99
- return pluginProps.backgroundColors;
100
- }
101
- }
102
- return undefined;
103
- }, [propsBackgroundColors]);
22
+ const { subPath, siteId, locale } = props;
23
+ // Resolve configuration from multiple sources (Props, Window, Storage)
24
+ const config = useMemo(() => resolvePluginConfig(props), [props]);
25
+ const { customBlocks, darkMode, backgroundColors, translations, emailConfig, unsubscribeTranslations } = config;
104
26
  const route = subPath[0] || 'newsletters';
105
- // Listen for config updates from settings screen
27
+ const newsletterId = subPath[1];
28
+ // Listen for config updates (e.g. from settings screen)
106
29
  useEffect(() => {
107
30
  if (typeof window === 'undefined')
108
31
  return;
@@ -120,8 +43,7 @@ export default function NewsletterPlugin(props) {
120
43
  case 'newsletters':
121
44
  return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
122
45
  case 'editor':
123
- const newsletterId = subPath[1];
124
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
46
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, translations: translations, emailConfig: emailConfig, unsubscribeTranslations: unsubscribeTranslations, onSave: async (state, extraData) => {
125
47
  // Save to API - create new or update existing newsletter
126
48
  const originalId = newsletterId || state.slug;
127
49
  const apiData = editorStateToAPI(state);
@@ -134,7 +56,6 @@ export default function NewsletterPlugin(props) {
134
56
  const langParam = extraData?.language ? `?language=${extraData.language}` : '';
135
57
  // If we have an id, try to update first
136
58
  if (originalId) {
137
- console.log('[NewsletterPlugin] Attempting to update newsletter with id:', originalId);
138
59
  const updateResponse = await fetch(`/api/plugin-newsletter/newsletters/${originalId}${langParam}`, {
139
60
  method: 'PUT',
140
61
  headers: { 'Content-Type': 'application/json' },
@@ -143,31 +64,14 @@ export default function NewsletterPlugin(props) {
143
64
  });
144
65
  if (updateResponse.ok) {
145
66
  const result = await updateResponse.json();
146
- // If the id changed, update the URL
147
67
  if (result.id && result.id !== originalId) {
148
68
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
149
69
  }
150
70
  return result;
151
71
  }
152
- // If 404, newsletter doesn't exist, create a new one
153
- if (updateResponse.status === 404) {
154
- console.log('[NewsletterPlugin] Newsletter not found, creating new newsletter');
155
- }
156
- else {
157
- // Other error, throw it
158
- const error = await updateResponse.json();
159
- console.error('[NewsletterPlugin] Save failed:', {
160
- status: updateResponse.status,
161
- statusText: updateResponse.statusText,
162
- error,
163
- });
164
- const errorMessage = error.message || error.error || 'Failed to save newsletter';
165
- throw new Error(errorMessage);
166
- }
167
72
  }
168
- // Create new newsletter (either no id or update returned 404)
169
- console.log('[NewsletterPlugin] Creating new newsletter');
170
- const createResponse = await fetch('/api/plugin-newsletter/newsletters/new', {
73
+ // Create new newsletter
74
+ const createResponse = await fetch(`/api/plugin-newsletter/newsletters/new${langParam}`, {
171
75
  method: 'POST',
172
76
  headers: { 'Content-Type': 'application/json' },
173
77
  credentials: 'include',
@@ -175,26 +79,18 @@ export default function NewsletterPlugin(props) {
175
79
  });
176
80
  if (!createResponse.ok) {
177
81
  const error = await createResponse.json();
178
- console.error('[NewsletterPlugin] Create failed:', {
179
- status: createResponse.status,
180
- statusText: createResponse.statusText,
181
- error,
182
- });
183
82
  const errorMessage = error.message || error.error || 'Failed to create newsletter';
184
83
  throw new Error(errorMessage);
185
84
  }
186
85
  const result = await createResponse.json();
187
- // Update the URL to the new newsletter's id
188
86
  if (result.id) {
189
87
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
190
88
  }
191
89
  return result;
192
90
  }, children: _jsx(NewsletterEditorView, { newsletterId: newsletterId, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
193
91
  case 'new':
194
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
195
- // Save to API - create new newsletter
92
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, translations: translations, emailConfig: emailConfig, unsubscribeTranslations: unsubscribeTranslations, onSave: async (state, extraData) => {
196
93
  const apiData = editorStateToAPI(state);
197
- // Include language in metadata if provided
198
94
  if (extraData?.language) {
199
95
  apiData.metadata = apiData.metadata || {};
200
96
  apiData.metadata.lang = extraData.language;
@@ -210,7 +106,6 @@ export default function NewsletterPlugin(props) {
210
106
  throw new Error(error.message || 'Failed to create newsletter');
211
107
  }
212
108
  const result = await response.json();
213
- // Update the URL to the new newsletter's slug
214
109
  if (result.slug) {
215
110
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
216
111
  }
@@ -219,7 +114,7 @@ export default function NewsletterPlugin(props) {
219
114
  case 'subscribers':
220
115
  return _jsx(SubscribersView, { siteId: siteId, locale: locale });
221
116
  case 'welcome':
222
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, isWelcomeEmail: true, onSave: async (state, extraData) => {
117
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, translations: translations, emailConfig: emailConfig, unsubscribeTranslations: unsubscribeTranslations, isWelcomeEmail: true, onSave: async (state, extraData) => {
223
118
  const apiData = editorStateToAPI(state);
224
119
  const language = extraData?.language || locale || 'en';
225
120
  const response = await fetch(`/api/plugin-newsletter/welcome-email?language=${language}`, {
@@ -240,11 +135,9 @@ export default function NewsletterPlugin(props) {
240
135
  return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
241
136
  }
242
137
  }
243
- // Export for use as default
138
+ // Export symbols as needed
244
139
  export { NewsletterPlugin as Index };
245
- // Export initialization utility for easy setup
246
140
  export { initNewsletterPlugin } from './init';
247
- // Export editor state management
248
- export { EditorProvider, useEditor } from './state/EditorContext';
249
- // Export block registry
250
141
  export { blockRegistry } from './registry';
142
+ export { EditorProvider, useEditor } from './state/EditorContext';
143
+ export { BlockRenderer, BlocksRenderer } from './lib/blocks/BlockRenderer';
@@ -1 +1 @@
1
- {"version":3,"file":"BlockRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/BlockRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI7D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,sBAAsB;IACtB,KAAK,EAAE,KAAK,CAAC;IAEb,oEAAoE;IACpE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEtE,uCAAuC;IACvC,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,eAAe,EACf,OAAY,EACf,EAAE,kBAAkB,2CAoDpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,MAAM,EACN,eAAe,EACf,OAAY,EACf,EAAE;IACC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL,2CAaA"}
1
+ {"version":3,"file":"BlockRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/BlockRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAK7D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,sBAAsB;IACtB,KAAK,EAAE,KAAK,CAAC;IAEb,oEAAoE;IACpE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEtE,uCAAuC;IACvC,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,eAAe,EACf,OAAY,EACf,EAAE,kBAAkB,2CAiEpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,MAAM,EACN,eAAe,EACf,OAAY,EACf,EAAE;IACC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL,2CAaA"}
@@ -8,20 +8,32 @@
8
8
  'use client';
9
9
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
10
10
  import { blockRegistry } from '../../registry/BlockRegistry';
11
+ import { useEditor } from '../../state/EditorContext';
11
12
  /**
12
13
  * Block Renderer Component
13
14
  * Renders a single block using its Preview component from the registry
14
15
  */
15
16
  export function BlockRenderer({ block, customRenderers, context = {} }) {
17
+ // We access the context to ensure re-render when blocks are registered
18
+ try {
19
+ useEditor();
20
+ }
21
+ catch (e) {
22
+ // Not in editor context (e.g. preview bridge)
23
+ }
16
24
  // Check for custom renderer override first
17
25
  if (customRenderers?.has(block.type)) {
18
26
  const CustomRenderer = customRenderers.get(block.type);
19
27
  return _jsx(CustomRenderer, { block: block, context: context });
20
28
  }
21
29
  // Get block definition from registry
22
- const definition = blockRegistry.get(block.type);
30
+ let definition = blockRegistry.get(block.type);
31
+ // If not found, try one last immediate re-check of the global window registry
32
+ if (!definition && typeof window !== 'undefined' && window.__JHITS_NEWSLETTER_REGISTRY__) {
33
+ definition = window.__JHITS_NEWSLETTER_REGISTRY__.get(block.type);
34
+ }
23
35
  if (!definition) {
24
- console.warn(`Block type "${block.type}" not found in registry. Available types:`, blockRegistry.getAll().map(b => b.type).join(', '));
36
+ console.warn(`[BlockRenderer] Unknown block type: ${block.type}. Registry contains:`, blockRegistry.getAll().map(b => b.type).join(', '));
25
37
  return (_jsxs("div", { className: "p-4 border border-red-300 bg-red-50 rounded", children: [_jsxs("p", { className: "text-red-600", children: ["Unknown block type: ", block.type] }), _jsx("p", { className: "text-xs text-red-500 mt-1", children: "Make sure this block type is registered via customBlocks prop" })] }));
26
38
  }
27
39
  // Use the Preview component from the block definition
@@ -39,6 +39,7 @@ export declare function generateNewsletterEmailHtml(blocks: Block[], metadata: {
39
39
  previewText?: string;
40
40
  }, context: EmailRenderContext & {
41
41
  unsubscribeUrl?: string;
42
+ unsubscribeText?: string;
42
43
  footerText?: string;
43
44
  logoUrl?: string;
44
45
  logoAlt?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"EmailRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/email/EmailRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAmBzF;AA0OD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,OAAO,EAAE,kBAAkB,GAAG;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,GACF,MAAM,CA8GR"}
1
+ {"version":3,"file":"EmailRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/email/EmailRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAmBzF;AAoPD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,OAAO,EAAE,kBAAkB,GAAG;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,GACF,MAAM,CA+GR"}
@@ -41,14 +41,25 @@ function renderBlockByType(block, context) {
41
41
  const level = block.data.level || 1;
42
42
  const text = block.data.text || '';
43
43
  const tag = `h${Math.min(Math.max(level, 1), 6)}`;
44
- const fontSize = level === 1 ? '32px' : level === 2 ? '24px' : level === 3 ? '20px' : '18px';
45
- return `<${tag} style="font-size: ${fontSize}; font-weight: bold; color: #1a2e26; margin: 20px 0 10px 0; line-height: 1.3;">${escapeHtml(text)}</${tag}>`;
44
+ // Aligned with BotanicsAndYou heading sizes
45
+ let fontSize = '30px';
46
+ if (level === 1)
47
+ fontSize = '48px';
48
+ if (level === 2)
49
+ fontSize = '36px';
50
+ if (level === 3)
51
+ fontSize = '30px';
52
+ if (level >= 4)
53
+ fontSize = '20px';
54
+ const fontWeight = level >= 4 ? '500' : 'bold';
55
+ return `<${tag} style="font-family: 'Georgia', serif; font-size: ${fontSize}; font-weight: ${fontWeight}; color: #1a2e26; margin: 40px 0 15px 0; line-height: 1.2;">${escapeHtml(text)}</${tag}>`;
46
56
  }
47
57
  case 'paragraph': {
48
58
  const html = block.data.html || block.data.text || '';
49
59
  // Convert basic HTML tags to email-safe inline styles
50
60
  const emailHtml = convertHtmlToEmailSafe(html);
51
- return `<p style="font-size: 15px; line-height: 1.8; color: #1a2e26; margin: 0 0 15px 0;">${emailHtml}</p>`;
61
+ // Aligned with BotanicsAndYou paragraph style (text-lg = 18px)
62
+ return `<p style="font-family: 'Georgia', serif; font-size: 18px; line-height: 1.625; color: #1a2e26; margin: 0 0 24px 0;">${emailHtml}</p>`;
52
63
  }
53
64
  case 'image': {
54
65
  const imageId = block.data.imageId;
@@ -114,7 +125,7 @@ function renderBlockByType(block, context) {
114
125
  imageStyles.push(`border-radius: ${borderRadiusValue}`);
115
126
  }
116
127
  const imageStyleString = imageStyles.join('; ');
117
- let html = `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin: 20px 0;">
128
+ let html = `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin: 30px 0;">
118
129
  <tr>
119
130
  <td align="center">
120
131
  <img src="${imageUrl}" alt="${escapeHtml(alt)}" style="${imageStyleString}" />
@@ -122,7 +133,7 @@ function renderBlockByType(block, context) {
122
133
  </tr>`;
123
134
  if (caption) {
124
135
  html += `<tr>
125
- <td align="center" style="padding-top: 10px; font-size: 12px; color: #666; font-style: italic;">
136
+ <td align="center" style="padding-top: 10px; font-size: 14px; color: #666; font-style: italic; font-family: 'Georgia', serif;">
126
137
  ${escapeHtml(caption)}
127
138
  </td>
128
139
  </tr>`;
@@ -138,9 +149,9 @@ function renderBlockByType(block, context) {
138
149
  const itemsHtml = items.map((item, idx) => {
139
150
  const text = typeof item === 'string' ? item : (item.html || item.text || '');
140
151
  const emailHtml = convertHtmlToEmailSafe(text);
141
- return `<li style="margin: 8px 0; padding-left: 5px; line-height: 1.8; color: #1a2e26;">${emailHtml}</li>`;
152
+ return `<li style="margin: 12px 0; padding-left: 5px; line-height: 1.625; color: #1a2e26;">${emailHtml}</li>`;
142
153
  }).join('');
143
- return `<${tag} style="margin: 15px 0; padding-left: 25px; color: #1a2e26; font-size: 15px; line-height: 1.8;">${itemsHtml}</${tag}>`;
154
+ return `<${tag} style="font-family: 'Georgia', serif; margin: 24px 0; padding-left: 24px; color: #1a2e26; font-size: 18px; line-height: 1.625;">${itemsHtml}</${tag}>`;
144
155
  }
145
156
  case 'table': {
146
157
  const rows = (Array.isArray(block.data?.rows) ? block.data.rows : []);
@@ -149,7 +160,7 @@ function renderBlockByType(block, context) {
149
160
  return '';
150
161
  }
151
162
  // Build table HTML using email-safe table structure
152
- let tableHtml = `<table width="100%" cellpadding="12" cellspacing="0" border="0" style="margin: 20px 0; border-collapse: collapse; width: 100%;">`;
163
+ let tableHtml = `<table width="100%" cellpadding="12" cellspacing="0" border="0" style="margin: 30px 0; border-collapse: collapse; width: 100%; font-family: 'Georgia', serif;">`;
153
164
  rows.forEach((row, rIdx) => {
154
165
  const isHeaderRow = rIdx === 0 && useHeader;
155
166
  const tag = isHeaderRow ? 'th' : 'td';
@@ -159,7 +170,7 @@ function renderBlockByType(block, context) {
159
170
  tableHtml += `<tr>`;
160
171
  row.forEach((cell) => {
161
172
  const cellHtml = convertHtmlToEmailSafe(cell.html || cell.text || '');
162
- tableHtml += `<${tag} style="padding: 12px; border: 1px solid #e5e7eb; text-align: left; color: ${textColor}; font-size: 15px; line-height: 1.8; background-color: ${bgColor}; font-weight: ${fontWeight};">${cellHtml}</${tag}>`;
173
+ tableHtml += `<${tag} style="padding: 15px; border: 1px solid #e5e7eb; text-align: left; color: ${textColor}; font-size: 16px; line-height: 1.6; background-color: ${bgColor}; font-weight: ${fontWeight};">${cellHtml}</${tag}>`;
163
174
  });
164
175
  tableHtml += `</tr>`;
165
176
  });
@@ -167,7 +178,7 @@ function renderBlockByType(block, context) {
167
178
  return tableHtml;
168
179
  }
169
180
  case 'divider': {
170
- return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin: 30px 0;">
181
+ return `<table width="100%" cellpadding="0" cellspacing="0" border="0" style="margin: 40px 0;">
171
182
  <tr>
172
183
  <td align="center">
173
184
  <table width="40" cellpadding="0" cellspacing="0" border="0">
@@ -183,7 +194,7 @@ function renderBlockByType(block, context) {
183
194
  // Generic fallback - try to extract text content
184
195
  const text = extractTextFromBlock(block);
185
196
  if (text) {
186
- return `<p style="font-size: 15px; line-height: 1.8; color: #1a2e26; margin: 0 0 15px 0;">${escapeHtml(text)}</p>`;
197
+ return `<p style="font-family: 'Georgia', serif; font-size: 18px; line-height: 1.625; color: #1a2e26; margin: 0 0 24px 0;">${escapeHtml(text)}</p>`;
187
198
  }
188
199
  return '';
189
200
  }
@@ -246,11 +257,12 @@ export function renderBlocksToEmail(blocks, context = {}) {
246
257
  * Uses the same styling as the welcome email template
247
258
  */
248
259
  export function generateNewsletterEmailHtml(blocks, metadata, context) {
249
- const { baseUrl = '', unsubscribeUrl, footerText, logoUrl, logoAlt = 'Logo', locale = 'en' } = context;
260
+ const { baseUrl = '', unsubscribeUrl, unsubscribeText: propsUnsubscribeText, footerText, logoUrl, logoAlt = 'Logo', locale = 'en' } = context;
250
261
  const contentHtml = renderBlocksToEmail(blocks, context);
251
262
  // Get unsubscribe text based on locale (matching welcome email)
252
263
  const isDutch = locale === 'nl';
253
- const unsubscribeText = isDutch ? 'Afmelden' : 'Unsubscribe';
264
+ const defaultText = isDutch ? 'Afmelden' : 'Unsubscribe';
265
+ const unsubscribeText = propsUnsubscribeText || defaultText;
254
266
  // Build header HTML if logo is provided
255
267
  const headerHtml = logoUrl ? `
256
268
  <div class="header">
@@ -290,8 +302,8 @@ export function generateNewsletterEmailHtml(blocks, metadata, context) {
290
302
  .content {
291
303
  padding: 0 50px 40px 50px;
292
304
  color: #1a2e26;
293
- line-height: 1.8;
294
- font-size: 15px;
305
+ line-height: 1.625;
306
+ font-size: 18px;
295
307
  }
296
308
  .footer {
297
309
  padding: 40px 50px;
@@ -303,12 +315,12 @@ export function generateNewsletterEmailHtml(blocks, metadata, context) {
303
315
  border-top: 1px solid #faf9f6;
304
316
  }
305
317
  h1 {
306
- font-weight: normal;
307
- font-style: italic;
308
- font-size: 30px;
318
+ font-weight: bold;
319
+ font-size: 48px;
309
320
  margin-bottom: 30px;
310
321
  color: #1a2e26;
311
- text-align: center;
322
+ text-align: left;
323
+ line-height: 1.2;
312
324
  }
313
325
  .divider {
314
326
  height: 1px;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Newsletter Plugin Configuration Resolver
3
+ * Centralizes the logic for resolving plugin settings from multiple sources
4
+ */
5
+ import { ClientBlockDefinition } from '../../types/block';
6
+ export interface ResolvedConfig {
7
+ customBlocks: ClientBlockDefinition[];
8
+ darkMode: boolean;
9
+ backgroundColors?: {
10
+ light: string;
11
+ dark?: string;
12
+ };
13
+ translations?: Record<string, any>;
14
+ unsubscribeTranslations?: Record<string, string>;
15
+ emailConfig?: {
16
+ logoUrl?: string;
17
+ logoAlt?: string;
18
+ footerText?: string;
19
+ };
20
+ }
21
+ /**
22
+ * Resolves a specific configuration value from priority sources:
23
+ * 1. LocalStorage (Dev overrides)
24
+ * 2. Component Props
25
+ * 3. Window Global (__JHITS_PLUGIN_PROPS__)
26
+ * 4. Default Value
27
+ */
28
+ export declare function resolveConfigValue<T>(key: string, propValue: T | undefined, defaultValue: T, pluginKey?: string): T;
29
+ /**
30
+ * Resolves all plugin configuration
31
+ */
32
+ export declare function resolvePluginConfig(props: any): ResolvedConfig;
33
+ //# sourceMappingURL=config-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-resolver.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/config-resolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,WAAW,cAAc;IAC3B,YAAY,EAAE,qBAAqB,EAAE,CAAC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAChC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,CAAC,GAAG,SAAS,EACxB,YAAY,EAAE,CAAC,EACf,SAAS,GAAE,MAA4B,GACxC,CAAC,CAqBH;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,GAAG,cAAc,CAS9D"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Newsletter Plugin Configuration Resolver
3
+ * Centralizes the logic for resolving plugin settings from multiple sources
4
+ */
5
+ /**
6
+ * Resolves a specific configuration value from priority sources:
7
+ * 1. LocalStorage (Dev overrides)
8
+ * 2. Component Props
9
+ * 3. Window Global (__JHITS_PLUGIN_PROPS__)
10
+ * 4. Default Value
11
+ */
12
+ export function resolveConfigValue(key, propValue, defaultValue, pluginKey = 'plugin-newsletter') {
13
+ if (typeof window === 'undefined')
14
+ return propValue ?? defaultValue;
15
+ // 1. Check LocalStorage (Dev priority)
16
+ try {
17
+ const saved = localStorage.getItem(`__JHITS_PLUGIN_NEWSLETTER_CONFIG__`);
18
+ if (saved) {
19
+ const config = JSON.parse(saved);
20
+ if (config[key] !== undefined)
21
+ return config[key];
22
+ }
23
+ }
24
+ catch (e) { }
25
+ // 2. Check Props
26
+ if (propValue !== undefined)
27
+ return propValue;
28
+ // 3. Check Window Global
29
+ const globalProps = window.__JHITS_PLUGIN_PROPS__?.[pluginKey] || window.__JHITS_PLUGIN_PROPS__?.['newsletter'];
30
+ if (globalProps && globalProps[key] !== undefined)
31
+ return globalProps[key];
32
+ // 4. Default
33
+ return defaultValue;
34
+ }
35
+ /**
36
+ * Resolves all plugin configuration
37
+ */
38
+ export function resolvePluginConfig(props) {
39
+ return {
40
+ customBlocks: resolveConfigValue('customBlocks', props.customBlocks, []),
41
+ darkMode: resolveConfigValue('darkMode', props.darkMode, true),
42
+ backgroundColors: resolveConfigValue('backgroundColors', props.backgroundColors, undefined),
43
+ translations: resolveConfigValue('translations', props.translations, undefined),
44
+ unsubscribeTranslations: resolveConfigValue('unsubscribeTranslations', props.unsubscribeTranslations, undefined),
45
+ emailConfig: resolveConfigValue('emailConfig', props.emailConfig, undefined),
46
+ };
47
+ }
@@ -5,9 +5,10 @@
5
5
  *
6
6
  * The registry is a singleton that starts empty and is populated by client apps
7
7
  */
8
- import { BlockTypeDefinition } from '../types/block';
8
+ import { BlockTypeDefinition, ClientBlockDefinition } from '../types/block';
9
9
  interface IBlockRegistry {
10
10
  register(definition: BlockTypeDefinition): void;
11
+ registerClientBlocks(definitions: ClientBlockDefinition[]): void;
11
12
  get(type: string): BlockTypeDefinition | undefined;
12
13
  getAll(): BlockTypeDefinition[];
13
14
  has(type: string): boolean;
@@ -19,7 +20,14 @@ interface IBlockRegistry {
19
20
  */
20
21
  declare class BlockRegistryImpl implements IBlockRegistry {
21
22
  private blocks;
23
+ constructor();
24
+ private registerDefaultBlocks;
22
25
  register(definition: BlockTypeDefinition): void;
26
+ /**
27
+ * Register multiple client blocks at once
28
+ * This is the primary method for client applications to register their blocks
29
+ */
30
+ registerClientBlocks(definitions: ClientBlockDefinition[]): void;
23
31
  get(type: string): BlockTypeDefinition | undefined;
24
32
  getAll(): BlockTypeDefinition[];
25
33
  has(type: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"BlockRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/BlockRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACH,mBAAmB,EAEtB,MAAM,gBAAgB,CAAC;AAGxB,UAAU,cAAc;IACpB,QAAQ,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAChD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAC;IACnD,MAAM,IAAI,mBAAmB,EAAE,CAAC;IAChC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,KAAK,IAAI,IAAI,CAAC;CACjB;AAED;;;GAGG;AACH,cAAM,iBAAkB,YAAW,cAAc;IAC7C,OAAO,CAAC,MAAM,CAA+C;IAE7D,QAAQ,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI;IAI/C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIlD,MAAM,IAAI,mBAAmB,EAAE;IAI/B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,KAAK,IAAI,IAAI;CAGhB;AAGD,eAAO,MAAM,aAAa,mBAA0B,CAAC;AACrD,eAAO,MAAM,aAAa,mBAAgB,CAAC"}
1
+ {"version":3,"file":"BlockRegistry.d.ts","sourceRoot":"","sources":["../../src/registry/BlockRegistry.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACH,mBAAmB,EACnB,qBAAqB,EAGxB,MAAM,gBAAgB,CAAC;AAwGxB,UAAU,cAAc;IACpB,QAAQ,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAChD,oBAAoB,CAAC,WAAW,EAAE,qBAAqB,EAAE,GAAG,IAAI,CAAC;IACjE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAC;IACnD,MAAM,IAAI,mBAAmB,EAAE,CAAC;IAChC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,KAAK,IAAI,IAAI,CAAC;CACjB;AAED;;;GAGG;AACH,cAAM,iBAAkB,YAAW,cAAc;IAC7C,OAAO,CAAC,MAAM,CAA+C;;IAO7D,OAAO,CAAC,qBAAqB;IAkD7B,QAAQ,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI;IAY/C;;;OAGG;IACH,oBAAoB,CAAC,WAAW,EAAE,qBAAqB,EAAE,GAAG,IAAI;IAsBhE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAWlD,MAAM,IAAI,mBAAmB,EAAE;IAI/B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,KAAK,IAAI,IAAI;CAIhB;AAGD,eAAO,MAAM,aAAa,mBAA0B,CAAC;AACrD,eAAO,MAAM,aAAa,mBAAgB,CAAC"}