@jhits/plugin-newsletter 0.0.16 → 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 (76) 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/send-newsletter.d.ts.map +1 -1
  4. package/dist/api/handlers/send-newsletter.js +54 -6
  5. package/dist/api/handlers/settings.d.ts.map +1 -1
  6. package/dist/api/handlers/settings.js +51 -1
  7. package/dist/index.d.ts +27 -10
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +15 -122
  10. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  11. package/dist/lib/blocks/BlockRenderer.js +14 -2
  12. package/dist/lib/email/EmailRenderer.d.ts +1 -0
  13. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  14. package/dist/lib/email/EmailRenderer.js +31 -19
  15. package/dist/lib/utils/config-resolver.d.ts +33 -0
  16. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  17. package/dist/lib/utils/config-resolver.js +47 -0
  18. package/dist/registry/BlockRegistry.d.ts +9 -1
  19. package/dist/registry/BlockRegistry.d.ts.map +1 -1
  20. package/dist/registry/BlockRegistry.js +126 -8
  21. package/dist/state/EditorContext.d.ts +11 -1
  22. package/dist/state/EditorContext.d.ts.map +1 -1
  23. package/dist/state/EditorContext.js +23 -5
  24. package/dist/state/types.d.ts +12 -0
  25. package/dist/state/types.d.ts.map +1 -1
  26. package/dist/types/block.d.ts +9 -0
  27. package/dist/types/block.d.ts.map +1 -1
  28. package/dist/types/newsletter.d.ts +2 -0
  29. package/dist/types/newsletter.d.ts.map +1 -1
  30. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  31. package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
  32. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  33. package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
  34. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  35. package/dist/views/CanvasEditor/EditorBody.js +1 -1
  36. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  37. package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
  38. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
  39. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  40. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  41. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
  42. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
  43. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
  44. package/dist/views/components/DomainPromptModal.d.ts +13 -0
  45. package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
  46. package/dist/views/components/DomainPromptModal.js +58 -0
  47. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
  48. package/dist/views/components/SendNewsletterModal.js +91 -22
  49. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
  50. package/dist/views/components/SmtpSettingsModal.js +10 -0
  51. package/dist/views/components/TestEmailModal.d.ts.map +1 -1
  52. package/dist/views/components/TestEmailModal.js +86 -17
  53. package/package.json +53 -9
  54. package/src/api/email-utils.ts +53 -4
  55. package/src/api/handlers/send-newsletter.ts +65 -6
  56. package/src/api/handlers/settings.ts +60 -2
  57. package/src/index.tsx +49 -155
  58. package/src/lib/blocks/BlockRenderer.tsx +16 -2
  59. package/src/lib/email/EmailRenderer.tsx +31 -20
  60. package/src/lib/utils/config-resolver.ts +71 -0
  61. package/src/registry/BlockRegistry.tsx +255 -0
  62. package/src/state/EditorContext.tsx +43 -8
  63. package/src/state/types.ts +16 -0
  64. package/src/types/block.ts +10 -0
  65. package/src/types/newsletter.ts +3 -0
  66. package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
  67. package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
  68. package/src/views/CanvasEditor/EditorBody.tsx +17 -13
  69. package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
  70. package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
  71. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
  72. package/src/views/components/DomainPromptModal.tsx +160 -0
  73. package/src/views/components/SendNewsletterModal.tsx +270 -184
  74. package/src/views/components/SmtpSettingsModal.tsx +11 -0
  75. package/src/views/components/TestEmailModal.tsx +235 -149
  76. package/src/registry/BlockRegistry.ts +0 -53
@@ -12,7 +12,9 @@ import { useEditor } from '../../state/EditorContext';
12
12
  import { SlashCommandDetector } from './components/SlashCommandDetector';
