@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,46 @@
1
+ /**
2
+ * Plugin Newsletter - Client Entry Point
3
+ * Main newsletter management interface for the dashboard
4
+ * Block-Based Newsletter Management System
5
+ * Multi-Tenant Architecture: Accepts custom blocks from client applications
6
+ */
7
+ import { ClientBlockDefinition } from './types/block';
8
+ /**
9
+ * Plugin Props Interface
10
+ * Matches the PluginProps from @jhits/jhits-dashboard
11
+ */
12
+ export interface PluginProps {
13
+ subPath: string[];
14
+ siteId: string;
15
+ locale: string;
16
+ /** Custom blocks from client application (optional, can also come from window.__JHITS_PLUGIN_PROPS__) */
17
+ customBlocks?: ClientBlockDefinition[];
18
+ /** Enable dark mode for content area and wrappers (default: true) */
19
+ darkMode?: boolean;
20
+ /** Background colors for the editor */
21
+ backgroundColors?: {
22
+ /** Background color for light mode (REQUIRED) */
23
+ light: string;
24
+ /** Background color for dark mode (optional) */
25
+ dark?: string;
26
+ };
27
+ }
28
+ /**
29
+ * Main Router Component
30
+ * Handles routing within the newsletter plugin
31
+ *
32
+ * Client Handshake:
33
+ * - Client apps can pass customBlocks via props
34
+ * - Or via window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].customBlocks
35
+ * - The EditorProvider will automatically register these blocks
36
+ */
37
+ export default function NewsletterPlugin(props: PluginProps): import("react/jsx-runtime").JSX.Element;
38
+ export { NewsletterPlugin as Index };
39
+ export type { Block, BlockTypeDefinition, ClientBlockDefinition, RichTextFormattingConfig, BlockEditProps, BlockPreviewProps, IBlockComponent, } from './types/block';
40
+ export type { Newsletter, NewsletterStatus, NewsletterMetadata, NewsletterListItem, NewsletterFilterOptions, } from './types/newsletter';
41
+ export { initNewsletterPlugin } from './init';
42
+ export type { NewsletterPluginConfig } from './init';
43
+ export { EditorProvider, useEditor } from './state/EditorContext';
44
+ export type { EditorProviderProps, EditorState, EditorContextValue } from './state';
45
+ export { blockRegistry } from './registry';
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAOtD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,WAAW,2CAsO1D;AAGD,OAAO,EAAE,gBAAgB,IAAI,KAAK,EAAE,CAAC;AAGrC,YAAY,EACR,KAAK,EACL,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAClB,MAAM,eAAe,CAAC;AAGvB,YAAY,EACR,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,GAC1B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,MAAM,QAAQ,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Plugin Newsletter - Client Entry Point
3
+ * Main newsletter management interface for the dashboard
4
+ * Block-Based Newsletter Management System
5
+ * Multi-Tenant Architecture: Accepts custom blocks from client applications
6
+ */
7
+ 'use client';
8
+ import { jsx as _jsx } from "react/jsx-runtime";
9
+ import { useMemo, useEffect } from 'react';
10
+ import { EditorProvider } from './state/EditorContext';
11
+ import { SubscribersView } from './views/SubscribersView';
12
+ import { SettingsView } from './views/SettingsView';
13
+ import { NewsletterManagerView } from './views/NewsletterManager';
14
+ import { NewsletterEditorView } from './views/NewsletterEditor';
15
+ import { editorStateToAPI } from './lib/mappers/apiMapper';
16
+ /**
17
+ * Main Router Component
18
+ * Handles routing within the newsletter plugin
19
+ *
20
+ * Client Handshake:
21
+ * - Client apps can pass customBlocks via props
22
+ * - Or via window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].customBlocks
23
+ * - The EditorProvider will automatically register these blocks
24
+ */
25
+ export default function NewsletterPlugin(props) {
26
+ const { subPath, siteId, locale, customBlocks: propsCustomBlocks, darkMode: propsDarkMode, backgroundColors: propsBackgroundColors } = props;
27
+ // Get custom blocks from props or window global (client app injection point)
28
+ const customBlocks = useMemo(() => {
29
+ // First, try props
30
+ if (propsCustomBlocks && propsCustomBlocks.length > 0) {
31
+ return propsCustomBlocks;
32
+ }
33
+ // Fallback to window global (for client app injection)
34
+ if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
35
+ const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
36
+ if (pluginProps?.customBlocks) {
37
+ return pluginProps.customBlocks;
38
+ }
39
+ }
40
+ return [];
41
+ }, [propsCustomBlocks]);
42
+ // Get dark mode setting from props, localStorage (dev settings), or window global
43
+ // Priority: localStorage (dev) > props > window global > default
44
+ const darkMode = useMemo(() => {
45
+ // First, check localStorage for dev settings (highest priority for dev)
46
+ if (typeof window !== 'undefined') {
47
+ try {
48
+ const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
49
+ if (saved) {
50
+ const config = JSON.parse(saved);
51
+ if (config.darkMode !== undefined) {
52
+ return config.darkMode;
53
+ }
54
+ }
55
+ }
56
+ catch (e) {
57
+ // Ignore localStorage errors
58
+ }
59
+ }
60
+ // Then try props
61
+ if (propsDarkMode !== undefined) {
62
+ return propsDarkMode;
63
+ }
64
+ // Fallback to window global if prop not provided
65
+ if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
66
+ const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
67
+ if (pluginProps?.darkMode !== undefined) {
68
+ return pluginProps.darkMode;
69
+ }
70
+ }
71
+ return true; // Default to dark mode enabled
72
+ }, [propsDarkMode]);
73
+ // Get background colors from props, localStorage (dev settings), or window global
74
+ // Priority: localStorage (dev) > props > window global
75
+ const backgroundColors = useMemo(() => {
76
+ // First, check localStorage for dev settings (highest priority for dev)
77
+ if (typeof window !== 'undefined') {
78
+ try {
79
+ const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
80
+ if (saved) {
81
+ const config = JSON.parse(saved);
82
+ if (config.backgroundColors) {
83
+ return config.backgroundColors;
84
+ }
85
+ }
86
+ }
87
+ catch (e) {
88
+ // Ignore localStorage errors
89
+ }
90
+ }
91
+ // Then try props
92
+ if (propsBackgroundColors) {
93
+ return propsBackgroundColors;
94
+ }
95
+ // Fallback to window global
96
+ if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
97
+ const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
98
+ if (pluginProps?.backgroundColors) {
99
+ return pluginProps.backgroundColors;
100
+ }
101
+ }
102
+ return undefined;
103
+ }, [propsBackgroundColors]);
104
+ const route = subPath[0] || 'newsletters';
105
+ // Listen for config updates from settings screen
106
+ useEffect(() => {
107
+ if (typeof window === 'undefined')
108
+ return;
109
+ const handleConfigUpdate = () => {
110
+ // Reload page to apply changes (simplest way to ensure all components pick up new values)
111
+ window.location.reload();
112
+ };
113
+ window.addEventListener('newsletter-plugin-config-updated', handleConfigUpdate);
114
+ return () => {
115
+ window.removeEventListener('newsletter-plugin-config-updated', handleConfigUpdate);
116
+ };
117
+ }, []);
118
+ // Route to appropriate view
119
+ switch (route) {
120
+ case 'newsletters':
121
+ return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
122
+ case 'editor':
123
+ const newsletterSlug = subPath[1];
124
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state) => {
125
+ // Save to API - create new or update existing newsletter
126
+ const originalSlug = newsletterSlug || state.slug;
127
+ const apiData = editorStateToAPI(state);
128
+ // If we have a slug, try to update first
129
+ if (originalSlug) {
130
+ console.log('[NewsletterPlugin] Attempting to update newsletter with slug:', originalSlug);
131
+ const updateResponse = await fetch(`/api/plugin-newsletter/newsletters/${originalSlug}`, {
132
+ method: 'PUT',
133
+ headers: { 'Content-Type': 'application/json' },
134
+ credentials: 'include',
135
+ body: JSON.stringify(apiData),
136
+ });
137
+ if (updateResponse.ok) {
138
+ const result = await updateResponse.json();
139
+ // If the slug changed, update the URL
140
+ if (result.slug && result.slug !== originalSlug) {
141
+ window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
142
+ }
143
+ return result;
144
+ }
145
+ // If 404, newsletter doesn't exist, create a new one
146
+ if (updateResponse.status === 404) {
147
+ console.log('[NewsletterPlugin] Newsletter not found, creating new newsletter');
148
+ }
149
+ else {
150
+ // Other error, throw it
151
+ const error = await updateResponse.json();
152
+ console.error('[NewsletterPlugin] Save failed:', {
153
+ status: updateResponse.status,
154
+ statusText: updateResponse.statusText,
155
+ error,
156
+ });
157
+ const errorMessage = error.message || error.error || 'Failed to save newsletter';
158
+ throw new Error(errorMessage);
159
+ }
160
+ }
161
+ // Create new newsletter (either no slug or update returned 404)
162
+ console.log('[NewsletterPlugin] Creating new newsletter');
163
+ const createResponse = await fetch('/api/plugin-newsletter/newsletters/new', {
164
+ method: 'POST',
165
+ headers: { 'Content-Type': 'application/json' },
166
+ credentials: 'include',
167
+ body: JSON.stringify(apiData),
168
+ });
169
+ if (!createResponse.ok) {
170
+ const error = await createResponse.json();
171
+ console.error('[NewsletterPlugin] Create failed:', {
172
+ status: createResponse.status,
173
+ statusText: createResponse.statusText,
174
+ error,
175
+ });
176
+ const errorMessage = error.message || error.error || 'Failed to create newsletter';
177
+ throw new Error(errorMessage);
178
+ }
179
+ const result = await createResponse.json();
180
+ // Update the URL to the new newsletter's slug
181
+ if (result.slug) {
182
+ window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
183
+ }
184
+ return result;
185
+ }, children: _jsx(NewsletterEditorView, { newsletterSlug: newsletterSlug, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
186
+ case 'new':
187
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state) => {
188
+ // Save to API - create new newsletter
189
+ const apiData = editorStateToAPI(state);
190
+ const response = await fetch('/api/plugin-newsletter/newsletters/new', {
191
+ method: 'POST',
192
+ headers: { 'Content-Type': 'application/json' },
193
+ credentials: 'include',
194
+ body: JSON.stringify(apiData),
195
+ });
196
+ if (!response.ok) {
197
+ const error = await response.json();
198
+ throw new Error(error.message || 'Failed to create newsletter');
199
+ }
200
+ const result = await response.json();
201
+ // Update the URL to the new newsletter's slug
202
+ if (result.slug) {
203
+ window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
204
+ }
205
+ return result;
206
+ }, children: _jsx(NewsletterEditorView, { siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
207
+ case 'subscribers':
208
+ return _jsx(SubscribersView, { siteId: siteId, locale: locale });
209
+ case 'settings':
210
+ return _jsx(SettingsView, { siteId: siteId, locale: locale });
211
+ default:
212
+ return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
213
+ }
214
+ }
215
+ // Export for use as default
216
+ export { NewsletterPlugin as Index };
217
+ // Export initialization utility for easy setup
218
+ export { initNewsletterPlugin } from './init';
219
+ // Export editor state management
220
+ export { EditorProvider, useEditor } from './state/EditorContext';
221
+ // Export block registry
222
+ export { blockRegistry } from './registry';
@@ -0,0 +1,10 @@
1
+ import 'server-only';
2
+ /**
3
+ * Plugin Newsletter - Server-Only Entry Point
4
+ * This file exports only server-side API handlers
5
+ * Used by the dynamic plugin router via @jhits/plugin-newsletter/server
6
+ */
7
+ export { handleNewsletterApi as handleApi } from './api/router';
8
+ export { handleNewsletterApi } from './api/router';
9
+ export type { NewsletterApiConfig } from './types/newsletter';
10
+ //# sourceMappingURL=index.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.server.d.ts","sourceRoot":"","sources":["../src/index.server.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import 'server-only';
2
+ /**
3
+ * Plugin Newsletter - Server-Only Entry Point
4
+ * This file exports only server-side API handlers
5
+ * Used by the dynamic plugin router via @jhits/plugin-newsletter/server
6
+ */
7
+ export { handleNewsletterApi as handleApi } from './api/router';
8
+ export { handleNewsletterApi } from './api/router'; // Keep original export for backward compatibility
package/dist/init.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Newsletter Plugin Initialization Utility
3
+ *
4
+ * Simple function to initialize the newsletter plugin with client configuration.
5
+ * Call this once in your app (e.g., in a script tag or root layout) to configure
6
+ * the newsletter plugin without needing a React component.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { initNewsletterPlugin } from '@jhits/plugin-newsletter/init';
11
+ * import { newsletterConfig } from '@/plugins/newsletter-config';
12
+ *
13
+ * // Call once when your app loads
14
+ * initNewsletterPlugin(newsletterConfig);
15
+ * ```
16
+ */
17
+ import type { ClientBlockDefinition } from './types/block';
18
+ export interface NewsletterPluginConfig {
19
+ /** Custom blocks available in the editor */
20
+ customBlocks?: ClientBlockDefinition[];
21
+ /** Dark mode setting for the editor content area and wrappers (default: true) */
22
+ darkMode?: boolean;
23
+ /** Background colors for the editor */
24
+ backgroundColors?: {
25
+ /** Background color for light mode (REQUIRED) - CSS color value (hex, rgb, or named color) */
26
+ light: string;
27
+ /** Background color for dark mode (optional) - CSS color value (hex, rgb, or named color) */
28
+ dark?: string;
29
+ };
30
+ /** Email configuration */
31
+ emailConfig?: {
32
+ /** Logo URL to display in email header (absolute URL) */
33
+ logoUrl?: string;
34
+ /** Logo alt text */
35
+ logoAlt?: string;
36
+ /** Footer text for emails */
37
+ footerText?: string;
38
+ };
39
+ }
40
+ /**
41
+ * Initialize the newsletter plugin with client configuration
42
+ *
43
+ * This function sets up the window global that the plugin reads from automatically.
44
+ * Call this once when your app loads, before the plugin is rendered.
45
+ *
46
+ * @param config - Newsletter plugin configuration (customBlocks, darkMode, etc.)
47
+ */
48
+ export declare function initNewsletterPlugin(config: NewsletterPluginConfig): void;
49
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACnC,4CAA4C;IAC5C,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,8FAA8F;QAC9F,KAAK,EAAE,MAAM,CAAC;QACd,6FAA6F;QAC7F,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,0BAA0B;IAC1B,WAAW,CAAC,EAAE;QACV,yDAAyD;QACzD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,oBAAoB;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,6BAA6B;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAkBzE"}
package/dist/init.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Newsletter Plugin Initialization Utility
3
+ *
4
+ * Simple function to initialize the newsletter plugin with client configuration.
5
+ * Call this once in your app (e.g., in a script tag or root layout) to configure
6
+ * the newsletter plugin without needing a React component.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { initNewsletterPlugin } from '@jhits/plugin-newsletter/init';
11
+ * import { newsletterConfig } from '@/plugins/newsletter-config';
12
+ *
13
+ * // Call once when your app loads
14
+ * initNewsletterPlugin(newsletterConfig);
15
+ * ```
16
+ */
17
+ 'use client';
18
+ /**
19
+ * Initialize the newsletter plugin with client configuration
20
+ *
21
+ * This function sets up the window global that the plugin reads from automatically.
22
+ * Call this once when your app loads, before the plugin is rendered.
23
+ *
24
+ * @param config - Newsletter plugin configuration (customBlocks, darkMode, etc.)
25
+ */
26
+ export function initNewsletterPlugin(config) {
27
+ if (typeof window === 'undefined') {
28
+ // Server-side: no-op
29
+ return;
30
+ }
31
+ // Initialize the global plugin props object if it doesn't exist
32
+ if (!window.__JHITS_PLUGIN_PROPS__) {
33
+ window.__JHITS_PLUGIN_PROPS__ = {};
34
+ }
35
+ // Set newsletter plugin configuration
36
+ window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'] = {
37
+ customBlocks: config.customBlocks || [],
38
+ darkMode: config.darkMode !== undefined ? config.darkMode : true, // Default to true
39
+ backgroundColors: config.backgroundColors || undefined,
40
+ emailConfig: config.emailConfig || undefined,
41
+ };
42
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Block Renderer
3
+ * Library component for rendering blocks (decoupled from editor)
4
+ * This is the "headless" rendering layer
5
+ *
6
+ * Multi-Tenant: Uses Preview components from client-provided blocks
7
+ */
8
+ import React from 'react';
9
+ import { Block, BlockPreviewProps } from '../../types/block';
10
+ /**
11
+ * Block Renderer Props
12
+ */
13
+ export interface BlockRendererProps {
14
+ /** Block to render */
15
+ block: Block;
16
+ /** Custom renderers for specific block types (optional override) */
17
+ customRenderers?: Map<string, React.ComponentType<BlockPreviewProps>>;
18
+ /** Additional context for rendering */
19
+ context?: {
20
+ siteId?: string;
21
+ locale?: string;
22
+ [key: string]: unknown;
23
+ };
24
+ }
25
+ /**
26
+ * Block Renderer Component
27
+ * Renders a single block using its Preview component from the registry
28
+ */
29
+ export declare function BlockRenderer({ block, customRenderers, context }: BlockRendererProps): import("react/jsx-runtime").JSX.Element;
30
+ /**
31
+ * Blocks Renderer Component
32
+ * Renders multiple blocks in sequence
33
+ */
34
+ export declare function BlocksRenderer({ blocks, customRenderers, context }: {
35
+ blocks: Block[];
36
+ customRenderers?: Map<string, React.ComponentType<BlockPreviewProps>>;
37
+ context?: {
38
+ siteId?: string;
39
+ locale?: string;
40
+ [key: string]: unknown;
41
+ };
42
+ }): import("react/jsx-runtime").JSX.Element;
43
+ //# sourceMappingURL=BlockRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/BlockRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI7D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,sBAAsB;IACtB,KAAK,EAAE,KAAK,CAAC;IAEb,oEAAoE;IACpE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEtE,uCAAuC;IACvC,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,eAAe,EACf,OAAY,EACf,EAAE,kBAAkB,2CAoDpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,MAAM,EACN,eAAe,EACf,OAAY,EACf,EAAE;IACC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL,2CAaA"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Block Renderer
3
+ * Library component for rendering blocks (decoupled from editor)
4
+ * This is the "headless" rendering layer
5
+ *
6
+ * Multi-Tenant: Uses Preview components from client-provided blocks
7
+ */
8
+ 'use client';
9
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
10
+ import { blockRegistry } from '../../registry/BlockRegistry';
11
+ /**
12
+ * Block Renderer Component
13
+ * Renders a single block using its Preview component from the registry
14
+ */
15
+ export function BlockRenderer({ block, customRenderers, context = {} }) {
16
+ // Check for custom renderer override first
17
+ if (customRenderers?.has(block.type)) {
18
+ const CustomRenderer = customRenderers.get(block.type);
19
+ return _jsx(CustomRenderer, { block: block, context: context });
20
+ }
21
+ // Get block definition from registry
22
+ const definition = blockRegistry.get(block.type);
23
+ if (!definition) {
24
+ console.warn(`Block type "${block.type}" not found in registry. Available types:`, blockRegistry.getAll().map(b => b.type).join(', '));
25
+ return (_jsxs("div", { className: "p-4 border border-red-300 bg-red-50 rounded", children: [_jsxs("p", { className: "text-red-600", children: ["Unknown block type: ", block.type] }), _jsx("p", { className: "text-xs text-red-500 mt-1", children: "Make sure this block type is registered via customBlocks prop" })] }));
26
+ }
27
+ // Use the Preview component from the block definition
28
+ const PreviewComponent = definition.components.Preview;
29
+ // Check if this is a container block with children
30
+ const isContainer = definition.isContainer === true;
31
+ const childBlocks = isContainer && block.children && Array.isArray(block.children) && block.children.length > 0
32
+ ? (typeof block.children[0] === 'object'
33
+ ? block.children
34
+ : [])
35
+ : [];
36
+ // If container block, pass child blocks and render function
37
+ if (isContainer) {
38
+ return (_jsx(PreviewComponent, { block: block, context: context, childBlocks: childBlocks, renderChild: (childBlock) => (_jsx(BlockRenderer, { block: childBlock, customRenderers: customRenderers, context: context })) }));
39
+ }
40
+ return _jsx(PreviewComponent, { block: block, context: context });
41
+ }
42
+ /**
43
+ * Blocks Renderer Component
44
+ * Renders multiple blocks in sequence
45
+ */
46
+ export function BlocksRenderer({ blocks, customRenderers, context = {} }) {
47
+ return (_jsx(_Fragment, { children: blocks.map((block) => (_jsx(BlockRenderer, { block: block, customRenderers: customRenderers, context: context }, block.id))) }));
48
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Email Renderer
3
+ * Converts blocks to email-safe HTML
4
+ *
5
+ * Email constraints:
6
+ * - Inline styles only (no CSS classes)
7
+ * - Table-based layouts (no flexbox/grid)
8
+ * - Max width ~600px
9
+ * - Limited CSS support
10
+ * - Images need absolute URLs
11
+ * - No JavaScript
12
+ */
13
+ import { Block } from '../../types/block';
14
+ /**
15
+ * Email rendering context
16
+ */
17
+ export interface EmailRenderContext {
18
+ siteId?: string;
19
+ locale?: string;
20
+ baseUrl?: string;
21
+ logoUrl?: string;
22
+ logoAlt?: string;
23
+ [key: string]: unknown;
24
+ }
25
+ /**
26
+ * Convert a block to email-safe HTML
27
+ */
28
+ export declare function renderBlockToEmail(block: Block, context?: EmailRenderContext): string;
29
+ /**
30
+ * Render all blocks to email HTML
31
+ */
32
+ export declare function renderBlocksToEmail(blocks: Block[], context?: EmailRenderContext): string;
33
+ /**
34
+ * Generate complete email HTML from newsletter
35
+ * Uses the same styling as the welcome email template
36
+ */
37
+ export declare function generateNewsletterEmailHtml(blocks: Block[], metadata: {
38
+ subject: string;
39
+ previewText?: string;
40
+ }, context: EmailRenderContext & {
41
+ unsubscribeUrl?: string;
42
+ footerText?: string;
43
+ logoUrl?: string;
44
+ logoAlt?: string;
45
+ locale?: string;
46
+ }): string;
47
+ //# sourceMappingURL=EmailRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmailRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/email/EmailRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAwBzF;AA0OD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,OAAO,EAAE,kBAAkB,GAAG;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,GACF,MAAM,CA8GR"}