@jhits/plugin-newsletter 0.0.8 → 0.0.10
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/data/locales/en/common.json +12 -0
- package/data/locales/nl/common.json +12 -0
- package/data/locales/sv/common.json +12 -0
- package/dist/api/email-utils.d.ts +6 -0
- package/dist/api/email-utils.d.ts.map +1 -0
- package/dist/api/email-utils.js +134 -0
- package/dist/api/handler.d.ts +2 -47
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +2 -523
- package/dist/api/handlers/index.d.ts +12 -0
- package/dist/api/handlers/index.d.ts.map +1 -0
- package/dist/api/handlers/index.js +11 -0
- package/dist/api/handlers/newsletters.d.ts +11 -0
- package/dist/api/handlers/newsletters.d.ts.map +1 -0
- package/dist/api/handlers/newsletters.js +250 -0
- package/dist/api/handlers/send-newsletter.d.ts +8 -0
- package/dist/api/handlers/send-newsletter.d.ts.map +1 -0
- package/dist/api/handlers/send-newsletter.js +206 -0
- package/dist/api/handlers/settings.d.ts +11 -0
- package/dist/api/handlers/settings.d.ts.map +1 -0
- package/dist/api/handlers/settings.js +304 -0
- package/dist/api/handlers/subscribers.d.ts +10 -0
- package/dist/api/handlers/subscribers.d.ts.map +1 -0
- package/dist/api/handlers/subscribers.js +96 -0
- package/dist/api/handlers/upload.d.ts +7 -0
- package/dist/api/handlers/upload.d.ts.map +1 -0
- package/dist/api/handlers/upload.js +32 -0
- package/dist/api/handlers/welcome-email.d.ts +13 -0
- package/dist/api/handlers/welcome-email.d.ts.map +1 -0
- package/dist/api/handlers/welcome-email.js +142 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +45 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -16
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
- package/dist/lib/email/EmailRenderer.js +3 -7
- package/dist/lib/i18n.d.ts +16 -0
- package/dist/lib/i18n.d.ts.map +1 -0
- package/dist/lib/i18n.js +60 -0
- package/dist/state/EditorContext.d.ts +3 -1
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +1 -5
- package/dist/state/reducer.js +5 -5
- package/dist/types/newsletter.d.ts +1 -0
- package/dist/types/newsletter.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts +4 -2
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +73 -10
- package/dist/views/CanvasEditor/EditorHeader.d.ts +6 -1
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorHeader.js +66 -11
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +6 -2
- 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/useNewsletterLoader.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +41 -5
- package/dist/views/NewsletterEditor.d.ts +4 -2
- package/dist/views/NewsletterEditor.d.ts.map +1 -1
- package/dist/views/NewsletterEditor.js +2 -2
- package/dist/views/NewsletterManager.d.ts.map +1 -1
- package/dist/views/NewsletterManager.js +137 -8
- package/dist/views/components/SendNewsletterModal.d.ts +18 -0
- package/dist/views/components/SendNewsletterModal.d.ts.map +1 -0
- package/dist/views/components/SendNewsletterModal.js +107 -0
- package/dist/views/components/SmtpSettingsModal.d.ts +10 -0
- package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -0
- package/dist/views/components/SmtpSettingsModal.js +145 -0
- package/dist/views/components/TestEmailModal.d.ts +10 -0
- package/dist/views/components/TestEmailModal.d.ts.map +1 -0
- package/dist/views/components/TestEmailModal.js +99 -0
- package/package.json +20 -9
- package/templates/logo.png +0 -0
- package/templates/test-email.hbs +221 -0
- package/templates/welcome-email-legacy.hbs +35 -0
package/dist/api/router.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
'use server';
|
|
6
6
|
import { NextResponse } from 'next/server';
|
|
7
|
-
import { GET_SUBSCRIBERS, POST_SUBSCRIBE, GET_SUBSCRIBER, DELETE_SUBSCRIBER, GET_SETTINGS, POST_SETTINGS, GET_NEWSLETTERS, GET_NEWSLETTER, POST_NEWSLETTER, PUT_NEWSLETTER, DELETE_NEWSLETTER, } from './handler';
|
|
7
|
+
import { GET_SUBSCRIBERS, POST_SUBSCRIBE, GET_SUBSCRIBER, DELETE_SUBSCRIBER, GET_SETTINGS, POST_SETTINGS, GET_NEWSLETTERS, GET_NEWSLETTER, POST_NEWSLETTER, PUT_NEWSLETTER, DELETE_NEWSLETTER, GET_WELCOME_EMAIL_STATUS, GET_WELCOME_EMAIL, POST_WELCOME_EMAIL, GET_SMTP_SETTINGS, POST_SMTP_SETTINGS, POST_TEST_EMAIL, POST_LOGO_UPLOAD, POST_SEND_NEWSLETTER, GET_NEWSLETTER_FOR_SEND, } from './handler';
|
|
8
8
|
/**
|
|
9
9
|
* Handle newsletter API requests
|
|
10
10
|
*/
|
|
@@ -50,16 +50,24 @@ export async function handleNewsletterApi(req, path, config) {
|
|
|
50
50
|
return await POST_NEWSLETTER(req, config);
|
|
51
51
|
}
|
|
52
52
|
if (path[1]) {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const newsletterIdOrSlug = decodeURIComponent(path[1]);
|
|
54
|
+
// Special route: /api/plugin-newsletter/newsletters/[id]/send
|
|
55
|
+
if (path[2] === 'send' && method === 'POST') {
|
|
56
|
+
return await POST_SEND_NEWSLETTER(req, newsletterIdOrSlug, config);
|
|
57
|
+
}
|
|
58
|
+
// Special route: /api/plugin-newsletter/newsletters/[id]/send (GET for info)
|
|
59
|
+
if (path[2] === 'send' && method === 'GET') {
|
|
60
|
+
return await GET_NEWSLETTER_FOR_SEND(req, newsletterIdOrSlug, config);
|
|
61
|
+
}
|
|
62
|
+
// /api/plugin-newsletter/newsletters/[id-or-slug]
|
|
55
63
|
if (method === 'GET') {
|
|
56
|
-
return await GET_NEWSLETTER(req,
|
|
64
|
+
return await GET_NEWSLETTER(req, newsletterIdOrSlug, config);
|
|
57
65
|
}
|
|
58
66
|
if (method === 'PUT') {
|
|
59
|
-
return await PUT_NEWSLETTER(req,
|
|
67
|
+
return await PUT_NEWSLETTER(req, newsletterIdOrSlug, config);
|
|
60
68
|
}
|
|
61
69
|
if (method === 'DELETE') {
|
|
62
|
-
return await DELETE_NEWSLETTER(req,
|
|
70
|
+
return await DELETE_NEWSLETTER(req, newsletterIdOrSlug, config);
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
else {
|
|
@@ -72,6 +80,37 @@ export async function handleNewsletterApi(req, path, config) {
|
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
}
|
|
83
|
+
// Route: /api/plugin-newsletter/welcome-email
|
|
84
|
+
if (route === 'welcome-email') {
|
|
85
|
+
// Status endpoint: /api/plugin-newsletter/welcome-email/status
|
|
86
|
+
if (path[1] === 'status' && method === 'GET') {
|
|
87
|
+
return await GET_WELCOME_EMAIL_STATUS(req, config);
|
|
88
|
+
}
|
|
89
|
+
// Welcome email content: /api/plugin-newsletter/welcome-email
|
|
90
|
+
if (method === 'GET') {
|
|
91
|
+
return await GET_WELCOME_EMAIL(req, config);
|
|
92
|
+
}
|
|
93
|
+
if (method === 'POST' || method === 'PUT') {
|
|
94
|
+
return await POST_WELCOME_EMAIL(req, config);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Route: /api/plugin-newsletter/smtp
|
|
98
|
+
if (route === 'smtp') {
|
|
99
|
+
// Test email endpoint: /api/plugin-newsletter/smtp/test
|
|
100
|
+
if (path[1] === 'test' && method === 'POST') {
|
|
101
|
+
return await POST_TEST_EMAIL(req, config);
|
|
102
|
+
}
|
|
103
|
+
if (method === 'GET') {
|
|
104
|
+
return await GET_SMTP_SETTINGS(req, config);
|
|
105
|
+
}
|
|
106
|
+
if (method === 'POST' || method === 'PUT') {
|
|
107
|
+
return await POST_SMTP_SETTINGS(req, config);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Route: /api/plugin-newsletter/upload
|
|
111
|
+
if (route === 'upload' && method === 'POST') {
|
|
112
|
+
return await POST_LOGO_UPLOAD(req, config);
|
|
113
|
+
}
|
|
75
114
|
// Method not allowed
|
|
76
115
|
return NextResponse.json({ error: `Method ${method} not allowed for route: ${route || '/'}` }, { status: 405 });
|
|
77
116
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAOtD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAOtD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,WAAW,2CA8Q1D;AAGD,OAAO,EAAE,gBAAgB,IAAI,KAAK,EAAE,CAAC;AAGrC,YAAY,EACR,KAAK,EACL,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAClB,MAAM,eAAe,CAAC;AAGvB,YAAY,EACR,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,GAC1B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,MAAM,QAAQ,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -120,15 +120,20 @@ export default function NewsletterPlugin(props) {
|
|
|
120
120
|
case 'newsletters':
|
|
121
121
|
return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
|
|
122
122
|
case 'editor':
|
|
123
|
-
const
|
|
124
|
-
return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state) => {
|
|
123
|
+
const newsletterId = subPath[1];
|
|
124
|
+
return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
|
|
125
125
|
// Save to API - create new or update existing newsletter
|
|
126
|
-
const
|
|
126
|
+
const originalId = newsletterId || state.slug;
|
|
127
127
|
const apiData = editorStateToAPI(state);
|
|
128
|
-
//
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
// Include language in metadata if provided
|
|
129
|
+
if (extraData?.language) {
|
|
130
|
+
apiData.metadata = apiData.metadata || {};
|
|
131
|
+
apiData.metadata.lang = extraData.language;
|
|
132
|
+
}
|
|
133
|
+
// If we have an id, try to update first
|
|
134
|
+
if (originalId) {
|
|
135
|
+
console.log('[NewsletterPlugin] Attempting to update newsletter with id:', originalId);
|
|
136
|
+
const updateResponse = await fetch(`/api/plugin-newsletter/newsletters/${originalId}`, {
|
|
132
137
|
method: 'PUT',
|
|
133
138
|
headers: { 'Content-Type': 'application/json' },
|
|
134
139
|
credentials: 'include',
|
|
@@ -136,9 +141,9 @@ export default function NewsletterPlugin(props) {
|
|
|
136
141
|
});
|
|
137
142
|
if (updateResponse.ok) {
|
|
138
143
|
const result = await updateResponse.json();
|
|
139
|
-
// If the
|
|
140
|
-
if (result.
|
|
141
|
-
window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.
|
|
144
|
+
// If the id changed, update the URL
|
|
145
|
+
if (result.id && result.id !== originalId) {
|
|
146
|
+
window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
|
|
142
147
|
}
|
|
143
148
|
return result;
|
|
144
149
|
}
|
|
@@ -158,7 +163,7 @@ export default function NewsletterPlugin(props) {
|
|
|
158
163
|
throw new Error(errorMessage);
|
|
159
164
|
}
|
|
160
165
|
}
|
|
161
|
-
// Create new newsletter (either no
|
|
166
|
+
// Create new newsletter (either no id or update returned 404)
|
|
162
167
|
console.log('[NewsletterPlugin] Creating new newsletter');
|
|
163
168
|
const createResponse = await fetch('/api/plugin-newsletter/newsletters/new', {
|
|
164
169
|
method: 'POST',
|
|
@@ -177,16 +182,21 @@ export default function NewsletterPlugin(props) {
|
|
|
177
182
|
throw new Error(errorMessage);
|
|
178
183
|
}
|
|
179
184
|
const result = await createResponse.json();
|
|
180
|
-
// Update the URL to the new newsletter's
|
|
181
|
-
if (result.
|
|
182
|
-
window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.
|
|
185
|
+
// Update the URL to the new newsletter's id
|
|
186
|
+
if (result.id) {
|
|
187
|
+
window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
|
|
183
188
|
}
|
|
184
189
|
return result;
|
|
185
|
-
}, children: _jsx(NewsletterEditorView, {
|
|
190
|
+
}, children: _jsx(NewsletterEditorView, { newsletterId: newsletterId, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
|
|
186
191
|
case 'new':
|
|
187
|
-
return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state) => {
|
|
192
|
+
return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
|
|
188
193
|
// Save to API - create new newsletter
|
|
189
194
|
const apiData = editorStateToAPI(state);
|
|
195
|
+
// Include language in metadata if provided
|
|
196
|
+
if (extraData?.language) {
|
|
197
|
+
apiData.metadata = apiData.metadata || {};
|
|
198
|
+
apiData.metadata.lang = extraData.language;
|
|
199
|
+
}
|
|
190
200
|
const response = await fetch('/api/plugin-newsletter/newsletters/new', {
|
|
191
201
|
method: 'POST',
|
|
192
202
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -206,6 +216,22 @@ export default function NewsletterPlugin(props) {
|
|
|
206
216
|
}, children: _jsx(NewsletterEditorView, { siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
|
|
207
217
|
case 'subscribers':
|
|
208
218
|
return _jsx(SubscribersView, { siteId: siteId, locale: locale });
|
|
219
|
+
case 'welcome':
|
|
220
|
+
return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, isWelcomeEmail: true, onSave: async (state, extraData) => {
|
|
221
|
+
const apiData = editorStateToAPI(state);
|
|
222
|
+
const language = extraData?.language || locale || 'en';
|
|
223
|
+
const response = await fetch(`/api/plugin-newsletter/welcome-email?language=${language}`, {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
headers: { 'Content-Type': 'application/json' },
|
|
226
|
+
credentials: 'include',
|
|
227
|
+
body: JSON.stringify(apiData),
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
const error = await response.json();
|
|
231
|
+
throw new Error(error.message || 'Failed to save welcome email');
|
|
232
|
+
}
|
|
233
|
+
return await response.json();
|
|
234
|
+
}, children: _jsx(NewsletterEditorView, { siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors, isWelcomeEmail: true }) }));
|
|
209
235
|
case 'settings':
|
|
210
236
|
return _jsx(SettingsView, { siteId: siteId, locale: locale });
|
|
211
237
|
default:
|
|
@@ -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,
|
|
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"}
|
|
@@ -16,11 +16,8 @@ import { blockRegistry } from '../../registry/BlockRegistry';
|
|
|
16
16
|
*/
|
|
17
17
|
export function renderBlockToEmail(block, context = {}) {
|
|
18
18
|
const definition = blockRegistry.get(block.type);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
// Check if block has email-specific renderer (preferred)
|
|
23
|
-
if (definition.components.Email) {
|
|
19
|
+
// If block is registered and has Email component, use it
|
|
20
|
+
if (definition?.components?.Email) {
|
|
24
21
|
const childBlocks = block.children && Array.isArray(block.children) && block.children.length > 0
|
|
25
22
|
? (typeof block.children[0] === 'object' ? block.children : [])
|
|
26
23
|
: [];
|
|
@@ -31,8 +28,7 @@ export function renderBlockToEmail(block, context = {}) {
|
|
|
31
28
|
renderChild: (childBlock) => renderBlockToEmail(childBlock, context),
|
|
32
29
|
});
|
|
33
30
|
}
|
|
34
|
-
// Fallback
|
|
35
|
-
// For now, we'll create basic email-safe HTML based on block type
|
|
31
|
+
// Fallback: render using built-in email-safe HTML (handles all standard block types)
|
|
36
32
|
return renderBlockByType(block, context);
|
|
37
33
|
}
|
|
38
34
|
/**
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18n Utility
|
|
3
|
+
* Simple translation loader for the newsletter plugin
|
|
4
|
+
*/
|
|
5
|
+
export declare function getTranslations(locale: string): any;
|
|
6
|
+
export declare function getTestEmailTranslations(language: string): {
|
|
7
|
+
title: string;
|
|
8
|
+
greeting: string;
|
|
9
|
+
description: string;
|
|
10
|
+
successTitle: string;
|
|
11
|
+
successMessage: string;
|
|
12
|
+
recipientLabel: string;
|
|
13
|
+
footerText: string;
|
|
14
|
+
ignoreText: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/lib/i18n.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA4CH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CASnD;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACtB,CAcA"}
|
package/dist/lib/i18n.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* i18n Utility
|
|
3
|
+
* Simple translation loader for the newsletter plugin
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
function getLocalesDir() {
|
|
8
|
+
const possiblePaths = [
|
|
9
|
+
path.join(process.cwd(), 'node_modules', '@jhits', 'plugin-newsletter', 'data', 'locales'),
|
|
10
|
+
path.join(__dirname, '..', 'data', 'locales'),
|
|
11
|
+
path.join(__dirname, '..', '..', 'data', 'locales'),
|
|
12
|
+
];
|
|
13
|
+
for (const localesPath of possiblePaths) {
|
|
14
|
+
if (fs.existsSync(localesPath)) {
|
|
15
|
+
return localesPath;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return possiblePaths[0];
|
|
19
|
+
}
|
|
20
|
+
const localesDir = getLocalesDir();
|
|
21
|
+
const cache = {};
|
|
22
|
+
function loadLocale(locale) {
|
|
23
|
+
if (cache[locale]) {
|
|
24
|
+
return cache[locale];
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const localePath = path.join(localesDir, locale, 'common.json');
|
|
28
|
+
if (fs.existsSync(localePath)) {
|
|
29
|
+
const content = fs.readFileSync(localePath, 'utf-8');
|
|
30
|
+
cache[locale] = JSON.parse(content);
|
|
31
|
+
return cache[locale];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error(`[i18n] Failed to load locale ${locale}:`, error);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export function getTranslations(locale) {
|
|
40
|
+
const translations = loadLocale(locale);
|
|
41
|
+
if (translations) {
|
|
42
|
+
return translations;
|
|
43
|
+
}
|
|
44
|
+
const fallback = loadLocale('en');
|
|
45
|
+
return fallback || {};
|
|
46
|
+
}
|
|
47
|
+
export function getTestEmailTranslations(language) {
|
|
48
|
+
const translations = getTranslations(language);
|
|
49
|
+
const enTranslations = getTranslations('en');
|
|
50
|
+
return translations.testEmail || enTranslations.testEmail || {
|
|
51
|
+
title: 'SMTP Configuration Verified',
|
|
52
|
+
greeting: 'Hello,',
|
|
53
|
+
description: 'This is a test email to verify that your SMTP configuration is working correctly.',
|
|
54
|
+
successTitle: 'Success!',
|
|
55
|
+
successMessage: 'Your SMTP settings are configured correctly.',
|
|
56
|
+
recipientLabel: 'Email sent to:',
|
|
57
|
+
footerText: 'This is an automated test email from',
|
|
58
|
+
ignoreText: 'If you did not expect to receive this email, you can safely ignore it.',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -29,13 +29,15 @@ export interface EditorProviderProps {
|
|
|
29
29
|
/** Background color for dark mode (optional) */
|
|
30
30
|
dark?: string;
|
|
31
31
|
};
|
|
32
|
+
/** If true, this editor is for the welcome email */
|
|
33
|
+
isWelcomeEmail?: boolean;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Editor Provider
|
|
35
37
|
* Provides editor state and actions to child components
|
|
36
38
|
* Automatically registers client-provided blocks on mount
|
|
37
39
|
*/
|
|
38
|
-
export declare function EditorProvider({ children, initialState, onSave, customBlocks, darkMode, backgroundColors }: EditorProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
export declare function EditorProvider({ children, initialState, onSave, customBlocks, darkMode, backgroundColors, isWelcomeEmail }: EditorProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
39
41
|
/**
|
|
40
42
|
* Hook to access editor context
|
|
41
43
|
* @throws Error if used outside EditorProvider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorContext.d.ts","sourceRoot":"","sources":["../../src/state/EditorContext.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAmG,MAAM,OAAO,CAAC;AAExH,OAAO,EAAE,WAAW,EAAoC,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAG5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAMvD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,+BAA+B;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACpC,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;OAGG;IACH,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorContext.d.ts","sourceRoot":"","sources":["../../src/state/EditorContext.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAmG,MAAM,OAAO,CAAC;AAExH,OAAO,EAAE,WAAW,EAAoC,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAG5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAMvD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,+BAA+B;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACpC,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;OAGG;IACH,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,YAAiB,EACjB,QAAe,EACf,gBAAgB,EAChB,cAAc,EACjB,EAAE,mBAAmB,2CA8MrB;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,kBAAkB,CAM9C"}
|
|
@@ -16,7 +16,7 @@ const EditorContext = createContext(null);
|
|
|
16
16
|
* Provides editor state and actions to child components
|
|
17
17
|
* Automatically registers client-provided blocks on mount
|
|
18
18
|
*/
|
|
19
|
-
export function EditorProvider({ children, initialState, onSave, customBlocks = [], darkMode = true, backgroundColors }) {
|
|
19
|
+
export function EditorProvider({ children, initialState, onSave, customBlocks = [], darkMode = true, backgroundColors, isWelcomeEmail }) {
|
|
20
20
|
// Register client blocks on mount
|
|
21
21
|
useEffect(() => {
|
|
22
22
|
if (customBlocks && customBlocks.length > 0) {
|
|
@@ -116,12 +116,8 @@ export function EditorProvider({ children, initialState, onSave, customBlocks =
|
|
|
116
116
|
const resetEditor = useCallback(() => {
|
|
117
117
|
dispatch({ type: 'RESET_EDITOR' });
|
|
118
118
|
}, []);
|
|
119
|
-
// Helper: Save
|
|
120
|
-
// Uses stateRef to always get the latest state, avoiding stale closure issues
|
|
121
119
|
const save = useCallback(async () => {
|
|
122
120
|
if (onSave) {
|
|
123
|
-
// Use stateRef.current to get the absolute latest state
|
|
124
|
-
// This ensures we don't have stale closure issues with React state updates
|
|
125
121
|
await onSave(stateRef.current);
|
|
126
122
|
dispatch({ type: 'MARK_CLEAN' });
|
|
127
123
|
}
|
package/dist/state/reducer.js
CHANGED
|
@@ -453,11 +453,11 @@ export function editorReducer(state, action) {
|
|
|
453
453
|
const newsletter = action.payload;
|
|
454
454
|
return {
|
|
455
455
|
...state,
|
|
456
|
-
blocks: newsletter.blocks,
|
|
457
|
-
title: newsletter.title,
|
|
458
|
-
slug: newsletter.slug,
|
|
459
|
-
metadata: newsletter.metadata,
|
|
460
|
-
status: newsletter.publication
|
|
456
|
+
blocks: newsletter.blocks || [],
|
|
457
|
+
title: newsletter.title || 'Untitled',
|
|
458
|
+
slug: newsletter.slug || '',
|
|
459
|
+
metadata: newsletter.metadata || { subject: '', previewText: '' },
|
|
460
|
+
status: newsletter.publication?.status || 'draft',
|
|
461
461
|
newsletterId: newsletter.id,
|
|
462
462
|
isDirty: false,
|
|
463
463
|
selectedBlockId: null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newsletter.d.ts","sourceRoot":"","sources":["../../src/types/newsletter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,UAAU;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,IAAI,GAAG,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE;QACP,CAAC,GAAG,EAAE,MAAM,GAAG;YACX,KAAK,EAAE,MAAM,CAAC;YACd,OAAO,EAAE,MAAM,CAAC;SACnB,CAAC;KACL,CAAC;IACF,SAAS,CAAC,EAAE,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;AAE3E;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC,yBAAyB;IACzB,MAAM,EAAE,gBAAgB,CAAC;IAEzB,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,mBAAmB;IACnB,OAAO,EAAE,MAAM,CAAC;IAEhB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,oBAAoB;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,sDAAsD;IACtD,eAAe,CAAC,EAAE;QACd,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,QAAQ,CAAC;QACpC,KAAK,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACL;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACvB,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IAEX,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IAEd,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IAEb,8BAA8B;IAC9B,MAAM,EAAE,KAAK,EAAE,CAAC;IAEhB,uBAAuB;IACvB,WAAW,EAAE,yBAAyB,CAAC;IAEvC,0BAA0B;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;IAE7B,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAElB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"newsletter.d.ts","sourceRoot":"","sources":["../../src/types/newsletter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface CanvasEditorViewProps {
|
|
2
|
-
|
|
2
|
+
newsletterId?: string;
|
|
3
3
|
siteId: string;
|
|
4
4
|
locale: string;
|
|
5
5
|
/** Enable dark mode for content area and wrappers (default: true) */
|
|
@@ -9,6 +9,8 @@ export interface CanvasEditorViewProps {
|
|
|
9
9
|
light: string;
|
|
10
10
|
dark?: string;
|
|
11
11
|
};
|
|
12
|
+
/** If true, this editor is for the welcome email */
|
|
13
|
+
isWelcomeEmail?: boolean;
|
|
12
14
|
}
|
|
13
|
-
export declare function CanvasEditorView({
|
|
15
|
+
export declare function CanvasEditorView({ newsletterId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale, isWelcomeEmail }: CanvasEditorViewProps): import("react/jsx-runtime").JSX.Element;
|
|
14
16
|
//# sourceMappingURL=CanvasEditorView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CanvasEditorView.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/CanvasEditorView.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,qBAAqB;IAClC,
|
|
1
|
+
{"version":3,"file":"CanvasEditorView.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/CanvasEditorView.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,qBAAqB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,qBAAqB,2CAwT1J"}
|
|
@@ -10,7 +10,7 @@ import { SlashCommandMenu } from './components/SlashCommandMenu';
|
|
|
10
10
|
import { useSlashCommand } from './hooks/useSlashCommand';
|
|
11
11
|
import { useNewsletterLoader, useRegisteredBlocks, useKeyboardShortcuts } from './hooks';
|
|
12
12
|
import { blockRegistry } from '../../registry';
|
|
13
|
-
export function CanvasEditorView({
|
|
13
|
+
export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale, isWelcomeEmail }) {
|
|
14
14
|
const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, canUndo, canRedo } = useEditor();
|
|
15
15
|
const effectiveDarkMode = darkMode !== undefined ? darkMode : contextDarkMode;
|
|
16
16
|
const effectiveBackgroundColors = propsBackgroundColors || contextBackgroundColors;
|
|
@@ -18,21 +18,85 @@ export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: p
|
|
|
18
18
|
const [isPreviewMode, setIsPreviewMode] = useState(false);
|
|
19
19
|
const [isSaving, setIsSaving] = useState(false);
|
|
20
20
|
const [saveError, setSaveError] = useState(null);
|
|
21
|
+
// Primary language from SMTP settings (used for welcome email)
|
|
22
|
+
const [primaryLanguage, setPrimaryLanguage] = useState(locale || 'en');
|
|
23
|
+
const [isLoadingLanguage, setIsLoadingLanguage] = useState(true);
|
|
24
|
+
const [availableLanguages, setAvailableLanguages] = useState([locale || 'en']);
|
|
25
|
+
const [currentLanguage, setCurrentLanguage] = useState(locale || 'en');
|
|
21
26
|
// Get registered blocks
|
|
22
27
|
const registeredBlocks = useRegisteredBlocks();
|
|
23
|
-
// Newsletter loading
|
|
24
|
-
const { isLoadingNewsletter } = useNewsletterLoader(
|
|
28
|
+
// Newsletter loading - use current language for welcome email (wait until language settings are loaded)
|
|
29
|
+
const { isLoadingNewsletter } = useNewsletterLoader(newsletterId, state.newsletterId, (newsletter) => {
|
|
25
30
|
helpers.loadNewsletter(newsletter);
|
|
31
|
+
// Set current language from loaded newsletter metadata
|
|
32
|
+
if (newsletter.metadata?.lang && !isWelcomeEmail) {
|
|
33
|
+
setCurrentLanguage(newsletter.metadata.lang);
|
|
34
|
+
}
|
|
26
35
|
setTimeout(() => {
|
|
27
36
|
dispatch({ type: 'MARK_CLEAN' });
|
|
28
37
|
}, 0);
|
|
29
|
-
});
|
|
38
|
+
}, isWelcomeEmail, !isLoadingLanguage ? currentLanguage : undefined);
|
|
39
|
+
// Fetch primary language and available languages from SMTP/welcome-email settings
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const fetchLanguageSettings = async () => {
|
|
42
|
+
try {
|
|
43
|
+
const [smtpResponse, welcomeResponse] = await Promise.all([
|
|
44
|
+
fetch('/api/plugin-newsletter/smtp'),
|
|
45
|
+
fetch('/api/plugin-newsletter/welcome-email/status')
|
|
46
|
+
]);
|
|
47
|
+
const smtpData = await smtpResponse.json();
|
|
48
|
+
const welcomeData = await welcomeResponse.json();
|
|
49
|
+
const primary = smtpData.primaryLanguage || 'en';
|
|
50
|
+
const available = welcomeData.availableLanguages || [primary];
|
|
51
|
+
if (!available.includes(primary)) {
|
|
52
|
+
available.unshift(primary);
|
|
53
|
+
}
|
|
54
|
+
setPrimaryLanguage(primary);
|
|
55
|
+
setAvailableLanguages(available);
|
|
56
|
+
setCurrentLanguage(primary);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('Failed to fetch language settings:', error);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
setIsLoadingLanguage(false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
fetchLanguageSettings();
|
|
66
|
+
}, []);
|
|
67
|
+
// Handle language change for welcome email
|
|
68
|
+
const handleLanguageChange = async (newLanguage) => {
|
|
69
|
+
// Save current content first if dirty
|
|
70
|
+
if (state.isDirty) {
|
|
71
|
+
const confirmed = window.confirm('You have unsaved changes. Do you want to save them first?');
|
|
72
|
+
if (confirmed) {
|
|
73
|
+
await handleSave();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
setCurrentLanguage(newLanguage);
|
|
77
|
+
// Reload with new language
|
|
78
|
+
const response = await fetch(`/api/plugin-newsletter/welcome-email?language=${newLanguage}`);
|
|
79
|
+
if (response.ok) {
|
|
80
|
+
const newsletter = await response.json();
|
|
81
|
+
helpers.loadNewsletter(newsletter);
|
|
82
|
+
dispatch({ type: 'MARK_CLEAN' });
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
// Handle adding a new language
|
|
86
|
+
const handleAddLanguage = async (newLanguage) => {
|
|
87
|
+
if (availableLanguages.includes(newLanguage))
|
|
88
|
+
return;
|
|
89
|
+
// Add the new language to the list
|
|
90
|
+
setAvailableLanguages([...availableLanguages, newLanguage]);
|
|
91
|
+
// Switch to the new language (it will copy from primary language)
|
|
92
|
+
await handleLanguageChange(newLanguage);
|
|
93
|
+
};
|
|
30
94
|
// Ensure at least one paragraph block exists when creating new newsletter
|
|
31
95
|
useEffect(() => {
|
|
32
|
-
if (!
|
|
96
|
+
if (!newsletterId && !isWelcomeEmail && state.blocks.length === 0 && !isLoadingNewsletter) {
|
|
33
97
|
helpers.addBlock('paragraph', 0, undefined);
|
|
34
98
|
}
|
|
35
|
-
}, [
|
|
99
|
+
}, [newsletterId, state.blocks.length, isLoadingNewsletter, helpers, isWelcomeEmail]);
|
|
36
100
|
// Track if we just loaded a newsletter to prevent marking as dirty during cleanup
|
|
37
101
|
const justLoadedRef = useRef(false);
|
|
38
102
|
const previousIsLoadingRef = useRef(false);
|
|
@@ -48,7 +112,6 @@ export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: p
|
|
|
48
112
|
requestAnimationFrame(() => {
|
|
49
113
|
requestAnimationFrame(() => {
|
|
50
114
|
loadingCleanupTimerRef.current = setTimeout(() => {
|
|
51
|
-
console.log('[CanvasEditorView] Newsletter loading complete - ensuring clean state');
|
|
52
115
|
dispatch({ type: 'MARK_CLEAN' });
|
|
53
116
|
justLoadedRef.current = false;
|
|
54
117
|
loadingCleanupTimerRef.current = null;
|
|
@@ -124,16 +187,16 @@ export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: p
|
|
|
124
187
|
if (isLoadingNewsletter) {
|
|
125
188
|
return (_jsx("div", { className: "h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin mx-auto mb-4" }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400", children: "Loading newsletter..." })] }) }));
|
|
126
189
|
}
|
|
127
|
-
return (_jsx("div", { className: "h-full rounded-[2.5rem] w-full bg-dashboard-card text-dashboard-text flex flex-col font-sans transition-colors duration-300 overflow-hidden relative", children: _jsxs("main", { className: "flex flex-1 flex-col relative min-h-0", children: [_jsx(ErrorBanner, { error: saveError, onDismiss: () => setSaveError(null) }), _jsx(EditorHeader, { isPreviewMode: isPreviewMode, onPreviewToggle: () => setIsPreviewMode(!isPreviewMode), isSidebarOpen: isSidebarOpen, onSidebarToggle: () => setSidebarOpen(!isSidebarOpen), isSaving: isSaving, onSave: handleSave, onSaveError: (error) => {
|
|
190
|
+
return (_jsx("div", { className: "h-full rounded-[2.5rem] w-full bg-dashboard-card text-dashboard-text flex flex-col font-sans transition-colors duration-300 overflow-hidden relative", children: isLoadingLanguage ? (_jsx("div", { className: "h-full w-full flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin mx-auto mb-4" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Loading..." })] }) })) : (_jsxs("main", { className: "flex flex-1 flex-col relative min-h-0", children: [_jsx(ErrorBanner, { error: saveError, onDismiss: () => setSaveError(null) }), _jsx(EditorHeader, { isPreviewMode: isPreviewMode, onPreviewToggle: () => setIsPreviewMode(!isPreviewMode), isSidebarOpen: isSidebarOpen, onSidebarToggle: () => setSidebarOpen(!isSidebarOpen), isSaving: isSaving, onSave: handleSave, onSaveError: (error) => {
|
|
128
191
|
if (error) {
|
|
129
192
|
setSaveError(error);
|
|
130
193
|
}
|
|
131
194
|
else {
|
|
132
195
|
setSaveError(null);
|
|
133
196
|
}
|
|
134
|
-
}, isDirty: state.isDirty }), _jsxs("div", { className: "flex flex-1 relative overflow-hidden min-h-0 flex-nowrap", children: [_jsx(EditorCanvas, { isPreviewMode: isPreviewMode, contentBlocks: state.blocks, title: state.title, siteId: siteId, locale: locale, darkMode: effectiveDarkMode, backgroundColors: effectiveBackgroundColors, metadata: state.metadata, onTitleChange: (title) => dispatch({ type: 'SET_TITLE', payload: title }), onMetadataChange: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }), onBlockAdd: (type, index, containerId) => helpers.addBlock(type, index, containerId), onBlockUpdate: (id, data) => helpers.updateBlock(id, data), onBlockDelete: (id) => helpers.deleteBlock(id), onBlockMove: (id, newIndex, containerId) => helpers.moveBlock(id, newIndex, containerId), slashCommand: slashCommand }), !isPreviewMode && (_jsx("aside", { className: `transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto overflow-x-hidden h-full ${isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none'}`, children: _jsx(EditorSidebar, {
|
|
197
|
+
}, isDirty: state.isDirty, isWelcomeEmail: isWelcomeEmail, languages: availableLanguages, currentLanguage: currentLanguage, onLanguageChange: handleLanguageChange, onAddLanguage: handleAddLanguage }), _jsxs("div", { className: "flex flex-1 relative overflow-hidden min-h-0 flex-nowrap", children: [_jsx(EditorCanvas, { isPreviewMode: isPreviewMode, contentBlocks: state.blocks, title: state.title, siteId: siteId, locale: locale, darkMode: effectiveDarkMode, backgroundColors: effectiveBackgroundColors, metadata: state.metadata, onTitleChange: (title) => dispatch({ type: 'SET_TITLE', payload: title }), onMetadataChange: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }), onBlockAdd: (type, index, containerId) => helpers.addBlock(type, index, containerId), onBlockUpdate: (id, data) => helpers.updateBlock(id, data), onBlockDelete: (id) => helpers.deleteBlock(id), onBlockMove: (id, newIndex, containerId) => helpers.moveBlock(id, newIndex, containerId), slashCommand: slashCommand }), !isPreviewMode && (_jsx("aside", { className: `transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto overflow-x-hidden h-full ${isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none'}`, children: _jsx(EditorSidebar, { metadata: state.metadata, status: state.status, onMetadataUpdate: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }), defaultLanguage: primaryLanguage, isWelcomeEmail: isWelcomeEmail, languages: availableLanguages, currentLanguage: currentLanguage, onLanguageChange: handleLanguageChange }) }))] }), slashCommand.isOpen && slashCommand.position && (_jsx(SlashCommandMenu, { blocks: slashCommand.filteredBlocks, query: slashCommand.query, selectedIndex: slashCommand.selectedIndex, onSelect: (replaceBlockId) => {
|
|
135
198
|
// Use the replaceBlockId passed from the menu (which comes from state)
|
|
136
199
|
// This ensures we use the correct block ID that was set when the menu opened
|
|
137
200
|
slashCommand.selectCurrent(replaceBlockId);
|
|
138
|
-
}, position: slashCommand.position, onClose: slashCommand.closeMenu, replaceBlockId: slashCommand.replaceBlockId }))] }) }));
|
|
201
|
+
}, position: slashCommand.position, onClose: slashCommand.closeMenu, replaceBlockId: slashCommand.replaceBlockId }))] })) }));
|
|
139
202
|
}
|
|
@@ -7,6 +7,11 @@ export interface EditorHeaderProps {
|
|
|
7
7
|
onSave: () => Promise<void>;
|
|
8
8
|
onSaveError: (error: string | null) => void;
|
|
9
9
|
isDirty?: boolean;
|
|
10
|
+
isWelcomeEmail?: boolean;
|
|
11
|
+
languages?: string[];
|
|
12
|
+
currentLanguage?: string;
|
|
13
|
+
onLanguageChange?: (language: string) => void;
|
|
14
|
+
onAddLanguage?: (language: string) => void;
|
|
10
15
|
}
|
|
11
|
-
export declare function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty, }: EditorHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty, isWelcomeEmail, languages, currentLanguage, onLanguageChange, onAddLanguage, }: EditorHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
12
17
|
//# sourceMappingURL=EditorHeader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorHeader.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/EditorHeader.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EditorHeader.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/EditorHeader.tsx"],"names":[],"mappings":"AAqCA,MAAM,WAAW,iBAAiB;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED,wBAAgB,YAAY,CAAC,EACzB,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,QAAQ,EACR,MAAM,EACN,WAAW,EACX,OAAe,EACf,cAAsB,EACtB,SAAkB,EAClB,eAAsB,EACtB,gBAAgB,EAChB,aAAa,GAChB,EAAE,iBAAiB,2CA2LnB"}
|