13
13
  export function BlockWrapper({ block, onUpdate, onDelete, onMoveUp, onMoveDown, allBlocks = [], slashCommand, blockIndex, onAddBlockBelow, }) {
14
14
  const [isHovered, setIsHovered] = useState(false);
15
- const { state } = useEditor();
15
+ const { state, translations, registeredBlockTypes } = useEditor();
16
+ // We use the block definition from registry
17
+ // The component will re-render when registeredBlockTypes changes
16
18
  const blockDefinition = blockRegistry.get(block.type);
17
19
  // Check if this is a container block
18
20
  const isContainer = isContainerBlock(block, blockRegistry);
@@ -25,6 +27,19 @@ export function BlockWrapper({ block, onUpdate, onDelete, onMoveUp, onMoveDown,
25
27
  return (_jsx("div", { className: "p-4 border border-red-300 dark:border-red-700 rounded-lg bg-red-50 dark:bg-red-900/20", children: _jsxs("p", { className: "text-sm text-red-600 dark:text-red-400", children: ["Unknown block type: ", block.type] }) }));
26
28
  }
27
29
  const EditComponent = blockDefinition.components.Edit;
30
+ // Helper to check if block is empty (for easier deletion)
31
+ const isBlockEmpty = () => {
32
+ if (block.type === 'paragraph' || block.type === 'heading') {
33
+ const text = block.data.text || '';
34
+ const html = block.data.html || '';
35
+ return text.trim() === '' && html.trim() === '';
36
+ }
37
+ if (block.type === 'list') {
38
+ const items = block.data.items || [];
39
+ return items.length === 0 || (items.length === 1 && (typeof items[0] === 'string' ? items[0].trim() === '' : items[0].text?.trim() === ''));
40
+ }
41
+ return false;
42
+ };
28
43
  // Store block ID when hovering for paste context
29
44
  useEffect(() => {
30
45
  if (isHovered) {
@@ -35,9 +50,15 @@ export function BlockWrapper({ block, onUpdate, onDelete, onMoveUp, onMoveDown,
35
50
  }, [isHovered, block.id]);
36
51
  return (_jsxs("div", { className: "group relative flex items-start gap-1 py-0.5", onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "data-block-wrapper": true, "data-block-id": block.id, children: [_jsx("div", { className: "flex-1 min-w-0 email-block-content", children: block.type === 'paragraph' && slashCommand ? (_jsx(SlashCommandDetector, { blockId: block.id, blockIndex: blockIndex || 0, blockType: block.type, content: block.data.html || block.data.text || '', onContentChange: (content) => {
37
52
  onUpdate({ html: content, text: content.replace(/<[^>]*>/g, '') });
38
- }, slashCommand: slashCommand, onAddBlockBelow: onAddBlockBelow, children: _jsx("div", { className: "email-block-wrapper", children: _jsx(EditComponent, { block: block, onUpdate: onUpdate, onDelete: onDelete, isSelected: state.selectedBlockId === block.id, childBlocks: childBlocks }) }) })) : (_jsx("div", { className: "email-block-wrapper", children: _jsx(EditComponent, { block: block, onUpdate: onUpdate, onDelete: onDelete, isSelected: state.selectedBlockId === block.id, childBlocks: childBlocks }) })) }), _jsx("div", { className: `flex-shrink-0 w-6 flex items-start justify-center pt-1 transition-opacity duration-150 ${isHovered ? 'opacity-100' : 'opacity-0'}`, children: _jsx("button", { onClick: (e) => {
53
+ }, slashCommand: slashCommand, onAddBlockBelow: onAddBlockBelow, children: _jsx("div", { className: "email-block-wrapper", children: _jsx(EditComponent, { block: block, onUpdate: onUpdate, onDelete: onDelete, isSelected: state.selectedBlockId === block.id, childBlocks: childBlocks, context: {
54
+ locale: state.metadata.lang || 'en',
55
+ translations
56
+ } }) }) })) : (_jsx("div", { className: "email-block-wrapper", children: _jsx(EditComponent, { block: block, onUpdate: onUpdate, onDelete: onDelete, isSelected: state.selectedBlockId === block.id, childBlocks: childBlocks, context: {
57
+ locale: state.metadata.lang || 'en',
58
+ translations
59
+ } }) })) }), _jsx("div", { className: `flex-shrink-0 w-6 flex items-start justify-center pt-1 transition-opacity duration-150 ${isHovered ? 'opacity-100' : 'opacity-0'}`, children: _jsx("button", { onClick: (e) => {
39
60
  e.stopPropagation();
40
- if (confirm('Delete this block?')) {
61
+ if (isBlockEmpty() || confirm('Delete this block?')) {
41
62
  onDelete();
42
63
  }
43
64
  }, className: "p-1 -mr-1 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors", title: "Delete block", children: _jsx(Trash2, { size: 16, className: "text-neutral-400 dark:text-neutral-500 hover:text-red-500 dark:hover:text-red-400 transition-colors" }) }) })] }));
@@ -1 +1 @@
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,2CA4U1J"}
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,2CA6Z1J"}
@@ -11,7 +11,7 @@ import { useSlashCommand } from './hooks/useSlashCommand';
11
11
  import { useNewsletterLoader, useRegisteredBlocks, useKeyboardShortcuts } from './hooks';
12
12
  import { blockRegistry } from '../../registry';
13
13
  export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale, isWelcomeEmail }) {
14
- const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, canUndo, canRedo } = useEditor();
14
+ const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, emailConfig: contextEmailConfig, canUndo, canRedo } = useEditor();
15
15
  const effectiveDarkMode = darkMode !== undefined ? darkMode : contextDarkMode;
16
16
  const effectiveBackgroundColors = propsBackgroundColors || contextBackgroundColors;
17
17
  const [isSidebarOpen, setSidebarOpen] = useState(true);
@@ -23,6 +23,12 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
23
23
  const [isLoadingLanguage, setIsLoadingLanguage] = useState(true);
24
24
  const [availableLanguages, setAvailableLanguages] = useState([locale || 'en']);
25
25
  const [currentLanguage, setCurrentLanguage] = useState(locale || 'en');
26
+ // Global branding settings from SMTP
27
+ const [globalUnsubscribeTranslations, setGlobalUnsubscribeTranslations] = useState({
28
+ en: '',
29
+ nl: '',
30
+ sv: '',
31
+ });
26
32
  // Get registered blocks
27
33
  const registeredBlocks = useRegisteredBlocks();
28
34
  // Newsletter loading - wait for language settings to be loaded first
@@ -65,6 +71,24 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
65
71
  setPrimaryLanguage(primary);
66
72
  setAvailableLanguages(available);
67
73
  setCurrentLanguage(primary);
74
+ // Load global unsubscribe translations
75
+ if (smtpData.unsubscribeTranslations) {
76
+ setGlobalUnsubscribeTranslations(smtpData.unsubscribeTranslations);
77
+ }
78
+ // Update emailConfig with logo from SMTP settings if available
79
+ if (smtpData.logoUrl) {
80
+ if (typeof window !== 'undefined') {
81
+ if (!window.__JHITS_PLUGIN_PROPS__)
82
+ window.__JHITS_PLUGIN_PROPS__ = {};
83
+ if (!window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'])
84
+ window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'] = {};
85
+ const currentConfig = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].emailConfig || {};
86
+ window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].emailConfig = {
87
+ ...currentConfig,
88
+ logoUrl: smtpData.logoUrl
89
+ };
90
+ }
91
+ }
68
92
  }
