@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,24 @@
1
+ /**
2
+ * Editor Body Component
3
+ * Simplified Notion-style editor with single paragraph by default
4
+ */
5
+ import { Block } from '../../types/block';
6
+ import type { useSlashCommand } from './hooks/useSlashCommand';
7
+ export interface EditorBodyProps {
8
+ blocks: Block[];
9
+ onBlockAdd: (type: string, index: number, containerId?: string) => void;
10
+ onBlockUpdate: (id: string, data: Partial<Block['data']>) => void;
11
+ onBlockDelete: (id: string) => void;
12
+ onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
13
+ /** Enable dark mode for content area and wrappers (default: true) */
14
+ darkMode?: boolean;
15
+ /** Background colors for the editor */
16
+ backgroundColors?: {
17
+ light: string;
18
+ dark?: string;
19
+ };
20
+ /** Slash command handler */
21
+ slashCommand?: ReturnType<typeof useSlashCommand>;
22
+ }
23
+ export declare function EditorBody({ blocks, onBlockAdd, onBlockUpdate, onBlockDelete, onBlockMove, darkMode, backgroundColors, slashCommand, }: EditorBodyProps): import("react/jsx-runtime").JSX.Element;
24
+ //# sourceMappingURL=EditorBody.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Editor Body Component
3
+ * Simplified Notion-style editor with single paragraph by default
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import React from 'react';
8
+ import { Plus } from 'lucide-react';
9
+ import { BlockWrapper } from './BlockWrapper';
10
+ export function EditorBody({ blocks, onBlockAdd, onBlockUpdate, onBlockDelete, onBlockMove, darkMode = true, backgroundColors, slashCommand, }) {
11
+ const handleDelete = (blockId, index) => {
12
+ onBlockDelete(blockId);
13
+ // If deleting the last block, ensure we still have at least one paragraph
14
+ if (blocks.length === 1) {
15
+ setTimeout(() => {
16
+ onBlockAdd('paragraph', 0);
17
+ }, 0);
18
+ }
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" }) }) })] }));
21
+ }
@@ -0,0 +1,12 @@
1
+ export interface EditorHeaderProps {
2
+ isPreviewMode: boolean;
3
+ onPreviewToggle: () => void;
4
+ isSidebarOpen: boolean;
5
+ onSidebarToggle: () => void;
6
+ isSaving: boolean;
7
+ onSave: () => Promise<void>;
8
+ onSaveError: (error: string | null) => void;
9
+ isDirty?: boolean;
10
+ }
11
+ export declare function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty, }: EditorHeaderProps): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=EditorHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorHeader.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/EditorHeader.tsx"],"names":[],"mappings":"AAMA,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;CACrB;AAED,wBAAgB,YAAY,CAAC,EACzB,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,QAAQ,EACR,MAAM,EACN,WAAW,EACX,OAAe,GAClB,EAAE,iBAAiB,2CAgHnB"}
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { ArrowLeft, Settings2, Save, Edit, Eye } from 'lucide-react';
5
+ import { useEditor } from '../../state/EditorContext';
6
+ export function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty = false, }) {
7
+ const { state } = useEditor();
8
+ const [saveError, setSaveError] = useState(null);
9
+ const handleSave = async () => {
10
+ try {
11
+ setSaveError(null);
12
+ await onSave();
13
+ }
14
+ catch (error) {
15
+ console.error('[EditorHeader] Failed to save newsletter:', error);
16
+ let errorMessage = error.message || 'Failed to save newsletter';
17
+ if (errorMessage.includes('Unauthorized')) {
18
+ errorMessage = 'You are not authorized to save. Please log in again.';
19
+ }
20
+ setSaveError(errorMessage);
21
+ onSaveError(errorMessage);
22
+ }
23
+ };
24
+ return (_jsxs("header", { className: "flex items-center justify-between px-6 py-3 bg-dashboard-sidebar backdrop-blur-md border-b border-dashboard-border flex-none shrink-0", children: [_jsx("div", { className: "flex items-center gap-6", children: _jsx("button", { onClick: () => {
25
+ if (isDirty) {
26
+ const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave? Your changes will be lost.');
27
+ if (!confirmed) {
28
+ return;
29
+ }
30
+ }
31
+ window.location.href = '/dashboard/newsletter';
32
+ }, className: "text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white transition-colors", children: _jsx(ArrowLeft, { size: 20, strokeWidth: 1.5 }) }) }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center bg-dashboard-bg border border-dashboard-border rounded-full p-1 gap-1", children: [_jsxs("button", { onClick: () => {
33
+ if (isPreviewMode) {
34
+ onPreviewToggle();
35
+ }
36
+ }, className: `flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] uppercase tracking-widest font-bold transition-all ${!isPreviewMode
37
+ ? 'bg-primary text-white shadow-sm'
38
+ : 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'}`, title: "Edit mode", children: [_jsx(Edit, { size: 12, strokeWidth: 2.5 }), _jsx("span", { children: "Edit" })] }), _jsxs("button", { onClick: () => {
39
+ if (!isPreviewMode) {
40
+ onPreviewToggle();
41
+ }
42
+ }, className: `flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] uppercase tracking-widest font-bold transition-all ${isPreviewMode
43
+ ? 'bg-primary text-white shadow-sm'
44
+ : 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'}`, title: "Preview mode", children: [_jsx(Eye, { size: 12, strokeWidth: 2.5 }), _jsx("span", { children: "Preview" })] })] }), _jsxs("button", { onClick: onSidebarToggle, className: `flex items-center gap-2 text-[10px] uppercase tracking-widest font-black transition-all ${isSidebarOpen ? 'text-dashboard-text' : 'text-neutral-500 dark:text-neutral-400'}`, children: [_jsx(Settings2, { size: 16, strokeWidth: 1.5 }), "Settings"] }), _jsx("button", { onClick: handleSave, disabled: isSaving || !isDirty, className: `inline-flex items-center gap-2 px-4 py-2 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg ${isSaving || !isDirty
45
+ ? 'bg-neutral-400 text-white cursor-not-allowed'
46
+ : 'bg-primary text-white hover:bg-primary/90'}`, children: isSaving ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { size: 14 }), isDirty ? 'Save Changes' : 'Saved'] })) })] })] }));
47
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ export interface CustomBlockItemProps {
3
+ blockType: string;
4
+ name: string;
5
+ description?: string;
6
+ icon: React.ReactNode;
7
+ onAddBlock?: (blockType: string) => void;
8
+ }
9
+ export declare function CustomBlockItem({ blockType, name, description, icon, onAddBlock }: CustomBlockItemProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=CustomBlockItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomBlockItem.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/CustomBlockItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAGhD,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,IAAI,EACJ,WAAW,EACX,IAAI,EACJ,UAAU,EACb,EAAE,oBAAoB,2CA+DtB"}
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef } from 'react';
4
+ import { GripVertical } from 'lucide-react';
5
+ export function CustomBlockItem({ blockType, name, description, icon, onAddBlock }) {
6
+ const [hasDragged, setHasDragged] = useState(false);
7
+ const mouseDownRef = useRef(null);
8
+ const handleDragStart = (e) => {
9
+ e.dataTransfer.setData('block-type', blockType);
10
+ e.dataTransfer.effectAllowed = 'move';
11
+ setHasDragged(true);
12
+ };
13
+ const handleMouseDown = (e) => {
14
+ mouseDownRef.current = { x: e.clientX, y: e.clientY };
15
+ setHasDragged(false);
16
+ };
17
+ const handleMouseMove = (e) => {
18
+ if (mouseDownRef.current) {
19
+ const dx = Math.abs(e.clientX - mouseDownRef.current.x);
20
+ const dy = Math.abs(e.clientY - mouseDownRef.current.y);
21
+ if (dx > 5 || dy > 5) {
22
+ setHasDragged(true);
23
+ }
24
+ }
25
+ };
26
+ const handleClick = (e) => {
27
+ if (!hasDragged && onAddBlock) {
28
+ e.preventDefault();
29
+ e.stopPropagation();
30
+ onAddBlock(blockType);
31
+ }
32
+ mouseDownRef.current = null;
33
+ setTimeout(() => setHasDragged(false), 100);
34
+ };
35
+ return (_jsxs("div", { draggable: true, onDragStart: handleDragStart, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onClick: handleClick, className: "p-4 rounded-xl border border-dashboard-border bg-dashboard-bg hover:border-primary cursor-pointer transition-all group", title: description, children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "text-neutral-500 dark:text-neutral-400 group-hover:text-primary dark:group-hover:text-primary transition-colors", children: icon }), _jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider text-neutral-700 dark:text-neutral-300 group-hover:text-neutral-950 dark:group-hover:text-white transition-colors", children: name })] }), _jsx(GripVertical, { size: 12, className: "text-neutral-400 dark:text-neutral-500 group-hover:text-neutral-600 dark:group-hover:text-neutral-400" })] }), description && (_jsx("p", { className: "text-[9px] text-neutral-500 dark:text-neutral-400 leading-relaxed line-clamp-2", children: description }))] }));
36
+ }
@@ -0,0 +1,25 @@
1
+ import type { Block } from '../../../types/block';
2
+ import type { NewsletterMetadata } from '../../../types/newsletter';
3
+ import type { useSlashCommand } from '../hooks/useSlashCommand';
4
+ export interface EditorCanvasProps {
5
+ isPreviewMode: boolean;
6
+ contentBlocks: Block[];
7
+ title: string;
8
+ siteId: string;
9
+ locale: string;
10
+ darkMode: boolean;
11
+ backgroundColors?: {
12
+ light: string;
13
+ dark?: string;
14
+ };
15
+ metadata?: NewsletterMetadata;
16
+ onTitleChange: (title: string) => void;
17
+ onMetadataChange?: (metadata: Partial<NewsletterMetadata>) => void;
18
+ onBlockAdd: (type: string, index: number, containerId?: string) => void;
19
+ onBlockUpdate: (id: string, data: Partial<Block['data']>) => void;
20
+ onBlockDelete: (id: string) => void;
21
+ onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
22
+ slashCommand?: ReturnType<typeof useSlashCommand>;
23
+ }
24
+ export declare function EditorCanvas({ isPreviewMode, contentBlocks, title, siteId, locale, darkMode, backgroundColors, metadata, onTitleChange, onMetadataChange, onBlockAdd, onBlockUpdate, onBlockDelete, onBlockMove, slashCommand, }: EditorCanvasProps): import("react/jsx-runtime").JSX.Element;
25
+ //# sourceMappingURL=EditorCanvas.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,397 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useRef, useEffect, useState } from 'react';
4
+ import { EditorBody } from '../EditorBody';
5
+ import { generateNewsletterEmailHtml } from '../../../lib/email/EmailRenderer';
6
+ // Add email-like styling for editor blocks
7
+ const emailEditorStyles = `
8
+ .email-editor-content p,
9
+ .email-editor-content .text-md {
10
+ font-size: 15px !important;
11
+ line-height: 1.8 !important;
12
+ color: #1a2e26 !important;
13
+ margin: 0 0 15px 0 !important;
14
+ font-family: 'Georgia', serif !important;
15
+ }
16
+
17
+ .email-editor-content h1,
18
+ .email-editor-content h2,
19
+ .email-editor-content h3,
20
+ .email-editor-content h4,
21
+ .email-editor-content h5,
22
+ .email-editor-content h6 {
23
+ font-family: 'Georgia', serif !important;
24
+ font-weight: bold !important;
25
+ color: #1a2e26 !important;
26
+ margin: 20px 0 10px 0 !important;
27
+ line-height: 1.3 !important;
28
+ }
29
+
30
+ .email-editor-content h1 {
31
+ font-size: 32px !important;
32
+ }
33
+
34
+ .email-editor-content h2 {
35
+ font-size: 24px !important;
36
+ }
37
+
38
+ .email-editor-content h3 {
39
+ font-size: 20px !important;
40
+ }
41
+
42
+ .email-editor-content h4,
43
+ .email-editor-content h5,
44
+ .email-editor-content h6 {
45
+ font-size: 18px !important;
46
+ }
47
+
48
+ .email-editor-content ul,
49
+ .email-editor-content ol {
50
+ margin: 15px 0 !important;
51
+ padding-left: 25px !important;
52
+ color: #1a2e26 !important;
53
+ font-size: 15px !important;
54
+ line-height: 1.8 !important;
55
+ }
56
+
57
+ .email-editor-content li {
58
+ margin: 8px 0 !important;
59
+ padding-left: 5px !important;
60
+ line-height: 1.8 !important;
61
+ color: #1a2e26 !important;
62
+ }
63
+
64
+ .email-editor-content img {
65
+ max-width: 100% !important;
66
+ height: auto !important;
67
+ display: block !important;
68
+ margin: 20px 0 !important;
69
+ }
70
+
71
+ .email-block-wrapper {
72
+ font-family: 'Georgia', serif;
73
+ color: #1a2e26;
74
+ }
75
+
76
+ /* Ensure all blocks are visible */
77
+ .email-block-content > * {
78
+ display: block !important;
79
+ visibility: visible !important;
80
+ opacity: 1 !important;
81
+ }
82
+
83
+ /* Heading input styling to match preview */
84
+ .email-block-content input[type="text"] {
85
+ font-family: 'Georgia', serif !important;
86
+ color: #1a2e26 !important;
87
+ background: transparent !important;
88
+ border: none !important;
89
+ outline: none !important;
90
+ width: 100% !important;
91
+ padding: 0 !important;
92
+ }
93
+
94
+ .email-block-content input[type="text"]::placeholder {
95
+ color: #a1a1aa !important;
96
+ }
97
+
98
+ /* Table block styling */
99
+ .email-block-content table {
100
+ width: 100% !important;
101
+ border-collapse: collapse !important;
102
+ margin: 20px 0 !important;
103
+ display: table !important;
104
+ visibility: visible !important;
105
+ opacity: 1 !important;
106
+ }
107
+
108
+ .email-block-content table th,
109
+ .email-block-content table td {
110
+ padding: 12px !important;
111
+ border: 1px solid #e5e7eb !important;
112
+ text-align: left !important;
113
+ color: #1a2e26 !important;
114
+ font-size: 15px !important;
115
+ line-height: 1.8 !important;
116
+ background-color: transparent !important;
117
+ }
118
+
119
+ .email-block-content table th {
120
+ background-color: #f9fafb !important;
121
+ font-weight: bold !important;
122
+ }
123
+
124
+ /* Table container styling */
125
+ .email-block-content div[class*="overflow-hidden"],
126
+ .email-block-content div[class*="rounded"] {
127
+ display: block !important;
128
+ visibility: visible !important;
129
+ opacity: 1 !important;
130
+ }
131
+
132
+ /* List block styling */
133
+ .email-block-content ul,
134
+ .email-block-content ol {
135
+ display: block !important;
136
+ margin: 15px 0 !important;
137
+ padding-left: 25px !important;
138
+ visibility: visible !important;
139
+ opacity: 1 !important;
140
+ }
141
+
142
+ .email-block-content li {
143
+ display: list-item !important;
144
+ margin: 8px 0 !important;
145
+ visibility: visible !important;
146
+ opacity: 1 !important;
147
+ }
148
+
149
+ /* List container styling */
150
+ .email-block-content div[class*="bg-white"][class*="rounded"] {
151
+ display: block !important;
152
+ visibility: visible !important;
153
+ opacity: 1 !important;
154
+ background-color: #ffffff !important;
155
+ border: 1px solid #e5e7eb !important;
156
+ }
157
+
158
+ /* Recipe block styling */
159
+ .email-block-content div[class*="recipe"],
160
+ .email-block-content div[class*="Recipe"] {
161
+ display: block !important;
162
+ visibility: visible !important;
163
+ opacity: 1 !important;
164
+ }
165
+
166
+ /* Ensure all container divs are visible */
167
+ .email-block-content > div {
168
+ display: block !important;
169
+ visibility: visible !important;
170
+ opacity: 1 !important;
171
+ }
172
+
173
+ .email-block-content div[class*="bg-white"],
174
+ .email-block-content div[class*="border"] {
175
+ display: block !important;
176
+ visibility: visible !important;
177
+ opacity: 1 !important;
178
+ }
179
+
180
+ /* Ensure buttons and inputs are visible */
181
+ .email-block-content button,
182
+ .email-block-content input {
183
+ visibility: visible !important;
184
+ opacity: 1 !important;
185
+ }
186
+
187
+ /* Override any hidden or invisible states */
188
+ .email-block-content [style*="display: none"],
189
+ .email-block-content [style*="visibility: hidden"],
190
+ .email-block-content [style*="opacity: 0"] {
191
+ display: block !important;
192
+ visibility: visible !important;
193
+ opacity: 1 !important;
194
+ }
195
+ `;
196
+ export function EditorCanvas({ isPreviewMode, contentBlocks, title, siteId, locale, darkMode, backgroundColors, metadata, onTitleChange, onMetadataChange, onBlockAdd, onBlockUpdate, onBlockDelete, onBlockMove, slashCommand, }) {
197
+ const titleRef = useRef(null);
198
+ // Inject email-like styles for editor
199
+ useEffect(() => {
200
+ const styleId = 'email-editor-styles';
201
+ if (!document.getElementById(styleId)) {
202
+ const style = document.createElement('style');
203
+ style.id = styleId;
204
+ style.textContent = emailEditorStyles;
205
+ document.head.appendChild(style);
206
+ }
207
+ }, []);
208
+ // Handle Title Auto-resize
209
+ useEffect(() => {
210
+ if (titleRef.current) {
211
+ titleRef.current.style.height = 'auto';
212
+ titleRef.current.style.height = `${titleRef.current.scrollHeight}px`;
213
+ }
214
+ }, [title]);
215
+ return (_jsx("div", { className: "flex-1 overflow-y-auto overflow-x-hidden pb-40 custom-scrollbar selection:bg-primary/20 dark:selection:bg-primary/30 min-h-0", style: {
216
+ backgroundColor: backgroundColors
217
+ ? (darkMode && backgroundColors.dark
218
+ ? backgroundColors.dark
219
+ : backgroundColors.light)
220
+ : '#faf9f6',
221
+ }, children: isPreviewMode ? (_jsx("div", { className: "mx-auto transition-all duration-500 max-w-[800px] px-6 py-6 w-full", children: _jsx(EmailPreview, { title: title, blocks: contentBlocks, siteId: siteId, locale: locale, metadata: metadata }) })) : (_jsx("div", { className: "mx-auto transition-all duration-500 max-w-[800px] px-6 py-6 w-full", children: _jsx("div", { className: "bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-900 dark:to-neutral-800 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-700 shadow-xl", children: _jsxs("div", { className: "bg-white dark:bg-neutral-800 rounded-lg shadow-2xl overflow-hidden", children: [_jsxs("div", { className: "bg-white dark:bg-neutral-800 border-b border-neutral-200 dark:border-neutral-700", children: [_jsxs("div", { className: "px-4 py-2 flex items-center justify-between border-b border-neutral-100 dark:border-neutral-700", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded", title: "Close", children: _jsx("svg", { className: "w-4 h-4 text-neutral-600 dark:text-neutral-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), _jsx("button", { className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded", title: "Minimize", children: _jsx("svg", { className: "w-4 h-4 text-neutral-600 dark:text-neutral-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) }) }), _jsx("button", { className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded", title: "Fullscreen", children: _jsx("svg", { className: "w-4 h-4 text-neutral-600 dark:text-neutral-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" }) }) })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-medium", children: "Compose Email" }) })] }), _jsx("div", { className: "px-4 py-3 border-b border-neutral-100 dark:border-neutral-700", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0", children: "To:" }), _jsx("input", { type: "text", value: "All Subscribers", readOnly: true, className: "flex-1 text-sm bg-transparent border-none outline-none text-neutral-700 dark:text-neutral-300 placeholder:text-neutral-400" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0", children: "Subject:" }), _jsx("input", { type: "text", value: metadata?.subject || '', onChange: (e) => onMetadataChange?.({
222
+ subject: e.target.value,
223
+ previewText: metadata?.previewText,
224
+ lang: metadata?.lang,
225
+ recipientFilter: metadata?.recipientFilter,
226
+ }), placeholder: "Enter email subject...", className: "flex-1 text-sm bg-transparent border-none outline-none text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-400 font-medium" })] })] }) })] }), _jsxs("div", { className: "bg-white dark:bg-neutral-800", style: {
227
+ backgroundColor: '#ffffff',
228
+ }, children: [(() => {
229
+ // Get email config from window global (set by initNewsletterPlugin)
230
+ let emailConfig = {};
231
+ if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
232
+ const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
233
+ if (pluginProps?.emailConfig) {
234
+ emailConfig = pluginProps.emailConfig;
235
+ }
236
+ }
237
+ return emailConfig.logoUrl ? (_jsx("div", { style: {
238
+ padding: '40px 0 20px 0',
239
+ textAlign: 'center',
240
+ backgroundColor: '#ffffff',
241
+ display: 'flex',
242
+ justifyContent: 'center',
243
+ alignItems: 'center',
244
+ }, children: _jsx("img", { src: emailConfig.logoUrl, alt: emailConfig.logoAlt || 'Logo', style: {
245
+ width: '180px',
246
+ height: 'auto',
247
+ display: 'block',
248
+ margin: '0 auto',
249
+ } }) })) : null;
250
+ })(), _jsxs("div", { className: "email-editor-content", style: {
251
+ fontFamily: "'Georgia', serif",
252
+ color: '#1a2e26',
253
+ lineHeight: '1.8',
254
+ fontSize: '15px',
255
+ padding: '0 50px 40px 50px',
256
+ minHeight: '400px',
257
+ backgroundColor: '#ffffff',
258
+ }, children: [_jsx(EditorBody, { blocks: contentBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onBlockAdd: onBlockAdd, onBlockUpdate: onBlockUpdate, onBlockDelete: onBlockDelete, onBlockMove: onBlockMove, slashCommand: slashCommand }), (() => {
259
+ // Generate unsubscribe URL (similar to welcome email)
260
+ const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
261
+ const slugs = {
262
+ sv: '/avmälla',
263
+ nl: '/afmelden',
264
+ en: '/unsubscribe',
265
+ };
266
+ const slug = slugs[locale] || slugs.en;
267
+ const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
268
+ // Get unsubscribe text based on locale
269
+ const isDutch = locale === 'nl';
270
+ const unsubscribeText = isDutch ? 'Afmelden' : 'Unsubscribe';
271
+ return (_jsxs(_Fragment, { children: [_jsx("div", { style: {
272
+ height: '1px',
273
+ width: '40px',
274
+ backgroundColor: '#1a2e2620',
275
+ margin: '30px auto',
276
+ } }), _jsx("p", { style: {
277
+ textAlign: 'center',
278
+ fontSize: '12px',
279
+ color: '#a1a1aa',
280
+ margin: 0,
281
+ }, children: _jsx("a", { href: unsubscribeUrl, style: {
282
+ color: '#a1a1aa',
283
+ textDecoration: 'none',
284
+ }, onClick: (e) => e.preventDefault(), children: unsubscribeText }) })] }));
285
+ })()] }), (() => {
286
+ // Get email config from window global
287
+ let emailConfig = {};
288
+ if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
289
+ const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
290
+ if (pluginProps?.emailConfig) {
291
+ emailConfig = pluginProps.emailConfig;
292
+ }
293
+ }
294
+ return emailConfig.footerText ? (_jsx("div", { style: {
295
+ padding: '40px 50px',
296
+ textAlign: 'center',
297
+ fontFamily: 'sans-serif',
298
+ fontSize: '10px',
299
+ color: '#a1a1aa',
300
+ letterSpacing: '1px',
301
+ borderTop: '1px solid #faf9f6',
302
+ backgroundColor: '#ffffff',
303
+ }, children: emailConfig.footerText })) : null;
304
+ })()] })] }) }) })) }));
305
+ }
306
+ /**
307
+ * Email Preview Component
308
+ * Shows how the newsletter will look in email clients
309
+ * Matches the editor view structure for consistency
310
+ */
311
+ function EmailPreview({ title, blocks, siteId, locale, metadata }) {
312
+ const [emailHtml, setEmailHtml] = useState('');
313
+ const iframeRef = useRef(null);
314
+ useEffect(() => {
315
+ // Get email config from window global (set by initNewsletterPlugin)
316
+ let emailConfig = {};
317
+ if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
318
+ const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
319
+ if (pluginProps?.emailConfig) {
320
+ emailConfig = pluginProps.emailConfig;
321
+ }
322
+ }
323
+ // Resolve image IDs to filenames before generating HTML
324
+ const resolveImageBlocks = async () => {
325
+ const resolvedBlocks = await Promise.all(blocks.map(async (block) => {
326
+ if (block.type === 'image' && block.data.imageId) {
327
+ const imageId = block.data.imageId;
328
+ // Check if already a filename
329
+ const hasFileExtension = /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(imageId);
330
+ const looksLikeTimestamp = /^\d+-/.test(imageId);
331
+ if (hasFileExtension || looksLikeTimestamp) {
332
+ // Already a filename, use it directly
333
+ return block;
334
+ }
335
+ // Need to resolve semantic ID to filename
336
+ try {
337
+ const response = await fetch(`/api/plugin-images/resolve?id=${encodeURIComponent(imageId)}`);
338
+ if (response.ok) {
339
+ const data = await response.json();
340
+ return {
341
+ ...block,
342
+ data: {
343
+ ...block.data,
344
+ imageId: data.filename || imageId, // Use resolved filename
345
+ },
346
+ };
347
+ }
348
+ }
349
+ catch (error) {
350
+ console.warn(`Failed to resolve image ID ${imageId}:`, error);
351
+ }
352
+ }
353
+ return block;
354
+ }));
355
+ // Generate unsubscribe URL (similar to welcome email)
356
+ const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
357
+ const slugs = {
358
+ sv: '/avmälla',
359
+ nl: '/afmelden',
360
+ en: '/unsubscribe',
361
+ };
362
+ const slug = slugs[locale] || slugs.en;
363
+ // Use placeholder email for preview
364
+ const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
365
+ // Generate email HTML with resolved image blocks
366
+ const html = generateNewsletterEmailHtml(resolvedBlocks, { subject: metadata?.subject || '', previewText: metadata?.previewText || '' }, {
367
+ siteId,
368
+ locale,
369
+ baseUrl,
370
+ logoUrl: emailConfig.logoUrl,
371
+ logoAlt: emailConfig.logoAlt,
372
+ footerText: emailConfig.footerText,
373
+ unsubscribeUrl,
374
+ });
375
+ setEmailHtml(html);
376
+ // Update iframe content
377
+ if (iframeRef.current) {
378
+ const iframe = iframeRef.current;
379
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
380
+ if (iframeDoc) {
381
+ iframeDoc.open();
382
+ iframeDoc.write(html);
383
+ iframeDoc.close();
384
+ }
385
+ }
386
+ };
387
+ resolveImageBlocks();
388
+ }, [title, blocks, siteId, locale, metadata]);
389
+ if (blocks.length === 0) {
390
+ return (_jsx("div", { className: "bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-900 dark:to-neutral-800 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-700 shadow-xl", children: _jsx("div", { className: "bg-white dark:bg-neutral-800 rounded-lg shadow-2xl overflow-hidden", children: _jsxs("div", { className: "text-center py-20 text-neutral-400 dark:text-neutral-500", children: [_jsx("p", { className: "text-sm", children: "No content blocks yet. Switch to Edit mode to add blocks." }), _jsx("p", { className: "text-xs mt-2 text-neutral-500 dark:text-neutral-600", children: "Email preview will appear here once you add blocks." })] }) }) }));
391
+ }
392
+ return (_jsx("div", { children: _jsx("div", { className: "bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-900 dark:to-neutral-800 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-700 shadow-xl", children: _jsxs("div", { className: "bg-white dark:bg-neutral-800 rounded-lg shadow-2xl overflow-hidden", children: [_jsxs("div", { className: "bg-white dark:bg-neutral-800 border-b border-neutral-200 dark:border-neutral-700", children: [_jsxs("div", { className: "px-4 py-2 flex items-center justify-between border-b border-neutral-100 dark:border-neutral-700", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded", title: "Close", children: _jsx("svg", { className: "w-4 h-4 text-neutral-600 dark:text-neutral-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), _jsx("button", { className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded", title: "Minimize", children: _jsx("svg", { className: "w-4 h-4 text-neutral-600 dark:text-neutral-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) }) }), _jsx("button", { className: "p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded", title: "Fullscreen", children: _jsx("svg", { className: "w-4 h-4 text-neutral-600 dark:text-neutral-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" }) }) })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-medium", children: "Email Preview" }) })] }), _jsx("div", { className: "px-4 py-3 border-b border-neutral-100 dark:border-neutral-700", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0", children: "To:" }), _jsx("input", { type: "text", value: "All Subscribers", readOnly: true, className: "flex-1 text-sm bg-transparent border-none outline-none text-neutral-700 dark:text-neutral-300 placeholder:text-neutral-400" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0", children: "Subject:" }), _jsx("input", { type: "text", value: metadata?.subject || 'Newsletter Subject', readOnly: true, className: "flex-1 text-sm bg-transparent border-none outline-none text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-400 font-medium" })] })] }) })] }), _jsx("div", { className: "bg-white dark:bg-neutral-800", children: _jsx("iframe", { ref: iframeRef, title: "Email Preview", className: "w-full border-0 bg-white", style: {
393
+ minHeight: '500px',
394
+ maxHeight: '900px',
395
+ display: 'block',
396
+ }, sandbox: "allow-same-origin" }) })] }) }) }));
397
+ }
@@ -0,0 +1,7 @@
1
+ import type { BlockTypeDefinition } from '../../../types/block';
2
+ export interface EditorLibraryProps {
3
+ registeredBlocks: BlockTypeDefinition[];
4
+ onAddBlock: (type: string) => void;
5
+ }
6
+ export declare function EditorLibrary({ registeredBlocks, onAddBlock }: EditorLibraryProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=EditorLibrary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorLibrary.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/EditorLibrary.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IAC/B,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,wBAAgB,aAAa,CAAC,EAAE,gBAAgB,EAAE,UAAU,EAAE,EAAE,kBAAkB,2CA2GjF"}