@jhits/plugin-newsletter 0.0.4 → 0.0.6

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 (173) hide show
  1. package/dist/api/handler.d.ts +51 -0
  2. package/dist/api/handler.d.ts.map +1 -0
  3. package/dist/api/handler.js +526 -0
  4. package/dist/api/router.d.ts +11 -0
  5. package/dist/api/router.d.ts.map +1 -0
  6. package/dist/api/router.js +82 -0
  7. package/dist/index.d.ts +46 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +222 -0
  10. package/dist/index.server.d.ts +10 -0
  11. package/dist/index.server.d.ts.map +1 -0
  12. package/dist/index.server.js +8 -0
  13. package/dist/init.d.ts +49 -0
  14. package/dist/init.d.ts.map +1 -0
  15. package/dist/init.js +42 -0
  16. package/dist/lib/blocks/BlockRenderer.d.ts +43 -0
  17. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -0
  18. package/dist/lib/blocks/BlockRenderer.js +48 -0
  19. package/dist/lib/email/EmailRenderer.d.ts +47 -0
  20. package/dist/lib/email/EmailRenderer.d.ts.map +1 -0
  21. package/dist/lib/email/EmailRenderer.js +359 -0
  22. package/dist/lib/email/index.d.ts +6 -0
  23. package/dist/lib/email/index.d.ts.map +1 -0
  24. package/dist/lib/email/index.js +4 -0
  25. package/dist/lib/mappers/apiMapper.d.ts +30 -0
  26. package/dist/lib/mappers/apiMapper.d.ts.map +1 -0
  27. package/dist/lib/mappers/apiMapper.js +36 -0
  28. package/dist/lib/utils/blockHelpers.d.ts +23 -0
  29. package/dist/lib/utils/blockHelpers.d.ts.map +1 -0
  30. package/dist/lib/utils/blockHelpers.js +65 -0
  31. package/dist/lib/utils/slugify.d.ts +14 -0
  32. package/dist/lib/utils/slugify.d.ts.map +1 -0
  33. package/dist/lib/utils/slugify.js +37 -0
  34. package/dist/registry/BlockRegistry.d.ts +31 -0
  35. package/dist/registry/BlockRegistry.d.ts.map +1 -0
  36. package/dist/registry/BlockRegistry.js +34 -0
  37. package/dist/registry/index.d.ts +5 -0
  38. package/dist/registry/index.d.ts.map +1 -0
  39. package/dist/registry/index.js +4 -0
  40. package/dist/state/EditorContext.d.ts +44 -0
  41. package/dist/state/EditorContext.d.ts.map +1 -0
  42. package/dist/state/EditorContext.js +212 -0
  43. package/dist/state/index.d.ts +10 -0
  44. package/dist/state/index.d.ts.map +1 -0
  45. package/dist/state/index.js +6 -0
  46. package/dist/state/reducer.d.ts +11 -0
  47. package/dist/state/reducer.d.ts.map +1 -0
  48. package/dist/state/reducer.js +488 -0
  49. package/dist/state/types.d.ts +157 -0
  50. package/dist/state/types.d.ts.map +1 -0
  51. package/dist/state/types.js +26 -0
  52. package/dist/types/block.d.ts +230 -0
  53. package/dist/types/block.d.ts.map +1 -0
  54. package/dist/types/block.js +8 -0
  55. package/dist/types/newsletter.d.ts +129 -0
  56. package/dist/types/newsletter.d.ts.map +1 -0
  57. package/dist/types/newsletter.js +4 -0
  58. package/dist/types/registry.d.ts +13 -0
  59. package/dist/types/registry.d.ts.map +1 -0
  60. package/dist/types/registry.js +4 -0
  61. package/dist/views/CanvasEditor/BlockWrapper.d.ts +23 -0
  62. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
  63. package/dist/views/CanvasEditor/BlockWrapper.js +44 -0
  64. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
  65. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
  66. package/dist/views/CanvasEditor/CanvasEditorView.js +139 -0
  67. package/dist/views/CanvasEditor/EditorBody.d.ts +24 -0
  68. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -0
  69. package/dist/views/CanvasEditor/EditorBody.js +21 -0
  70. package/dist/views/CanvasEditor/EditorHeader.d.ts +12 -0
  71. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
  72. package/dist/views/CanvasEditor/EditorHeader.js +47 -0
  73. package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts +10 -0
  74. package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
  75. package/dist/views/CanvasEditor/components/CustomBlockItem.js +36 -0
  76. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +25 -0
  77. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
  78. package/dist/views/CanvasEditor/components/EditorCanvas.js +397 -0
  79. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
  80. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
  81. package/dist/views/CanvasEditor/components/EditorLibrary.js +25 -0
  82. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +9 -0
  83. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
  84. package/dist/views/CanvasEditor/components/EditorSidebar.js +16 -0
  85. package/dist/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
  86. package/dist/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
  87. package/dist/views/CanvasEditor/components/ErrorBanner.js +8 -0
  88. package/dist/views/CanvasEditor/components/LibraryItem.d.ts +10 -0
  89. package/dist/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
  90. package/dist/views/CanvasEditor/components/LibraryItem.js +35 -0
  91. package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts +18 -0
  92. package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts.map +1 -0
  93. package/dist/views/CanvasEditor/components/SlashCommandDetector.js +164 -0
  94. package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts +22 -0
  95. package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts.map +1 -0
  96. package/dist/views/CanvasEditor/components/SlashCommandMenu.js +57 -0
  97. package/dist/views/CanvasEditor/components/index.d.ts +16 -0
  98. package/dist/views/CanvasEditor/components/index.d.ts.map +1 -0
  99. package/dist/views/CanvasEditor/components/index.js +9 -0
  100. package/dist/views/CanvasEditor/hooks/index.d.ts +7 -0
  101. package/dist/views/CanvasEditor/hooks/index.d.ts.map +1 -0
  102. package/dist/views/CanvasEditor/hooks/index.js +6 -0
  103. package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
  104. package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  105. package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.js +114 -0
  106. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts +5 -0
  107. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -0
  108. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +28 -0
  109. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
  110. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
  111. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +46 -0
  112. package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts +31 -0
  113. package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts.map +1 -0
  114. package/dist/views/CanvasEditor/hooks/useSlashCommand.js +87 -0
  115. package/dist/views/CanvasEditor/index.d.ts +12 -0
  116. package/dist/views/CanvasEditor/index.d.ts.map +1 -0
  117. package/dist/views/CanvasEditor/index.js +7 -0
  118. package/dist/views/NewsletterEditor.d.ts +16 -0
  119. package/dist/views/NewsletterEditor.d.ts.map +1 -0
  120. package/dist/views/NewsletterEditor.js +10 -0
  121. package/dist/views/NewsletterManager.d.ts +10 -0
  122. package/dist/views/NewsletterManager.d.ts.map +1 -0
  123. package/dist/views/NewsletterManager.js +95 -0
  124. package/dist/views/SettingsView.d.ts +10 -0
  125. package/dist/views/SettingsView.d.ts.map +1 -0
  126. package/dist/views/SettingsView.js +103 -0
  127. package/dist/views/SubscribersView.d.ts +10 -0
  128. package/dist/views/SubscribersView.d.ts.map +1 -0
  129. package/dist/views/SubscribersView.js +94 -0
  130. package/package.json +24 -23
  131. package/src/api/handler.ts +340 -1
  132. package/src/api/router.ts +35 -0
  133. package/src/index.tsx +284 -4
  134. package/src/index.tsx.patch +98 -0
  135. package/src/init.tsx +72 -0
  136. package/src/lib/blocks/BlockRenderer.tsx +125 -0
  137. package/src/lib/email/EmailRenderer.tsx +425 -0
  138. package/src/lib/email/index.ts +6 -0
  139. package/src/lib/mappers/apiMapper.ts +57 -0
  140. package/src/lib/utils/blockHelpers.ts +71 -0
  141. package/src/lib/utils/slugify.ts +43 -0
  142. package/src/registry/BlockRegistry.ts +53 -0
  143. package/src/registry/index.ts +5 -0
  144. package/src/state/EditorContext.tsx +279 -0
  145. package/src/state/index.ts +10 -0
  146. package/src/state/reducer.ts +561 -0
  147. package/src/state/types.ts +154 -0
  148. package/src/types/block.ts +275 -0
  149. package/src/types/newsletter.ts +114 -1
  150. package/src/types/registry.ts +14 -0
  151. package/src/views/CanvasEditor/BlockWrapper.tsx +143 -0
  152. package/src/views/CanvasEditor/CanvasEditorView.tsx +249 -0
  153. package/src/views/CanvasEditor/EditorBody.tsx +95 -0
  154. package/src/views/CanvasEditor/EditorHeader.tsx +139 -0
  155. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +83 -0
  156. package/src/views/CanvasEditor/components/EditorCanvas.tsx +674 -0
  157. package/src/views/CanvasEditor/components/EditorLibrary.tsx +120 -0
  158. package/src/views/CanvasEditor/components/EditorSidebar.tsx +156 -0
  159. package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
  160. package/src/views/CanvasEditor/components/LibraryItem.tsx +71 -0
  161. package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +196 -0
  162. package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +131 -0
  163. package/src/views/CanvasEditor/components/index.ts +16 -0
  164. package/src/views/CanvasEditor/hooks/index.ts +7 -0
  165. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +136 -0
  166. package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +34 -0
  167. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +54 -0
  168. package/src/views/CanvasEditor/hooks/useSlashCommand.ts +106 -0
  169. package/src/views/CanvasEditor/index.ts +12 -0
  170. package/src/views/NewsletterEditor.tsx +38 -0
  171. package/src/views/NewsletterManager.tsx +240 -0
  172. package/src/views/SettingsView.tsx +14 -14
  173. package/src/views/SubscribersView.tsx +20 -20