69
93
  catch (error) {
70
94
  console.error('Failed to fetch language settings:', error);
@@ -75,6 +99,39 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
75
99
  };
76
100
  fetchLanguageSettings();
77
101
  }, []);
102
+ // Handle global unsubscribe text update
103
+ const handleGlobalUnsubscribeUpdate = async (text) => {
104
+ const newTranslations = {
105
+ ...globalUnsubscribeTranslations,
106
+ [currentLanguage]: text
107
+ };
108
+ // Optimistic update
109
+ setGlobalUnsubscribeTranslations(newTranslations);
110
+ // Update window props for immediate preview reflection
111
+ if (typeof window !== 'undefined') {
112
+ if (!window.__JHITS_PLUGIN_PROPS__)
113
+ window.__JHITS_PLUGIN_PROPS__ = {};
114
+ if (!window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'])
115
+ window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'] = {};
116
+ window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].unsubscribeTranslations = newTranslations;
117
+ }
118
+ try {
119
+ // We need current SMTP settings to save along with the new translation
120
+ const smtpResponse = await fetch('/api/plugin-newsletter/smtp');
121
+ const smtpData = await smtpResponse.json();
122
+ await fetch('/api/plugin-newsletter/smtp', {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({
126
+ ...smtpData,
127
+ unsubscribeTranslations: newTranslations
128
+ }),
129
+ });
130
+ }
131
+ catch (error) {
132
+ console.error('Failed to save global unsubscribe translation:', error);
133
+ }
134
+ };
78
135
  // Handle language change for welcome email
79
136
  const handleLanguageChange = async (newLanguage) => {
80
137
  // Save current content first if dirty
@@ -164,17 +221,14 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
164
221
  return;
165
222
  }
166
223
  // Replace the block in place by updating the blocks array directly
167
- // This preserves the position and ID
168
224
  const newBlocks = state.blocks.map((block, idx) => {
169
225
  if (idx === blockIndex) {
170
- // Replace this block with the new type
171
226
  return {
172
227
  ...block,
173
228
  type: blockType,
174
229
  data: { ...blockDefinition.defaultData },
175
230
  };
176
231
  }
177
- // Keep all other blocks unchanged
178
232
  return block;
179
233
  });
180
234
  dispatch({ type: 'SET_BLOCKS', payload: newBlocks });
@@ -185,12 +239,26 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
185
239
  }, [state.blocks, dispatch]);
186
240
  // Slash command hook
187
241
  const slashCommand = useSlashCommand(registeredBlocks, handleSlashCommandSelect);
