@jhits/plugin-newsletter 0.0.7 → 0.0.8

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