@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
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
|
|
27
|
-
//
|
|
28
|
-
const
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
169
|
-
|
|
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
|
|
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;
|
|
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
|
-
|
|
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(`
|
|
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;
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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.
|
|
294
|
-
font-size:
|
|
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:
|
|
307
|
-
font-
|
|
308
|
-
font-size: 30px;
|
|
318
|
+
font-weight: bold;
|
|
319
|
+
font-size: 48px;
|
|
309
320
|
margin-bottom: 30px;
|
|
310
321
|
color: #1a2e26;
|
|
311
|
-
text-align:
|
|
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.
|
|
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"}
|