242
+ // Lock dashboard scroll when editor is active to ensure our internal scrolling works
243
+ useEffect(() => {
244
+ if (typeof window === 'undefined')
245
+ return;
246
+ const dashboardContainer = document.querySelector('.custom-scrollbar.overflow-y-auto');
247
+ const originalOverflow = dashboardContainer ? dashboardContainer.style.overflow : '';
248
+ if (dashboardContainer) {
249
+ dashboardContainer.style.overflow = 'hidden';
250
+ }
251
+ return () => {
252
+ if (dashboardContainer) {
253
+ dashboardContainer.style.overflow = originalOverflow;
254
+ }
255
+ };
256
+ }, []);
188
257
  // Handle save
189
258
  const handleSave = async () => {
190
259
  setIsSaving(true);
191
260
  setSaveError(null);
192
261
  try {
193
- // Always pass language for saving (both welcome email and regular newsletters)
194
262
  await helpers.save({ language: currentLanguage });
195
263
  setIsSaving(false);
196
264
  }
@@ -208,16 +276,8 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
208
276
  if (isLoadingNewsletter) {
209
277
  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..." })] }) }));
210
278
  }
211
- 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) => {
212
- if (error) {
213
- setSaveError(error);
214
- }
215
- else {
216
- setSaveError(null);
217
- }
218
- }, 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) => {
219
- // Use the replaceBlockId passed from the menu (which comes from state)
220
- // This ensures we use the correct block ID that was set when the menu opened
221
- slashCommand.selectCurrent(replaceBlockId);
222
- }, position: slashCommand.position, onClose: slashCommand.closeMenu, replaceBlockId: slashCommand.replaceBlockId }))] })) }));
279
+ return (_jsx("div", { className: "absolute inset-0 flex flex-col font-sans transition-colors duration-300 overflow-hidden bg-white dark:bg-neutral-950", 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 overflow-hidden", children: [_jsx(ErrorBanner, { error: saveError, onDismiss: () => setSaveError(null) }), _jsx("div", { className: "flex-none z-30 border-b border-dashboard-border", children: _jsx(EditorHeader, { isPreviewMode: isPreviewMode, onPreviewToggle: () => setIsPreviewMode(!isPreviewMode), isSidebarOpen: isSidebarOpen, onSidebarToggle: () => setSidebarOpen(!isSidebarOpen), isSaving: isSaving, onSave: handleSave, onSaveError: (error) => setSaveError(error || null), isDirty: state.isDirty, isWelcomeEmail: isWelcomeEmail, languages: availableLanguages, currentLanguage: currentLanguage, onLanguageChange: handleLanguageChange, onAddLanguage: handleAddLanguage }) }), _jsxs("div", { className: "flex-1 flex relative overflow-hidden min-h-0 flex-nowrap", children: [_jsx("div", { className: "flex-1 overflow-hidden h-full", children: _jsx(EditorCanvas, { isPreviewMode: isPreviewMode, contentBlocks: state.blocks, title: state.title, siteId: siteId, locale: currentLanguage, 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: `flex-none transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto custom-scrollbar h-full ${isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none border-l-0'}`, 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, globalUnsubscribeText: globalUnsubscribeTranslations[currentLanguage], onGlobalUnsubscribeUpdate: handleGlobalUnsubscribeUpdate }) }))] }), slashCommand.isOpen && slashCommand.position && (_jsx("div", { className: "fixed z-50 transition-all duration-200", style: {
280
+ left: slashCommand.position.left,
281
+ top: slashCommand.position.top
282
+ }, children: _jsx(SlashCommandMenu, { blocks: slashCommand.filteredBlocks, query: slashCommand.query, selectedIndex: slashCommand.selectedIndex, onSelect: (replaceBlockId) => slashCommand.selectCurrent(replaceBlockId), position: slashCommand.position, onClose: slashCommand.closeMenu, replaceBlockId: slashCommand.replaceBlockId }) }))] })) }));
223
283
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EditorBody.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/EditorBody.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,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,4BAA4B;IAC5B,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;CACrD;AAED,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,QAAe,EACf,gBAAgB,EAChB,YAAY,GACf,EAAE,eAAe,2CAuDjB"}
1
+ {"version":3,"file":"EditorBody.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/EditorBody.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,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,4BAA4B;IAC5B,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;CACrD;AAED,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,QAAe,EACf,gBAAgB,EAChB,YAAY,GACf,EAAE,eAAe,2CA2DjB"}
@@ -17,5 +17,5 @@ export function EditorBody({ blocks, onBlockAdd, onBlockUpdate, onBlockDelete, o
17
17
  }, 0);
18
18
  }