@@ -0,0 +1,488 @@
1
+ /**
2
+ * Newsletter Editor Reducer
3
+ * Pure function that handles state transitions
4
+ */
5
+ import { initialEditorState } from './types';
6
+ /**
7
+ * Generate a unique block ID
8
+ */
9
+ function generateBlockId() {
10
+ // Use crypto.randomUUID if available, otherwise fallback to timestamp-based
11
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
12
+ return crypto.randomUUID();
13
+ }
14
+ return `block-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
15
+ }
16
+ /**
17
+ * Clone a block with a new ID
18
+ */
19
+ function cloneBlock(block) {
20
+ return {
21
+ ...block,
22
+ id: generateBlockId(),
23
+ data: { ...block.data },
24
+ meta: block.meta ? { ...block.meta } : undefined,
25
+ children: block.children ? (Array.isArray(block.children) && block.children.length > 0 && typeof block.children[0] === 'object'
26
+ ? block.children.map(cloneBlock)
27
+ : [...block.children]) : undefined,
28
+ };
29
+ }
30
+ /**
31
+ * Find a block by ID recursively (including nested blocks)
32
+ */
33
+ function findBlockById(blocks, id) {
34
+ for (const block of blocks) {
35
+ if (block.id === id) {
36
+ return block;
37
+ }
38
+ if (block.children && Array.isArray(block.children) && block.children.length > 0) {
39
+ // Check if children are Block objects or IDs
40
+ if (typeof block.children[0] === 'object') {
41
+ const found = findBlockById(block.children, id);
42
+ if (found)
43
+ return found;
44
+ }
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Update blocks recursively to add a block to a container
51
+ */
52
+ function addBlockToContainer(blocks, containerId, newBlock, index) {
53
+ return blocks.map(block => {
54
+ // Check if this is the container (exact match or column container like "block-123-col-0")
55
+ const isContainer = block.id === containerId;
56
+ const isColumnContainer = containerId.startsWith(`${block.id}-col-`);
57
+ if (isContainer) {
58
+ // Direct container match
59
+ const currentChildren = Array.isArray(block.children)
60
+ ? (typeof block.children[0] === 'object'
61
+ ? block.children
62
+ : [])
63
+ : [];
64
+ const updatedChildren = [...currentChildren];
65
+ if (index !== undefined && index >= 0 && index <= updatedChildren.length) {
66
+ updatedChildren.splice(index, 0, newBlock);
67
+ }
68
+ else {
69
+ updatedChildren.push(newBlock);
70
+ }
71
+ return {
72
+ ...block,
73
+ children: updatedChildren,
74
+ };
75
+ }
76
+ else if (isColumnContainer) {
77
+ // Column container - extract column index and store in block meta
78
+ const columnIndex = parseInt(containerId.split('-col-')[1] || '0', 10);
79
+ newBlock.meta = {
80
+ ...newBlock.meta,
81
+ columnIndex,
82
+ };
83
+ const currentChildren = Array.isArray(block.children)
84
+ ? (typeof block.children[0] === 'object'
85
+ ? block.children
86
+ : [])
87
+ : [];
88
+ const updatedChildren = [...currentChildren];
89
+ if (index !== undefined && index >= 0 && index <= updatedChildren.length) {
90
+ updatedChildren.splice(index, 0, newBlock);
91
+ }
92
+ else {
93
+ updatedChildren.push(newBlock);
94
+ }
95
+ return {
96
+ ...block,
97
+ children: updatedChildren,
98
+ };
99
+ }
100
+ // Recursively search nested blocks
101
+ if (block.children && Array.isArray(block.children) && block.children.length > 0) {
102
+ if (typeof block.children[0] === 'object') {
103
+ return {
104
+ ...block,
105
+ children: addBlockToContainer(block.children, containerId, newBlock, index),
106
+ };
107
+ }
108
+ }
109
+ return block;
110
+ });
111
+ }
112
+ /**
113
+ * Update blocks recursively to update a nested block
114
+ */
115
+ function updateNestedBlock(blocks, id, data) {
116
+ return blocks.map(block => {
117
+ if (block.id === id) {
118
+ return {
119
+ ...block,
120
+ data: { ...block.data, ...data },
121
+ };
122
+ }
123
+ if (block.children && Array.isArray(block.children) && block.children.length > 0) {
124
+ if (typeof block.children[0] === 'object') {
125
+ return {
126
+ ...block,
127
+ children: updateNestedBlock(block.children, id, data),
128
+ };
129
+ }
130
+ }
131
+ return block;
132
+ });
133
+ }
134
+ /**
135
+ * Delete a nested block recursively
136
+ */
137
+ function deleteNestedBlock(blocks, id) {
138
+ return blocks.map(block => {
139
+ if (block.children && Array.isArray(block.children) && block.children.length > 0) {
140
+ if (typeof block.children[0] === 'object') {
141
+ const children = block.children;
142
+ // Check if any child matches
143
+ if (children.some(child => child.id === id)) {
144
+ return {
145
+ ...block,
146
+ children: children.filter(child => child.id !== id),
147
+ };
148
+ }
149
+ // Recursively search
150
+ return {
151
+ ...block,
152
+ children: deleteNestedBlock(children, id),
153
+ };
154
+ }
155
+ }
156
+ return block;
157
+ });
158
+ }
159
+ /**
160
+ * Move a nested block within a container
161
+ */
162
+ function moveNestedBlock(blocks, containerId, blockId, newIndex) {
163
+ return blocks.map(block => {
164
+ if (block.id === containerId && block.children && Array.isArray(block.children)) {
165
+ const children = typeof block.children[0] === 'object'
166
+ ? block.children
167
+ : [];
168
+ const currentIndex = children.findIndex(b => b.id === blockId);
169
+ if (currentIndex !== -1 && currentIndex !== newIndex) {
170
+ const newChildren = [...children];
171
+ const [moved] = newChildren.splice(currentIndex, 1);
172
+ newChildren.splice(newIndex, 0, moved);
173
+ return {
174
+ ...block,
175
+ children: newChildren,
176
+ };
177
+ }
178
+ }
179
+ if (block.children && Array.isArray(block.children) && block.children.length > 0) {
180
+ if (typeof block.children[0] === 'object') {
181
+ return {
182
+ ...block,
183
+ children: moveNestedBlock(block.children, containerId, blockId, newIndex),
184
+ };
185
+ }
186
+ }
187
+ return block;
188
+ });
189
+ }
190
+ /**
191
+ * Remove a block from the tree and return it
192
+ */
193
+ function removeBlockFromTree(blocks, id) {
194
+ for (let i = 0; i < blocks.length; i++) {
195
+ const block = blocks[i];
196
+ if (!block)
197
+ continue;
198
+ if (block.id === id) {
199
+ const removed = block;
200
+ const updated = [...blocks];
201
+ updated.splice(i, 1);
202
+ return { updatedBlocks: updated, removedBlock: removed };
203
+ }
204
+ if (block.children && Array.isArray(block.children) && block.children.length > 0) {
205
+ const firstChild = block.children[0];
206
+ if (firstChild && typeof firstChild === 'object') {
207
+ const result = removeBlockFromTree(block.children, id);
208
+ if (result.removedBlock) {
209
+ return {
210
+ updatedBlocks: blocks.map((b, idx) => idx === i
211
+ ? { ...b, children: result.updatedBlocks }
212
+ : b),
213
+ removedBlock: result.removedBlock,
214
+ };
215
+ }
216
+ }
217
+ }
218
+ }
219
+ return { updatedBlocks: blocks, removedBlock: null };
220
+ }
221
+ /**
222
+ * Move a block to a container
223
+ */
224
+ function moveBlockToContainer(blocks, blockId, containerId, newIndex) {
225
+ const { updatedBlocks, removedBlock } = removeBlockFromTree(blocks, blockId);
226
+ if (!removedBlock)
227
+ return blocks;
228
+ return addBlockToContainer(updatedBlocks, containerId, removedBlock, newIndex);
229
+ }
230
+ /**
231
+ * Editor Reducer
232
+ * Handles all state transitions for the editor
233
+ */
234
+ export function editorReducer(state, action) {
235
+ switch (action.type) {
236
+ case 'SET_BLOCKS':
237
+ return {
238
+ ...state,
239
+ blocks: action.payload,
240
+ isDirty: true,
241
+ };
242
+ case 'ADD_BLOCK': {
243
+ const { block, index, containerId } = action.payload;
244
+ const newBlock = {
245
+ ...block,
246
+ id: block.id || generateBlockId(),
247
+ };
248
+ // If containerId is provided, add to container's children
249
+ if (containerId) {
250
+ const updatedBlocks = addBlockToContainer(state.blocks, containerId, newBlock, index);
251
+ return {
252
+ ...state,
253
+ blocks: updatedBlocks,
254
+ selectedBlockId: newBlock.id,
255
+ isDirty: true,
256
+ };
257
+ }
258
+ // Otherwise, add to root level
259
+ const newBlocks = [...state.blocks];
260
+ if (index !== undefined && index >= 0 && index <= newBlocks.length) {
261
+ newBlocks.splice(index, 0, newBlock);
262
+ }
263
+ else {
264
+ newBlocks.push(newBlock);
265
+ }
266
+ return {
267
+ ...state,
268
+ blocks: newBlocks,
269
+ selectedBlockId: newBlock.id,
270
+ isDirty: true,
271
+ };
272
+ }
273
+ case 'UPDATE_BLOCK': {
274
+ const { id, data } = action.payload;
275
+ // Check if block is at root level
276
+ const rootBlock = state.blocks.find(block => block.id === id);
277
+ if (rootBlock) {
278
+ const newBlocks = state.blocks.map(block => block.id === id
279
+ ? {
280
+ ...block,
281
+ data: { ...block.data, ...data },
282
+ }
283
+ : block);
284
+ return {
285
+ ...state,
286
+ blocks: newBlocks,
287
+ isDirty: true,
288
+ };
289
+ }
290
+ // Otherwise, update nested block
291
+ const newBlocks = updateNestedBlock(state.blocks, id, data);
292
+ return {
293
+ ...state,
294
+ blocks: newBlocks,
295
+ isDirty: true,
296
+ };
297
+ }
298
+ case 'DELETE_BLOCK': {
299
+ const { id } = action.payload;
300
+ // Check if block is at root level
301
+ const rootBlock = state.blocks.find(block => block.id === id);
302
+ if (rootBlock) {
303
+ const newBlocks = state.blocks.filter(block => block.id !== id);
304
+ const wasSelected = state.selectedBlockId === id;
305
+ return {
306
+ ...state,
307
+ blocks: newBlocks,
308
+ selectedBlockId: wasSelected ? null : state.selectedBlockId,
309
+ isDirty: true,
310
+ };
311
+ }
312
+ // Otherwise, delete nested block
313
+ const newBlocks = deleteNestedBlock(state.blocks, id);
314
+ const wasSelected = state.selectedBlockId === id;
315
+ return {
316
+ ...state,
317
+ blocks: newBlocks,
318
+ selectedBlockId: wasSelected ? null : state.selectedBlockId,
319
+ isDirty: true,
320
+ };
321
+ }
322
+ case 'DUPLICATE_BLOCK': {
323
+ const { id } = action.payload;
324
+ const blockIndex = state.blocks.findIndex(block => block.id === id);
325
+ if (blockIndex === -1) {
326
+ return state;
327
+ }
328
+ const blockToDuplicate = state.blocks[blockIndex];
329
+ const duplicatedBlock = cloneBlock(blockToDuplicate);
330
+ const newBlocks = [...state.blocks];
331
+ newBlocks.splice(blockIndex + 1, 0, duplicatedBlock);
332
+ return {
333
+ ...state,
334
+ blocks: newBlocks,
335
+ selectedBlockId: duplicatedBlock.id,
336
+ isDirty: true,
337
+ };
338
+ }
339
+ case 'MOVE_BLOCK': {
340
+ const { id, newIndex, containerId: rawContainerId } = action.payload;
341
+ // Normalize 'root' string to undefined
342
+ const containerId = rawContainerId === 'root' || rawContainerId === undefined ? undefined : rawContainerId;
343
+ // If containerId is provided (and not 'root'), move to/within container
344
+ if (containerId) {
345
+ const containerBlock = findBlockById(state.blocks, containerId);
346
+ if (containerBlock && containerBlock.children && Array.isArray(containerBlock.children)) {
347
+ const children = typeof containerBlock.children[0] === 'object'
348
+ ? containerBlock.children
349
+ : [];
350
+ const currentIndex = children.findIndex(b => b.id === id);
351
+ if (currentIndex !== -1) {
352
+ // Block is already in this container - move within container
353
+ const newBlocks = moveNestedBlock(state.blocks, containerId, id, newIndex);
354
+ return {
355
+ ...state,
356
+ blocks: newBlocks,
357
+ isDirty: true,
358
+ };
359
+ }
360
+ }
361
+ // Block is not in this container - move it from wherever it is
362
+ const newBlocks = moveBlockToContainer(state.blocks, id, containerId, newIndex);
363
+ return {
364
+ ...state,
365
+ blocks: newBlocks,
366
+ isDirty: true,
367
+ };
368
+ }
369
+ // Moving to root level (containerId is undefined)
370
+ const currentIndex = state.blocks.findIndex(block => block.id === id);
371
+ if (currentIndex !== -1) {
372
+ // Block is at root level - move within root
373
+ const newBlocks = [...state.blocks];
374
+ const [moved] = newBlocks.splice(currentIndex, 1);
375
+ if (newIndex >= 0 && newIndex <= newBlocks.length) {
376
+ newBlocks.splice(newIndex, 0, moved);
377
+ }
378
+ else {
379
+ newBlocks.push(moved);
380
+ }
381
+ return {
382
+ ...state,
383
+ blocks: newBlocks,
384
+ isDirty: true,
385
+ };
386
+ }
387
+ // Block is nested somewhere - move it to root level
388
+ const { updatedBlocks, removedBlock } = removeBlockFromTree(state.blocks, id);
389
+ if (removedBlock) {
390
+ // Clear any nested metadata (like columnIndex)
391
+ const cleanedBlock = {
392
+ ...removedBlock,
393
+ meta: removedBlock.meta ? {
394
+ ...removedBlock.meta,
395
+ columnIndex: undefined,
396
+ } : undefined,
397
+ };
398
+ const newBlocks = [...updatedBlocks];
399
+ if (newIndex >= 0 && newIndex <= newBlocks.length) {
400
+ newBlocks.splice(newIndex, 0, cleanedBlock);
401
+ }
402
+ else {
403
+ newBlocks.push(cleanedBlock);
404
+ }
405
+ return {
406
+ ...state,
407
+ blocks: newBlocks,
408
+ isDirty: true,
409
+ };
410
+ }
411
+ return state;
412
+ }
413
+ case 'SET_TITLE':
414
+ return {
415
+ ...state,
416
+ title: action.payload,
417
+ isDirty: true,
418
+ };
419
+ case 'SET_SLUG':
420
+ return {
421
+ ...state,
422
+ slug: action.payload,
423
+ isDirty: true,
424
+ };
425
+ case 'SET_METADATA':
426
+ return {
427
+ ...state,
428
+ metadata: { ...state.metadata, ...action.payload },
429
+ isDirty: true,
430
+ };
431
+ case 'SET_STATUS':
432
+ return {
433
+ ...state,
434
+ status: action.payload,
435
+ isDirty: true,
436
+ };
437
+ case 'SET_FOCUS_MODE':
438
+ return {
439
+ ...state,
440
+ focusMode: action.payload,
441
+ };
442
+ case 'SELECT_BLOCK':
443
+ return {
444
+ ...state,
445
+ selectedBlockId: action.payload,
446
+ };
447
+ case 'SET_DRAGGED_BLOCK':
448
+ return {
449
+ ...state,
450
+ draggedBlockId: action.payload,
451
+ };
452
+ case 'LOAD_NEWSLETTER': {
453
+ const newsletter = action.payload;
454
+ return {
455
+ ...state,
456
+ blocks: newsletter.blocks,
457
+ title: newsletter.title,
458
+ slug: newsletter.slug,
459
+ metadata: newsletter.metadata,
460
+ status: newsletter.publication.status,
461
+ newsletterId: newsletter.id,
462
+ isDirty: false,
463
+ selectedBlockId: null,
464
+ };
465
+ }
466
+ case 'RESET_EDITOR':
467
+ return {
468
+ ...initialEditorState,
469
+ };
470
+ case 'MARK_CLEAN':
471
+ return {
472
+ ...state,
473
+ isDirty: false,
474
+ };
475
+ case 'MARK_DIRTY':
476
+ return {
477
+ ...state,
478
+ isDirty: true,
479
+ };
480
+ case 'UNDO':
481
+ case 'REDO':
482
+ case 'SAVE_HISTORY':
483
+ // These are handled by the context, not the reducer
484
+ return state;
485
+ default:
486
+ return state;
487
+ }
488
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * State Management Types for Newsletter Plugin
3
+ * Types for the editor state management system
4
+ */
5
+ import { Block } from '../types/block';
6
+ import { Newsletter, NewsletterStatus, NewsletterMetadata } from '../types/newsletter';
7
+ /**
8
+ * Editor State
9
+ * Represents the current state of the newsletter editor
10
+ */
11
+ export interface EditorState {
12
+ /** Array of blocks in the editor */
13
+ blocks: Block[];
14
+ /** Newsletter title */
15
+ title: string;
16
+ /** Newsletter slug */
17
+ slug: string;
18
+ /** Newsletter metadata */
19
+ metadata: NewsletterMetadata;
20
+ /** Publication status */
21
+ status: NewsletterStatus;
22
+ /** Whether the newsletter has unsaved changes */
23
+ isDirty: boolean;
24
+ /** Whether the editor is in focus mode */
25
+ focusMode: boolean;
26
+ /** Currently selected block ID */
27
+ selectedBlockId: string | null;
28
+ /** Currently dragged block ID */
29
+ draggedBlockId: string | null;
30
+ /** Newsletter ID (if editing existing newsletter) */
31
+ newsletterId: string | null;
32
+ }
33
+ /**
34
+ * Editor Actions
35
+ * Actions that can be dispatched to modify editor state
36
+ */
37
+ export type EditorAction = {
38
+ type: 'SET_BLOCKS';
39
+ payload: Block[];
40
+ } | {
41
+ type: 'ADD_BLOCK';
42
+ payload: {
43
+ block: Block;
44
+ index?: number;
45
+ containerId?: string;
46
+ };
47
+ } | {
48
+ type: 'UPDATE_BLOCK';
49
+ payload: {
50
+ id: string;
51
+ data: Partial<Block['data']>;
52
+ };
53
+ } | {
54
+ type: 'DELETE_BLOCK';
55
+ payload: {
56
+ id: string;
57
+ };
58
+ } | {
59
+ type: 'DUPLICATE_BLOCK';
60
+ payload: {
61
+ id: string;
62
+ };
63
+ } | {
64
+ type: 'MOVE_BLOCK';
65
+ payload: {
66
+ id: string;
67
+ newIndex: number;
68
+ containerId?: string;
69
+ };
70
+ } | {
71
+ type: 'SET_TITLE';
72
+ payload: string;
73
+ } | {
74
+ type: 'SET_SLUG';
75
+ payload: string;
76
+ } | {
77
+ type: 'SET_METADATA';
78
+ payload: Partial<NewsletterMetadata>;
79
+ } | {
80
+ type: 'SET_STATUS';
81
+ payload: NewsletterStatus;
82
+ } | {
83
+ type: 'SET_FOCUS_MODE';
84
+ payload: boolean;
85
+ } | {
86
+ type: 'SELECT_BLOCK';
87
+ payload: string | null;
88
+ } | {
89
+ type: 'SET_DRAGGED_BLOCK';
90
+ payload: string | null;
91
+ } | {
92
+ type: 'LOAD_NEWSLETTER';
93
+ payload: Newsletter;
94
+ } | {
95
+ type: 'RESET_EDITOR';
96
+ } | {
97
+ type: 'MARK_CLEAN';
98
+ } | {
99
+ type: 'MARK_DIRTY';
100
+ } | {
101
+ type: 'UNDO';
102
+ } | {
103
+ type: 'REDO';
104
+ } | {
105
+ type: 'SAVE_HISTORY';
106
+ };
107
+ /**
108
+ * Editor Context Value
109
+ * The value provided by the EditorContext
110
+ */
111
+ export interface EditorContextValue {
112
+ /** Current editor state */
113
+ state: EditorState;
114
+ /** Dispatch an action to modify state */
115
+ dispatch: (action: EditorAction) => void;
116
+ /** Enable dark mode for content area and wrappers */
117
+ darkMode: boolean;
118
+ /** Background colors for the editor */
119
+ backgroundColors?: {
120
+ /** Background color for light mode (REQUIRED) */
121
+ light: string;
122
+ /** Background color for dark mode (optional) */
123
+ dark?: string;
124
+ };
125
+ /** Helper functions for common operations */
126
+ helpers: {
127
+ /** Add a new block (supports nested containers via containerId) */
128
+ addBlock: (type: string, index?: number, containerId?: string) => void;
129
+ /** Update a block's data */
130
+ updateBlock: (id: string, data: Partial<Block['data']>) => void;
131
+ /** Delete a block */
132
+ deleteBlock: (id: string) => void;
133
+ /** Duplicate a block */
134
+ duplicateBlock: (id: string) => void;
135
+ /** Move a block to a new position (supports nested containers via containerId) */
136
+ moveBlock: (id: string, newIndex: number, containerId?: string) => void;
137
+ /** Load a newsletter into the editor */
138
+ loadNewsletter: (newsletter: Newsletter) => void;
139
+ /** Reset editor to initial state */
140
+ resetEditor: () => void;
141
+ /** Save current state (triggers save callback) */
142
+ save: () => Promise<void>;
143
+ /** Undo last action */
144
+ undo: () => void;
145
+ /** Redo last undone action */
146
+ redo: () => void;
147
+ };
148
+ /** Whether undo is available */
149
+ canUndo: boolean;
150
+ /** Whether redo is available */
151
+ canRedo: boolean;
152
+ }
153
+ /**
154
+ * Initial Editor State
155
+ */
156
+ export declare const initialEditorState: EditorState;
157
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/state/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * State Management Types for Newsletter Plugin
3
+ * Types for the editor state management system
4
+ */
5
+ /**
6
+ * Initial Editor State
7
+ */
8
+ export const initialEditorState = {
9
+ blocks: [],
10
+ title: '',
11
+ slug: '',
12
+ metadata: {
13
+ subject: '',
14
+ previewText: '',
15
+ lang: 'en',
16
+ recipientFilter: {
17
+ type: 'all',
18
+ },
19
+ },
20
+ status: 'draft',
21
+ isDirty: false,
22
+ focusMode: false,
23
+ selectedBlockId: null,
24
+ draggedBlockId: null,
25
+ newsletterId: null,
26
+ };