@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,25 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Library, Image as ImageIcon, LayoutTemplate, Type, Box } from 'lucide-react';
4
+ import { LibraryItem, CustomBlockItem } from './index';
5
+ export function EditorLibrary({ registeredBlocks, onAddBlock }) {
6
+ // Get all registered blocks (excluding Hero block for newsletters)
7
+ const allBlocks = registeredBlocks.filter(block => block.type !== 'hero');
8
+ const textBlocks = allBlocks.filter(block => block.category === 'text');
9
+ const customBlocks = allBlocks.filter(block => block.category === 'custom');
10
+ const mediaBlocks = allBlocks.filter(block => block.category === 'media');
11
+ const layoutBlocks = allBlocks.filter(block => block.category === 'layout');
12
+ return (_jsxs("div", { className: "p-6 w-72 min-w-0 max-w-full", children: [textBlocks.length > 0 && (_jsxs("div", { className: "mb-10", children: [_jsx("h3", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6", children: "Text" }), _jsx("div", { className: "grid grid-cols-2 gap-3", children: textBlocks.map((block) => {
13
+ const IconComponent = block.icon || block.components.Icon || Type;
14
+ return (_jsx(LibraryItem, { icon: _jsx(IconComponent, { size: 16 }), label: block.name, blockType: block.type, description: block.description, onAddBlock: onAddBlock }, block.type));
15
+ }) })] })), mediaBlocks.length > 0 && (_jsxs("div", { className: "mb-10", children: [_jsx("h3", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6", children: "Media" }), _jsx("div", { className: "grid grid-cols-2 gap-3", children: mediaBlocks.map((block) => {
16
+ const IconComponent = block.icon || block.components.Icon || ImageIcon;
17
+ return (_jsx(LibraryItem, { icon: _jsx(IconComponent, { size: 16 }), label: block.name, blockType: block.type, description: block.description, onAddBlock: onAddBlock }, block.type));
18
+ }) })] })), layoutBlocks.length > 0 && (_jsxs("div", { className: "mb-10", children: [_jsx("h3", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6", children: "Layout" }), _jsx("div", { className: "grid grid-cols-2 gap-3", children: layoutBlocks.map((block) => {
19
+ const IconComponent = block.icon || block.components.Icon || LayoutTemplate;
20
+ return (_jsx(LibraryItem, { icon: _jsx(IconComponent, { size: 16 }), label: block.name, blockType: block.type, description: block.description, onAddBlock: onAddBlock }, block.type));
21
+ }) })] })), customBlocks.length > 0 && (_jsxs("div", { children: [_jsx("h3", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6", children: "Custom Blocks" }), _jsx("div", { className: "space-y-3", children: customBlocks.map((block) => {
22
+ const IconComponent = block.icon || block.components.Icon || Box;
23
+ return (_jsx(CustomBlockItem, { blockType: block.type, name: block.name, description: block.description, icon: _jsx(IconComponent, { size: 16 }), onAddBlock: onAddBlock }, block.type));
24
+ }) })] })), allBlocks.length === 0 && (_jsxs("div", { className: "text-center py-12 text-neutral-400 dark:text-neutral-500", children: [_jsx(Library, { size: 32, className: "mx-auto mb-4 opacity-50" }), _jsx("p", { className: "text-sm", children: "No blocks available" }), _jsx("p", { className: "text-xs mt-2", children: "Register blocks in your app configuration" })] }))] }));
25
+ }
@@ -0,0 +1,9 @@
1
+ import type { NewsletterMetadata } from '../../../types/newsletter';
2
+ export interface EditorSidebarProps {
3
+ slug: string;
4
+ metadata: NewsletterMetadata;
5
+ status: string;
6
+ onMetadataUpdate: (metadata: Partial<NewsletterMetadata>) => void;
7
+ }
8
+ export declare function EditorSidebar({ slug, metadata, status, onMetadataUpdate, }: EditorSidebarProps): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=EditorSidebar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorSidebar.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/EditorSidebar.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;CACrE;AAED,wBAAgB,aAAa,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,gBAAgB,GACnB,EAAE,kBAAkB,2CAyIpB"}
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Mail, Users } from 'lucide-react';
4
+ export function EditorSidebar({ slug, metadata, status, onMetadataUpdate, }) {
5
+ return (_jsxs("div", { className: "p-8 w-80 min-w-0 max-w-full space-y-12 overflow-y-auto max-h-full", children: [_jsxs("section", { children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx(Mail, { size: 14, className: "text-neutral-500 dark:text-neutral-400" }), _jsx("label", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black", children: "Newsletter Settings" })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Email Subject" }), _jsx("input", { type: "text", value: metadata.subject || '', onChange: (e) => onMetadataUpdate({ subject: e.target.value }), placeholder: "Enter email subject", className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text" }), _jsxs("p", { className: "text-[9px] text-neutral-400 dark:text-neutral-500 mt-1", children: [metadata.subject?.length || 0, " / 100 characters"] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Preview Text" }), _jsx("textarea", { value: metadata.previewText || '', onChange: (e) => onMetadataUpdate({ previewText: e.target.value }), placeholder: "Preview text shown in email clients", rows: 3, className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text resize-none" }), _jsxs("p", { className: "text-[9px] text-neutral-400 dark:text-neutral-500 mt-1", children: [metadata.previewText?.length || 0, " / 150 characters"] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Language" }), _jsxs("select", { value: metadata.lang || 'en', onChange: (e) => onMetadataUpdate({ lang: e.target.value }), className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text", children: [_jsx("option", { value: "en", children: "English" }), _jsx("option", { value: "nl", children: "Dutch" }), _jsx("option", { value: "sv", children: "Swedish" })] })] })] })] }), _jsxs("section", { className: "pt-8 border-t border-neutral-200 dark:border-neutral-800", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx(Users, { size: 14, className: "text-neutral-500 dark:text-neutral-400" }), _jsx("label", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black", children: "Recipients" })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Filter Type" }), _jsxs("select", { value: metadata.recipientFilter?.type || 'all', onChange: (e) => onMetadataUpdate({
6
+ recipientFilter: {
7
+ type: e.target.value,
8
+ value: metadata.recipientFilter?.value,
9
+ }
10
+ }), className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text", children: [_jsx("option", { value: "all", children: "All Subscribers" }), _jsx("option", { value: "language", children: "By Language" }), _jsx("option", { value: "custom", children: "Custom Filter" })] })] }), metadata.recipientFilter?.type === 'language' && (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Language" }), _jsxs("select", { value: metadata.recipientFilter?.value || 'en', onChange: (e) => onMetadataUpdate({
11
+ recipientFilter: {
12
+ type: 'language',
13
+ value: e.target.value,
14
+ }
15
+ }), className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text", children: [_jsx("option", { value: "en", children: "English" }), _jsx("option", { value: "nl", children: "Dutch" }), _jsx("option", { value: "sv", children: "Swedish" })] })] }))] })] }), _jsx("section", { className: "pt-8 border-t border-neutral-200 dark:border-neutral-800", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Slug" }), _jsx("input", { type: "text", value: slug, readOnly: true, className: "w-full px-3 py-2 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg text-dashboard-text opacity-60 cursor-not-allowed" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Status" }), _jsx("div", { className: "px-3 py-2 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg", children: _jsx("span", { className: "uppercase font-bold", children: status }) })] })] }) })] }));
16
+ }
@@ -0,0 +1,6 @@
1
+ export interface ErrorBannerProps {
2
+ error: string | null;
3
+ onDismiss: () => void;
4
+ }
5
+ export declare function ErrorBanner({ error, onDismiss }: ErrorBannerProps): import("react/jsx-runtime").JSX.Element | null;
6
+ //# sourceMappingURL=ErrorBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBanner.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/ErrorBanner.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,gBAAgB,kDAoBjE"}
@@ -0,0 +1,8 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { AlertTriangle, X } from 'lucide-react';
4
+ export function ErrorBanner({ error, onDismiss }) {
5
+ if (!error)
6
+ return null;
7
+ return (_jsxs("div", { className: "bg-red-50 dark:bg-red-900/20 border-b border-red-200 dark:border-red-800 px-6 py-3 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3 flex-1", children: [_jsx(AlertTriangle, { className: "text-red-600 dark:text-red-400 flex-shrink-0", size: 20 }), _jsx("p", { className: "text-red-800 dark:text-red-300 text-sm font-medium", children: error })] }), _jsx("button", { onClick: onDismiss, className: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-200 transition-colors", "aria-label": "Dismiss error", children: _jsx(X, { size: 18 }) })] }));
8
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ export interface LibraryItemProps {
3
+ icon: React.ReactNode;
4
+ label: string;
5
+ blockType: string;
6
+ description?: string;
7
+ onAddBlock?: (blockType: string) => void;
8
+ }
9
+ export declare function LibraryItem({ icon, label, blockType, description, onAddBlock }: LibraryItemProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=LibraryItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LibraryItem.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/LibraryItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAEhD,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAgB,WAAW,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,SAAS,EACT,WAAW,EACX,UAAU,EACb,EAAE,gBAAgB,2CAoDlB"}
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useState, useRef } from 'react';
4
+ export function LibraryItem({ icon, label, blockType, description, onAddBlock }) {
5
+ const [hasDragged, setHasDragged] = useState(false);
6
+ const mouseDownRef = useRef(null);
7
+ const handleDragStart = (e) => {
8
+ e.dataTransfer.setData('block-type', blockType);
9
+ e.dataTransfer.effectAllowed = 'move';
10
+ setHasDragged(true);
11
+ };
12
+ const handleMouseDown = (e) => {
13
+ mouseDownRef.current = { x: e.clientX, y: e.clientY };
14
+ setHasDragged(false);
15
+ };
16
+ const handleMouseMove = (e) => {
17
+ if (mouseDownRef.current) {
18
+ const dx = Math.abs(e.clientX - mouseDownRef.current.x);
19
+ const dy = Math.abs(e.clientY - mouseDownRef.current.y);
20
+ if (dx > 5 || dy > 5) {
21
+ setHasDragged(true);
22
+ }
23
+ }
24
+ };
25
+ const handleClick = (e) => {
26
+ if (!hasDragged && onAddBlock) {
27
+ e.preventDefault();
28
+ e.stopPropagation();
29
+ onAddBlock(blockType);
30
+ }
31
+ mouseDownRef.current = null;
32
+ setTimeout(() => setHasDragged(false), 100);
33
+ };
34
+ return (_jsxs("div", { draggable: true, onDragStart: handleDragStart, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onClick: handleClick, className: "flex flex-col items-center justify-center p-5 rounded-2xl border border-dashboard-border bg-dashboard-card hover:border-primary hover:shadow-xl hover:shadow-primary/5 transition-all cursor-pointer group", children: [_jsx("div", { className: "text-neutral-400 dark:text-neutral-500 group-hover:text-primary dark:group-hover:text-primary mb-3 transition-colors duration-300", children: React.cloneElement(icon, { strokeWidth: 1.5 }) }), _jsx("span", { className: "text-[9px] font-black uppercase tracking-[0.15em] text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-950 dark:group-hover:text-white transition-colors", children: label })] }));
35
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { useSlashCommand } from '../hooks/useSlashCommand';
3
+ export interface SlashCommandDetectorProps {
4
+ children: React.ReactElement;
5
+ blockId: string;
6
+ blockIndex: number;
7
+ blockType: string;
8
+ content: string;
9
+ onContentChange: (content: string) => void;
10
+ slashCommand?: ReturnType<typeof useSlashCommand>;
11
+ onAddBlockBelow?: (blockType: string) => void;
12
+ }
13
+ /**
14
+ * Detects "/" input and shows slash command menu
15
+ * Wraps block edit components to add slash command functionality
16
+ */
17
+ export declare function SlashCommandDetector({ children, blockId, blockIndex, blockType, content, onContentChange, slashCommand, onAddBlockBelow, }: SlashCommandDetectorProps): import("react/jsx-runtime").JSX.Element;
18
+ //# sourceMappingURL=SlashCommandDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlashCommandDetector.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/SlashCommandDetector.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;IAClD,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,QAAQ,EACR,OAAO,EACP,UAAU,EACV,SAAS,EACT,OAAO,EACP,eAAe,EACf,YAAY,EACZ,eAAe,GAClB,EAAE,yBAAyB,2CAsK3B"}
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useCallback } from 'react';
4
+ /**
5
+ * Detects "/" input and shows slash command menu
6
+ * Wraps block edit components to add slash command functionality
7
+ */
8
+ export function SlashCommandDetector({ children, blockId, blockIndex, blockType, content, onContentChange, slashCommand, onAddBlockBelow, }) {
9
+ const containerRef = useRef(null);
10
+ const lastContentRef = useRef('');
11
+ const slashIndexRef = useRef(-1);
12
+ // Listen for input events on contentEditable elements
13
+ useEffect(() => {
14
+ if (!slashCommand || blockType !== 'paragraph')
15
+ return;
16
+ const container = containerRef.current;
17
+ if (!container)
18
+ return;
19
+ const handleInput = (e) => {
20
+ const target = e.target;
21
+ if (!target.isContentEditable)
22
+ return;
23
+ const textContent = target.textContent || '';
24
+ const plainText = textContent.replace(/\u00A0/g, ' '); // Replace non-breaking spaces
25
+ const previousText = (lastContentRef.current || '').replace(/\u00A0/g, ' ');
26
+ // Only allow "/" when paragraph is completely empty (no content before "/")
27
+ const wasEmpty = previousText.trim().length === 0;
28
+ const isNowEmpty = plainText.trim().length === 0;
29
+ if (plainText.length > previousText.length) {
30
+ // Character was added
31
+ const lastChar = plainText[plainText.length - 1];
32
+ // Check if "/" was typed and paragraph was empty before
33
+ if (lastChar === '/' && wasEmpty && plainText === '/') {
34
+ slashIndexRef.current = 0;
35
+ // Get cursor position
36
+ const selection = window.getSelection();
37
+ if (selection && selection.rangeCount > 0) {
38
+ const range = selection.getRangeAt(0);
39
+ const rect = range.getBoundingClientRect();
40
+ slashCommand.openMenu({
41
+ top: rect.bottom + 5,
42
+ left: rect.left,
43
+ }, blockId);
44
+ }
45
+ }
46
+ else if (slashCommand.isOpen && slashIndexRef.current >= 0) {
47
+ // Update query if menu is open (only if still starts with "/")
48
+ if (plainText.startsWith('/')) {
49
+ const query = plainText.slice(1);
50
+ slashCommand.updateQuery(query);
51
+ }
52
+ else {
53
+ // If user typed something other than "/", close the menu
54
+ slashCommand.closeMenu();
55
+ slashIndexRef.current = -1;
56
+ }
57
+ }
58
+ else if (!wasEmpty || (plainText.trim().length > 0 && plainText !== '/')) {
59
+ // If paragraph had content before or now has content that's not just "/", ensure menu is closed
60
+ if (slashCommand.isOpen) {
61
+ slashCommand.closeMenu();
62
+ slashIndexRef.current = -1;
63
+ }
64
+ }
65
+ }
66
+ else if (plainText.length < previousText.length) {
67
+ // Content was deleted
68
+ if (slashCommand.isOpen) {
69
+ if (plainText === '/') {
70
+ // Still just "/", keep menu open
71
+ slashIndexRef.current = 0;
72
+ }
73
+ else {
74
+ // No longer just "/", close menu
75
+ slashCommand.closeMenu();
76
+ slashIndexRef.current = -1;
77
+ }
78
+ }
79
+ }
80
+ lastContentRef.current = textContent; // Store original with non-breaking spaces
81
+ };
82
+ // Listen for input events on contentEditable elements within container
83
+ container.addEventListener('input', handleInput);
84
+ return () => {
85
+ container.removeEventListener('input', handleInput);
86
+ };
87
+ }, [blockType, slashCommand, blockId]);
88
+ // Handle keyboard events for slash command and Enter key
89
+ const handleKeyDown = useCallback((e) => {
90
+ // Handle Enter key to create new paragraph when not in slash command mode
91
+ if (!slashCommand?.isOpen && e.key === 'Enter' && !e.shiftKey) {
92
+ const container = containerRef.current;
93
+ if (container) {
94
+ const contentEditable = container.querySelector('[contenteditable="true"]');
95
+ if (contentEditable && document.activeElement === contentEditable) {
96
+ const selection = window.getSelection();
97
+ if (selection && selection.rangeCount > 0) {
98
+ const range = selection.getRangeAt(0);
99
+ const textContent = contentEditable.textContent || '';
100
+ const plainText = textContent.replace(/\u00A0/g, ' ');
101
+ // Check if cursor is at the end of the content
102
+ if (range.startOffset === plainText.length && range.endOffset === plainText.length) {
103
+ // Create new paragraph below when Enter is pressed at end
104
+ e.preventDefault();
105
+ e.stopPropagation();
106
+ if (onAddBlockBelow) {
107
+ onAddBlockBelow('paragraph');
108
+ }
109
+ return;
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ if (!slashCommand?.isOpen)
116
+ return;
117
+ if (e.key === 'ArrowDown') {
118
+ e.preventDefault();
119
+ slashCommand.moveSelection('down');
120
+ }
121
+ else if (e.key === 'ArrowUp') {
122
+ e.preventDefault();
123
+ slashCommand.moveSelection('up');
124
+ }
125
+ else if (e.key === 'Enter' || e.key === 'Tab') {
126
+ e.preventDefault();
127
+ e.stopPropagation();
128
+ const selectedBlock = slashCommand.filteredBlocks[slashCommand.selectedIndex];
129
+ if (selectedBlock) {
130
+ // Don't clear content here - the block replacement will handle it
131
+ // Just replace the block with selected type (this will replace the current paragraph)
132
+ slashCommand.selectCurrent(blockId);
133
+ }
134
+ }
135
+ else if (e.key === 'Escape') {
136
+ e.preventDefault();
137
+ e.stopPropagation();
138
+ // Clear the "/" from content - only clear if menu was open
139
+ if (slashCommand.isOpen) {
140
+ const container = containerRef.current;
141
+ if (container) {
142
+ const contentEditable = container.querySelector('[contenteditable="true"]');
143
+ if (contentEditable) {
144
+ const textContent = contentEditable.textContent || '';
145
+ const plainText = textContent.replace(/\u00A0/g, ' ');
146
+ // Only clear if it's just "/"
147
+ if (plainText === '/') {
148
+ contentEditable.textContent = '';
149
+ onContentChange('');
150
+ }
151
+ }
152
+ }
153
+ }
154
+ slashCommand.closeMenu();
155
+ slashIndexRef.current = -1;
156
+ }
157
+ }, [slashCommand, content, onContentChange, blockId, onAddBlockBelow]);
158
+ useEffect(() => {
159
+ // Always listen for keydown events (for both slash command and Enter key handling)
160
+ window.addEventListener('keydown', handleKeyDown);
161
+ return () => window.removeEventListener('keydown', handleKeyDown);
162
+ }, [handleKeyDown]);
163
+ return (_jsx("div", { ref: containerRef, children: children }));
164
+ }
@@ -0,0 +1,22 @@
1
+ import type { BlockTypeDefinition } from '../../../types/block';
2
+ export interface SlashCommandMenuProps {
3
+ /** Registered blocks to show in menu */
4
+ blocks: BlockTypeDefinition[];
5
+ /** Current search query */
6
+ query: string;
7
+ /** Selected index */
8
+ selectedIndex: number;
9
+ /** Callback when a block is selected - receives replaceBlockId if replacing */
10
+ onSelect: (replaceBlockId?: string) => void;
11
+ /** Position for the menu */
12
+ position: {
13
+ top: number;
14
+ left: number;
15
+ } | null;
16
+ /** Callback to close the menu */
17
+ onClose: () => void;
18
+ /** Block ID to replace (if replacing existing block) */
19
+ replaceBlockId?: string;
20
+ }
21
+ export declare function SlashCommandMenu({ blocks, query, selectedIndex, onSelect, position, onClose, replaceBlockId, }: SlashCommandMenuProps): import("react/jsx-runtime").JSX.Element | null;
22
+ //# sourceMappingURL=SlashCommandMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlashCommandMenu.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/SlashCommandMenu.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,qBAAqB;IAClC,wCAAwC;IACxC,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,4BAA4B;IAC5B,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/C,iCAAiC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,cAAc,GACjB,EAAE,qBAAqB,kDAmGvB"}
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from 'react';
4
+ import { Search } from 'lucide-react';
5
+ export function SlashCommandMenu({ blocks, query, selectedIndex, onSelect, position, onClose, replaceBlockId, }) {
6
+ const menuRef = useRef(null);
7
+ // Handle keyboard navigation
8
+ useEffect(() => {
9
+ const handleKeyDown = (e) => {
10
+ if (e.key === 'ArrowDown') {
11
+ e.preventDefault();
12
+ // Selection is handled by parent
13
+ }
14
+ else if (e.key === 'ArrowUp') {
15
+ e.preventDefault();
16
+ // Selection is handled by parent
17
+ }
18
+ else if (e.key === 'Enter' || e.key === 'Tab') {
19
+ e.preventDefault();
20
+ if (blocks[selectedIndex]) {
21
+ onSelect(replaceBlockId);
22
+ }
23
+ }
24
+ else if (e.key === 'Escape') {
25
+ e.preventDefault();
26
+ onClose();
27
+ }
28
+ };
29
+ window.addEventListener('keydown', handleKeyDown);
30
+ return () => window.removeEventListener('keydown', handleKeyDown);
31
+ }, [blocks, selectedIndex, onSelect, onClose, replaceBlockId]);
32
+ // Scroll selected item into view
33
+ useEffect(() => {
34
+ if (menuRef.current && selectedIndex >= 0) {
35
+ const selectedElement = menuRef.current.children[selectedIndex];
36
+ if (selectedElement) {
37
+ selectedElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
38
+ }
39
+ }
40
+ }, [selectedIndex]);
41
+ if (!position || blocks.length === 0) {
42
+ return null;
43
+ }
44
+ return (_jsxs("div", { ref: menuRef, className: "fixed z-50 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-2xl overflow-hidden", style: {
45
+ top: `${position.top}px`,
46
+ left: `${position.left}px`,
47
+ width: '280px',
48
+ maxHeight: '320px',
49
+ overflowY: 'auto',
50
+ }, children: [_jsxs("div", { className: "p-2 border-b border-neutral-200 dark:border-neutral-700 flex items-center gap-2", children: [_jsx(Search, { size: 14, className: "text-neutral-400" }), _jsx("input", { type: "text", value: query, readOnly: true, placeholder: "Search blocks...", className: "flex-1 bg-transparent border-none outline-none text-sm text-neutral-600 dark:text-neutral-300 placeholder:text-neutral-400" })] }), _jsx("div", { className: "py-1", children: blocks.map((block, index) => {
51
+ const IconComponent = block.icon || block.components.Icon;
52
+ const isSelected = index === selectedIndex;
53
+ return (_jsxs("button", { onClick: () => onSelect(replaceBlockId), className: `w-full px-3 py-2 flex items-center gap-3 text-left transition-colors ${isSelected
54
+ ? 'bg-primary/10 text-primary dark:bg-primary/20'
55
+ : 'hover:bg-neutral-100 dark:hover:bg-neutral-700 text-neutral-700 dark:text-neutral-300'}`, children: [IconComponent && (_jsx("div", { className: `flex-shrink-0 ${isSelected ? 'text-primary' : 'text-neutral-400'}`, children: _jsx(IconComponent, {}) })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: `text-sm font-medium ${isSelected ? 'text-primary' : 'text-neutral-900 dark:text-neutral-100'}`, children: block.name }), block.description && (_jsx("div", { className: "text-xs text-neutral-500 dark:text-neutral-400 truncate", children: block.description }))] })] }, block.type));
56
+ }) })] }));
57
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Newsletter Editor Components Exports
3
+ */
4
+ export { ErrorBanner } from './ErrorBanner';
5
+ export type { ErrorBannerProps } from './ErrorBanner';
6
+ export { LibraryItem } from './LibraryItem';
7
+ export type { LibraryItemProps } from './LibraryItem';
8
+ export { CustomBlockItem } from './CustomBlockItem';
9
+ export type { CustomBlockItemProps } from './CustomBlockItem';
10
+ export { EditorLibrary } from './EditorLibrary';
11
+ export type { EditorLibraryProps } from './EditorLibrary';
12
+ export { EditorCanvas } from './EditorCanvas';
13
+ export type { EditorCanvasProps } from './EditorCanvas';
14
+ export { EditorSidebar } from './EditorSidebar';
15
+ export type { EditorSidebarProps } from './EditorSidebar';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Newsletter Editor Components Exports
3
+ */
4
+ export { ErrorBanner } from './ErrorBanner';
5
+ export { LibraryItem } from './LibraryItem';
6
+ export { CustomBlockItem } from './CustomBlockItem';
7
+ export { EditorLibrary } from './EditorLibrary';
8
+ export { EditorCanvas } from './EditorCanvas';
9
+ export { EditorSidebar } from './EditorSidebar';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Newsletter Editor Hooks
3
+ */
4
+ export { useRegisteredBlocks } from './useRegisteredBlocks';
5
+ export { useNewsletterLoader } from './useNewsletterLoader';
6
+ export { useKeyboardShortcuts } from './useKeyboardShortcuts';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Newsletter Editor Hooks
3
+ */
4
+ export { useRegisteredBlocks } from './useRegisteredBlocks';
5
+ export { useNewsletterLoader } from './useNewsletterLoader';
6
+ export { useKeyboardShortcuts } from './useKeyboardShortcuts';
@@ -0,0 +1,3 @@
1
+ import type { EditorState } from '../../../state/types';
2
+ export declare function useKeyboardShortcuts(state: EditorState, dispatch: (action: any) => void, canUndo: boolean, canRedo: boolean, undo: () => void, redo: () => void): void;
3
+ //# sourceMappingURL=useKeyboardShortcuts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAUxD,wBAAgB,oBAAoB,CAChC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,EAC/B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,IAAI,EAChB,IAAI,EAAE,MAAM,IAAI,QAqHnB"}
@@ -0,0 +1,114 @@
1
+ import { useEffect } from 'react';
2
+ // Generate a unique block ID
3
+ function generateBlockId() {
4
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
5
+ return crypto.randomUUID();
6
+ }
7
+ return `block-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
8
+ }
9
+ export function useKeyboardShortcuts(state, dispatch, canUndo, canRedo, undo, redo) {
10
+ useEffect(() => {
11
+ const handleKeyDown = (e) => {
12
+ // Don't handle shortcuts if user is typing in an input/textarea/contentEditable
13
+ const target = e.target;
14
+ const isEditableElement = target.tagName === 'INPUT' ||
15
+ target.tagName === 'TEXTAREA' ||
16
+ target.isContentEditable ||
17
+ target.closest('input, textarea, [contenteditable="true"]');
18
+ // Check for Ctrl+Z / Cmd+Z (Undo)
19
+ if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
20
+ if (!isEditableElement) {
21
+ e.preventDefault();
22
+ e.stopPropagation();
23
+ if (canUndo) {
24
+ undo();
25
+ }
26
+ return;
27
+ }
28
+ }
29
+ // Check for Ctrl+Shift+Z / Cmd+Shift+Z or Ctrl+Y / Cmd+Y (Redo)
30
+ if (((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z') || ((e.ctrlKey || e.metaKey) && e.key === 'y')) {
31
+ if (!isEditableElement) {
32
+ e.preventDefault();
33
+ e.stopPropagation();
34
+ if (canRedo) {
35
+ redo();
36
+ }
37
+ return;
38
+ }
39
+ }
40
+ // Check for Ctrl+V (Windows/Linux) or Cmd+V (Mac)
41
+ if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
42
+ // Don't paste if user is typing in an input/textarea/contentEditable
43
+ if (isEditableElement) {
44
+ return; // Let the browser handle paste in editable elements
45
+ }
46
+ // Check if there's a copied block
47
+ if (typeof window !== 'undefined') {
48
+ const copiedBlockJson = localStorage.getItem('__NEWSLETTER_EDITOR_COPIED_BLOCK__');
49
+ if (copiedBlockJson) {
50
+ try {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ const copiedBlock = JSON.parse(copiedBlockJson);
54
+ // Clone a block with new IDs (recursive for nested blocks)
55
+ const cloneBlock = (blockToClone) => {
56
+ const cloned = {
57
+ ...blockToClone,
58
+ id: generateBlockId(),
59
+ data: { ...blockToClone.data },
60
+ meta: blockToClone.meta ? { ...blockToClone.meta } : undefined,
61
+ };
62
+ // Handle children if they exist
63
+ if (blockToClone.children) {
64
+ if (Array.isArray(blockToClone.children) && blockToClone.children.length > 0) {
65
+ if (typeof blockToClone.children[0] === 'object') {
66
+ cloned.children = blockToClone.children.map(cloneBlock);
67
+ }
68
+ else {
69
+ // If children are IDs, find and clone the actual blocks
70
+ const allBlocks = state.blocks;
71
+ const childIds = blockToClone.children;
72
+ const childBlocks = childIds
73
+ .map((childId) => allBlocks.find(b => b.id === childId))
74
+ .filter((b) => b !== undefined);
75
+ cloned.children = childBlocks.map(cloneBlock);
76
+ }
77
+ }
78
+ }
79
+ return cloned;
80
+ };
81
+ const pastedBlock = cloneBlock(copiedBlock);
82
+ // Find where to paste - use hovered block or selected block, or paste at end
83
+ const hoveredBlockId = window.__NEWSLETTER_EDITOR_HOVERED_BLOCK_ID__;
84
+ const targetBlockId = hoveredBlockId || state.selectedBlockId;
85
+ let pasteIndex;
86
+ if (targetBlockId) {
87
+ const targetIndex = state.blocks.findIndex(b => b.id === targetBlockId);
88
+ if (targetIndex !== -1) {
89
+ pasteIndex = targetIndex + 1;
90
+ }
91
+ }
92
+ // Dispatch ADD_BLOCK with the full block structure
93
+ dispatch({
94
+ type: 'ADD_BLOCK',
95
+ payload: {
96
+ block: pastedBlock,
97
+ index: pasteIndex,
98
+ containerId: undefined
99
+ }
100
+ });
101
+ }
102
+ catch (error) {
103
+ console.error('Failed to paste block:', error);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ };
109
+ window.addEventListener('keydown', handleKeyDown);
110
+ return () => {
111
+ window.removeEventListener('keydown', handleKeyDown);
112
+ };
113
+ }, [state.blocks, state.selectedBlockId, dispatch, canUndo, canRedo, undo, redo]);
114
+ }
@@ -0,0 +1,5 @@
1
+ import { Newsletter } from '../../../types/newsletter';
2
+ export declare function useNewsletterLoader(newsletterSlug: string | undefined, currentNewsletterId: string | null, loadNewsletter: (newsletter: Newsletter) => void): {
3
+ isLoadingNewsletter: boolean;
4
+ };
5
+ //# sourceMappingURL=useNewsletterLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNewsletterLoader.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/useNewsletterLoader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,wBAAgB,mBAAmB,CAC/B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,mBAAmB,EAAE,MAAM,GAAG,IAAI,EAClC,cAAc,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI;;EA2BnD"}