19
19
  };
20
- return (_jsxs("div", { className: "relative", children: [blocks.map((block, index) => (_jsxs(React.Fragment, { children: [_jsx("div", { className: "relative h-2 group/add-button", children: _jsx("button", { onClick: () => onBlockAdd('paragraph', index), className: "absolute left-0 top-1/2 -translate-y-1/2 w-6 h-6 flex items-center justify-center opacity-0 group-hover/add-button:opacity-100 transition-opacity hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded", title: "Add block", children: _jsx(Plus, { size: 14, className: "text-neutral-400 dark:text-neutral-500" }) }) }), _jsx("div", { className: "relative", children: _jsx(BlockWrapper, { block: block, onUpdate: (data) => onBlockUpdate(block.id, data), onDelete: () => handleDelete(block.id, index), onMoveUp: index > 0 ? () => onBlockMove(block.id, index - 1) : undefined, onMoveDown: index < blocks.length - 1 ? () => onBlockMove(block.id, index + 1) : undefined, allBlocks: blocks, slashCommand: slashCommand, blockIndex: index, onAddBlockBelow: (blockType) => onBlockAdd(blockType, index + 1) }) })] }, block.id))), _jsx("div", { className: "relative h-8 group/add-button-bottom mt-2", children: _jsx("button", { onClick: () => onBlockAdd('paragraph', blocks.length), className: "absolute left-0 top-1/2 -translate-y-1/2 w-6 h-6 flex items-center justify-center opacity-0 group-hover/add-button-bottom:opacity-100 transition-opacity hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded", title: "Add block", children: _jsx(Plus, { size: 14, className: "text-neutral-400 dark:text-neutral-500" }) }) })] }));
20
+ return (_jsxs("div", { className: "relative", children: [blocks.map((block, index) => (_jsxs(React.Fragment, { children: [_jsx("div", { className: "relative h-4 group/add-button", children: _jsxs("div", { className: "absolute inset-x-0 top-1/2 -translate-y-1/2 flex items-center justify-center", children: [_jsx("div", { className: "w-full h-[1px] bg-neutral-100 dark:bg-neutral-800 opacity-0 group-hover/add-button:opacity-100 transition-opacity" }), _jsx("button", { onClick: () => onBlockAdd('paragraph', index), className: "mx-4 flex-shrink-0 w-6 h-6 flex items-center justify-center opacity-0 group-hover/add-button:opacity-100 transition-all hover:bg-primary hover:text-white bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 shadow-sm rounded-full z-10", title: "Add block here", children: _jsx(Plus, { size: 14 }) }), _jsx("div", { className: "w-full h-[1px] bg-neutral-100 dark:bg-neutral-800 opacity-0 group-hover/add-button:opacity-100 transition-opacity" })] }) }), _jsx("div", { className: "relative", children: _jsx(BlockWrapper, { block: block, onUpdate: (data) => onBlockUpdate(block.id, data), onDelete: () => handleDelete(block.id, index), onMoveUp: index > 0 ? () => onBlockMove(block.id, index - 1) : undefined, onMoveDown: index < blocks.length - 1 ? () => onBlockMove(block.id, index + 1) : undefined, allBlocks: blocks, slashCommand: slashCommand, blockIndex: index, onAddBlockBelow: (blockType) => onBlockAdd(blockType, index + 1) }) })] }, block.id))), _jsx("div", { className: "relative h-12 mt-4 group/add-bottom", children: _jsx("button", { onClick: () => onBlockAdd('paragraph', blocks.length), className: "absolute left-0 top-1/2 -translate-y-1/2 w-8 h-8 flex items-center justify-center opacity-20 group-hover/add-bottom:opacity-100 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-full transition-all text-neutral-500", title: "Add block at the end", children: _jsx(Plus, { size: 18 }) }) })] }));
21
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EditorCanvas.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/EditorCanvas.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAiMhE,MAAM,WAAW,iBAAiB;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,KAAK,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;IACnE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;CACrD;AAED,wBAAgB,YAAY,CAAC,EACzB,aAAa,EACb,aAAa,EACb,KAAK,EACL,MAAM,EACN,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,YAAY,GACf,EAAE,iBAAiB,2CAuPnB"}
1
+ {"version":3,"file":"EditorCanvas.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/EditorCanvas.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAsNhE,MAAM,WAAW,iBAAiB;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,KAAK,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;IACnE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;CACrD;AAED,wBAAgB,YAAY,CAAC,EACzB,aAAa,EACb,aAAa,EACb,KAAK,EACL,MAAM,EACN,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,YAAY,GACf,EAAE,iBAAiB,2CAoQnB"}