@jhits/plugin-blog 0.0.15 → 0.0.16

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 (208) hide show
  1. package/package.json +5 -4
  2. package/src/api/categories.ts +43 -0
  3. package/src/api/check-title.ts +60 -0
  4. package/src/api/config-handler.ts +76 -0
  5. package/src/api/handler.ts +418 -0
  6. package/src/api/index.ts +33 -0
  7. package/src/api/route.ts +116 -0
  8. package/src/api/router.ts +128 -0
  9. package/src/api-server.ts +11 -0
  10. package/src/config.ts +161 -0
  11. package/src/hooks/index.d.ts +8 -0
  12. package/src/hooks/index.d.ts.map +1 -0
  13. package/src/hooks/index.ts +9 -0
  14. package/src/hooks/useBlog.d.ts +31 -0
  15. package/src/hooks/useBlog.d.ts.map +1 -0
  16. package/src/hooks/useBlog.ts +85 -0
  17. package/src/hooks/useBlogs.d.ts +39 -0
  18. package/src/hooks/useBlogs.d.ts.map +1 -0
  19. package/src/hooks/useBlogs.ts +123 -0
  20. package/src/hooks/useCategories.d.ts +9 -0
  21. package/src/hooks/useCategories.d.ts.map +1 -0
  22. package/src/hooks/useCategories.ts +76 -0
  23. package/src/index.server.ts +14 -0
  24. package/src/index.tsx +335 -0
  25. package/src/init.tsx +63 -0
  26. package/src/lib/blocks/BlockRenderer.d.ts +54 -0
  27. package/src/lib/blocks/BlockRenderer.d.ts.map +1 -0
  28. package/src/lib/blocks/BlockRenderer.tsx +141 -0
  29. package/src/lib/blocks/index.ts +6 -0
  30. package/src/lib/config-storage.d.ts +30 -0
  31. package/src/lib/config-storage.d.ts.map +1 -0
  32. package/src/lib/config-storage.ts +65 -0
  33. package/src/lib/index.ts +9 -0
  34. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +25 -0
  35. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -0
  36. package/src/lib/layouts/blocks/ColumnsBlock.tsx +298 -0
  37. package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +81 -0
  38. package/src/lib/layouts/blocks/SectionBlock.d.ts +25 -0
  39. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +1 -0
  40. package/src/lib/layouts/blocks/SectionBlock.tsx +104 -0
  41. package/src/lib/layouts/blocks/index.ts +8 -0
  42. package/src/lib/layouts/index.d.ts +23 -0
  43. package/src/lib/layouts/index.d.ts.map +1 -0
  44. package/src/lib/layouts/index.ts +52 -0
  45. package/src/lib/layouts/registerLayoutBlocks.d.ts +9 -0
  46. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +1 -0
  47. package/src/lib/layouts/registerLayoutBlocks.ts +64 -0
  48. package/src/lib/mappers/apiMapper.d.ts +66 -0
  49. package/src/lib/mappers/apiMapper.d.ts.map +1 -0
  50. package/src/lib/mappers/apiMapper.ts +254 -0
  51. package/src/lib/migration/index.ts +6 -0
  52. package/src/lib/migration/mapper.ts +140 -0
  53. package/src/lib/rich-text/RichTextEditor.d.ts +45 -0
  54. package/src/lib/rich-text/RichTextEditor.d.ts.map +1 -0
  55. package/src/lib/rich-text/RichTextEditor.tsx +826 -0
  56. package/src/lib/rich-text/RichTextPreview.d.ts +16 -0
  57. package/src/lib/rich-text/RichTextPreview.d.ts.map +1 -0
  58. package/src/lib/rich-text/RichTextPreview.tsx +210 -0
  59. package/src/lib/rich-text/index.d.ts +9 -0
  60. package/src/lib/rich-text/index.d.ts.map +1 -0
  61. package/src/lib/rich-text/index.ts +10 -0
  62. package/src/lib/utils/blockHelpers.d.ts +23 -0
  63. package/src/lib/utils/blockHelpers.d.ts.map +1 -0
  64. package/src/lib/utils/blockHelpers.ts +72 -0
  65. package/src/lib/utils/configValidation.d.ts +23 -0
  66. package/src/lib/utils/configValidation.d.ts.map +1 -0
  67. package/src/lib/utils/configValidation.ts +137 -0
  68. package/src/lib/utils/index.ts +8 -0
  69. package/src/lib/utils/slugify.ts +79 -0
  70. package/src/registry/BlockRegistry.d.ts +62 -0
  71. package/src/registry/BlockRegistry.d.ts.map +1 -0
  72. package/src/registry/BlockRegistry.ts +139 -0
  73. package/src/registry/index.d.ts +6 -0
  74. package/src/registry/index.d.ts.map +1 -0
  75. package/src/registry/index.ts +11 -0
  76. package/src/state/EditorContext.d.ts +45 -0
  77. package/src/state/EditorContext.d.ts.map +1 -0
  78. package/src/state/EditorContext.tsx +283 -0
  79. package/src/state/index.d.ts +7 -0
  80. package/src/state/index.d.ts.map +1 -0
  81. package/src/state/index.ts +8 -0
  82. package/src/state/reducer.d.ts +11 -0
  83. package/src/state/reducer.d.ts.map +1 -0
  84. package/src/state/reducer.ts +694 -0
  85. package/src/state/types.d.ts +162 -0
  86. package/src/state/types.d.ts.map +1 -0
  87. package/src/state/types.ts +160 -0
  88. package/src/types/block.d.ts +221 -0
  89. package/src/types/block.d.ts.map +1 -0
  90. package/src/types/block.ts +269 -0
  91. package/src/types/index.d.ts +8 -0
  92. package/src/types/index.d.ts.map +1 -0
  93. package/src/types/index.ts +17 -0
  94. package/src/types/post.d.ts +136 -0
  95. package/src/types/post.d.ts.map +1 -0
  96. package/src/types/post.ts +169 -0
  97. package/src/utils/client.d.ts +48 -0
  98. package/src/utils/client.d.ts.map +1 -0
  99. package/src/utils/client.ts +122 -0
  100. package/src/utils/index.ts +7 -0
  101. package/src/views/CanvasEditor/BlockWrapper.d.ts +16 -0
  102. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
  103. package/src/views/CanvasEditor/BlockWrapper.tsx +522 -0
  104. package/src/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
  105. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
  106. package/src/views/CanvasEditor/CanvasEditorView.tsx +337 -0
  107. package/src/views/CanvasEditor/EditorBody.d.ts +22 -0
  108. package/src/views/CanvasEditor/EditorBody.d.ts.map +1 -0
  109. package/src/views/CanvasEditor/EditorBody.tsx +665 -0
  110. package/src/views/CanvasEditor/EditorHeader.d.ts +18 -0
  111. package/src/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
  112. package/src/views/CanvasEditor/EditorHeader.tsx +268 -0
  113. package/src/views/CanvasEditor/LayoutContainer.d.ts +17 -0
  114. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +1 -0
  115. package/src/views/CanvasEditor/LayoutContainer.tsx +322 -0
  116. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +13 -0
  117. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +1 -0
  118. package/src/views/CanvasEditor/SaveConfirmationModal.tsx +233 -0
  119. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +14 -0
  120. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
  121. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +92 -0
  122. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +29 -0
  123. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
  124. package/src/views/CanvasEditor/components/EditorCanvas.tsx +160 -0
  125. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
  126. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
  127. package/src/views/CanvasEditor/components/EditorLibrary.tsx +122 -0
  128. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +13 -0
  129. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
  130. package/src/views/CanvasEditor/components/EditorSidebar.tsx +181 -0
  131. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
  132. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
  133. package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
  134. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +25 -0
  135. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -0
  136. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +341 -0
  137. package/src/views/CanvasEditor/components/LibraryItem.d.ts +14 -0
  138. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
  139. package/src/views/CanvasEditor/components/LibraryItem.tsx +80 -0
  140. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +15 -0
  141. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -0
  142. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +212 -0
  143. package/src/views/CanvasEditor/components/index.d.ts +21 -0
  144. package/src/views/CanvasEditor/components/index.d.ts.map +1 -0
  145. package/src/views/CanvasEditor/components/index.ts +28 -0
  146. package/src/views/CanvasEditor/hooks/index.d.ts +10 -0
  147. package/src/views/CanvasEditor/hooks/index.d.ts.map +1 -0
  148. package/src/views/CanvasEditor/hooks/index.ts +10 -0
  149. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +8 -0
  150. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -0
  151. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +103 -0
  152. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
  153. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  154. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +142 -0
  155. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +5 -0
  156. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -0
  157. package/src/views/CanvasEditor/hooks/usePostLoader.ts +39 -0
  158. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
  159. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
  160. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +55 -0
  161. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +25 -0
  162. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +1 -0
  163. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +339 -0
  164. package/src/views/CanvasEditor/index.d.ts +16 -0
  165. package/src/views/CanvasEditor/index.d.ts.map +1 -0
  166. package/src/views/CanvasEditor/index.ts +16 -0
  167. package/src/views/PostManager/EmptyState.d.ts +10 -0
  168. package/src/views/PostManager/EmptyState.d.ts.map +1 -0
  169. package/src/views/PostManager/EmptyState.tsx +42 -0
  170. package/src/views/PostManager/PostActionsMenu.d.ts +12 -0
  171. package/src/views/PostManager/PostActionsMenu.d.ts.map +1 -0
  172. package/src/views/PostManager/PostActionsMenu.tsx +112 -0
  173. package/src/views/PostManager/PostCards.d.ts +15 -0
  174. package/src/views/PostManager/PostCards.d.ts.map +1 -0
  175. package/src/views/PostManager/PostCards.tsx +197 -0
  176. package/src/views/PostManager/PostFilters.d.ts +16 -0
  177. package/src/views/PostManager/PostFilters.d.ts.map +1 -0
  178. package/src/views/PostManager/PostFilters.tsx +95 -0
  179. package/src/views/PostManager/PostManagerView.d.ts +11 -0
  180. package/src/views/PostManager/PostManagerView.d.ts.map +1 -0
  181. package/src/views/PostManager/PostManagerView.tsx +289 -0
  182. package/src/views/PostManager/PostStats.d.ts +11 -0
  183. package/src/views/PostManager/PostStats.d.ts.map +1 -0
  184. package/src/views/PostManager/PostStats.tsx +81 -0
  185. package/src/views/PostManager/PostTable.d.ts +15 -0
  186. package/src/views/PostManager/PostTable.d.ts.map +1 -0
  187. package/src/views/PostManager/PostTable.tsx +230 -0
  188. package/src/views/PostManager/index.d.ts +12 -0
  189. package/src/views/PostManager/index.d.ts.map +1 -0
  190. package/src/views/PostManager/index.ts +15 -0
  191. package/src/views/Preview/PreviewBridgeView.d.ts +12 -0
  192. package/src/views/Preview/PreviewBridgeView.d.ts.map +1 -0
  193. package/src/views/Preview/PreviewBridgeView.tsx +64 -0
  194. package/src/views/Preview/index.d.ts +6 -0
  195. package/src/views/Preview/index.d.ts.map +1 -0
  196. package/src/views/Preview/index.ts +7 -0
  197. package/src/views/Settings/SettingsView.d.ts +10 -0
  198. package/src/views/Settings/SettingsView.d.ts.map +1 -0
  199. package/src/views/Settings/SettingsView.tsx +298 -0
  200. package/src/views/Settings/index.d.ts +6 -0
  201. package/src/views/Settings/index.d.ts.map +1 -0
  202. package/src/views/Settings/index.ts +7 -0
  203. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +12 -0
  204. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -0
  205. package/src/views/SlugSEO/SlugSEOManagerView.tsx +94 -0
  206. package/src/views/SlugSEO/index.d.ts +6 -0
  207. package/src/views/SlugSEO/index.d.ts.map +1 -0
  208. package/src/views/SlugSEO/index.ts +7 -0
@@ -0,0 +1,141 @@
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
+
9
+ 'use client';
10
+
11
+ import React from 'react';
12
+ import { Block, BlockPreviewProps } from '../../types/block';
13
+ import { blockRegistry } from '../../registry/BlockRegistry';
14
+ import { getChildBlocks } from '../utils/blockHelpers';
15
+
16
+ /**
17
+ * Block Renderer Props
18
+ */
19
+ export interface BlockRendererProps {
20
+ /** Block to render */
21
+ block: Block;
22
+
23
+ /** Custom renderers for specific block types (optional override) */
24
+ customRenderers?: Map<string, React.ComponentType<BlockPreviewProps>>;
25
+
26
+ /** Additional context for rendering */
27
+ context?: {
28
+ siteId?: string;
29
+ locale?: string;
30
+ [key: string]: unknown;
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Block Renderer Component
36
+ * Renders a single block using its Preview component from the registry
37
+ *
38
+ * This is the headless rendering layer - it uses the Preview component
39
+ * provided by the client application, allowing each client to have
40
+ * their own design system in the frontend while the editor uses
41
+ * the dashboard's design system.
42
+ */
43
+ export function BlockRenderer({
44
+ block,
45
+ customRenderers,
46
+ context = {}
47
+ }: BlockRendererProps) {
48
+ // Check for custom renderer override first
49
+ if (customRenderers?.has(block.type)) {
50
+ const CustomRenderer = customRenderers.get(block.type)!;
51
+ return <CustomRenderer block={block} context={context} />;
52
+ }
53
+
54
+ // Get block definition from registry
55
+ const definition = blockRegistry.get(block.type);
56
+ if (!definition) {
57
+ console.warn(`Block type "${block.type}" not found in registry. Available types:`,
58
+ blockRegistry.getAll().map(b => b.type).join(', '));
59
+ return (
60
+ <div className="p-4 border border-red-300 bg-red-50 rounded">
61
+ <p className="text-red-600">Unknown block type: {block.type}</p>
62
+ <p className="text-xs text-red-500 mt-1">
63
+ Make sure this block type is registered via customBlocks prop
64
+ </p>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ // Use the Preview component from the block definition
70
+ // This is provided by the client application
71
+ const PreviewComponent = definition.components.Preview;
72
+
73
+ // Check if this is a container block with children
74
+ const isContainer = definition.isContainer === true;
75
+ const childBlocks = isContainer && block.children && Array.isArray(block.children) && block.children.length > 0
76
+ ? (typeof block.children[0] === 'object'
77
+ ? block.children as Block[]
78
+ : [])
79
+ : [];
80
+
81
+ // If container block, pass child blocks and render function
82
+ if (isContainer) {
83
+ return (
84
+ <PreviewComponent
85
+ block={block}
86
+ context={context}
87
+ childBlocks={childBlocks}
88
+ renderChild={(childBlock: Block) => (
89
+ <BlockRenderer
90
+ block={childBlock}
91
+ customRenderers={customRenderers}
92
+ context={context}
93
+ />
94
+ )}
95
+ />
96
+ );
97
+ }
98
+
99
+ return <PreviewComponent block={block} context={context} />;
100
+ }
101
+
102
+ /**
103
+ * Blocks Renderer
104
+ * Renders an array of blocks
105
+ */
106
+ export interface BlocksRendererProps {
107
+ /** Array of blocks to render */
108
+ blocks: Block[];
109
+
110
+ /** Custom renderers for specific block types */
111
+ customRenderers?: Map<string, React.ComponentType<{ block: Block }>>;
112
+
113
+ /** Additional props to pass to renderers */
114
+ renderProps?: Record<string, unknown>;
115
+
116
+ /** Wrapper component for the blocks */
117
+ wrapper?: React.ComponentType<{ children: React.ReactNode }>;
118
+ }
119
+
120
+ export function BlocksRenderer({
121
+ blocks,
122
+ customRenderers,
123
+ renderProps,
124
+ wrapper: Wrapper,
125
+ }: BlocksRendererProps) {
126
+ const content = blocks.map((block, index) => (
127
+ <BlockRenderer
128
+ key={block.id || index}
129
+ block={block}
130
+ customRenderers={customRenderers}
131
+ context={renderProps}
132
+ />
133
+ ));
134
+
135
+ if (Wrapper) {
136
+ return <Wrapper>{content}</Wrapper>;
137
+ }
138
+
139
+ return <>{content}</>;
140
+ }
141
+
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Block library exports
3
+ */
4
+
5
+ export * from './BlockRenderer';
6
+
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Plugin Configuration Storage
3
+ * Stores plugin-specific configuration in MongoDB
4
+ * Used by both client apps and dashboard
5
+ */
6
+ export interface PluginConfigDocument {
7
+ _id?: string;
8
+ pluginId: string;
9
+ siteId: string;
10
+ config: {
11
+ customBlocks?: unknown[];
12
+ darkMode?: boolean;
13
+ backgroundColors?: {
14
+ light: string;
15
+ dark?: string;
16
+ };
17
+ [key: string]: unknown;
18
+ };
19
+ updatedAt: Date;
20
+ }
21
+ export declare function getPluginConfigCollection(getDb: () => Promise<{
22
+ db: any;
23
+ }>): Promise<any>;
24
+ export declare function getPluginConfig(getDb: () => Promise<{
25
+ db: any;
26
+ }>, pluginId: string, siteId?: string): Promise<PluginConfigDocument | null>;
27
+ export declare function savePluginConfig(getDb: () => Promise<{
28
+ db: any;
29
+ }>, pluginId: string, siteId: string, config: PluginConfigDocument['config']): Promise<void>;
30
+ //# sourceMappingURL=config-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-storage.d.ts","sourceRoot":"","sources":["config-storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,oBAAoB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QACJ,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;QACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,gBAAgB,CAAC,EAAE;YACf,KAAK,EAAE,MAAM,CAAC;YACd,IAAI,CAAC,EAAE,MAAM,CAAC;SACjB,CAAC;QACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;IACF,SAAS,EAAE,IAAI,CAAC;CACnB;AAKD,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,CAAC,gBAIhF;AAGD,wBAAsB,eAAe,CACjC,KAAK,EAAE,MAAM,OAAO,CAAC;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,CAAC,EACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAkB,GAC3B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAGtC;AAGD,wBAAsB,gBAAgB,CAClC,KAAK,EAAE,MAAM,OAAO,CAAC;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,CAAC,EACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Plugin Configuration Storage
3
+ * Stores plugin-specific configuration in MongoDB
4
+ * Used by both client apps and dashboard
5
+ */
6
+
7
+ export interface PluginConfigDocument {
8
+ _id?: string;
9
+ pluginId: string;
10
+ siteId: string;
11
+ config: {
12
+ customBlocks?: unknown[];
13
+ darkMode?: boolean;
14
+ backgroundColors?: {
15
+ light: string;
16
+ dark?: string;
17
+ };
18
+ [key: string]: unknown;
19
+ };
20
+ updatedAt: Date;
21
+ }
22
+
23
+ const COLLECTION_NAME = 'pluginConfigs';
24
+
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ export async function getPluginConfigCollection(getDb: () => Promise<{ db: any }>) {
27
+ const { db } = await getDb();
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ return (db as any).collection(COLLECTION_NAME);
30
+ }
31
+
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ export async function getPluginConfig(
34
+ getDb: () => Promise<{ db: any }>,
35
+ pluginId: string,
36
+ siteId: string = 'default'
37
+ ): Promise<PluginConfigDocument | null> {
38
+ const collection = await getPluginConfigCollection(getDb);
39
+ return collection.findOne({ pluginId, siteId }) as Promise<PluginConfigDocument | null>;
40
+ }
41
+
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ export async function savePluginConfig(
44
+ getDb: () => Promise<{ db: any }>,
45
+ pluginId: string,
46
+ siteId: string,
47
+ config: PluginConfigDocument['config']
48
+ ): Promise<void> {
49
+ const collection = await getPluginConfigCollection(getDb);
50
+
51
+ await collection.updateOne(
52
+ { pluginId, siteId },
53
+ {
54
+ $set: {
55
+ config,
56
+ updatedAt: new Date()
57
+ },
58
+ $setOnInsert: {
59
+ pluginId,
60
+ siteId
61
+ }
62
+ },
63
+ { upsert: true }
64
+ );
65
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Library exports
3
+ * All decoupled, reusable components and utilities
4
+ */
5
+
6
+ export * from './blocks';
7
+ export * from './utils';
8
+ export * from './migration';
9
+
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Columns Block
3
+ * Flex/grid container with configurable column layouts
4
+ */
5
+ import React from 'react';
6
+ import { BlockEditProps, BlockPreviewProps } from '../../../types/block';
7
+ import { Block } from '../../../types/block';
8
+ /**
9
+ * Columns Block Edit Component
10
+ */
11
+ export declare const ColumnsEdit: React.FC<BlockEditProps & {
12
+ childBlocks: Block[];
13
+ onChildBlockAdd: (type: string, index: number, containerId: string) => void;
14
+ onChildBlockUpdate: (id: string, data: Partial<Block['data']>, containerId: string) => void;
15
+ onChildBlockDelete: (id: string, containerId: string) => void;
16
+ onChildBlockMove: (id: string, newIndex: number, containerId: string) => void;
17
+ }>;
18
+ /**
19
+ * Columns Block Preview Component
20
+ */
21
+ export declare const ColumnsPreview: React.FC<BlockPreviewProps & {
22
+ childBlocks?: Block[];
23
+ renderChild?: (block: Block) => React.ReactNode;
24
+ }>;
25
+ //# sourceMappingURL=ColumnsBlock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColumnsBlock.d.ts","sourceRoot":"","sources":["ColumnsBlock.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,GAAG;IAChD,WAAW,EAAE,KAAK,EAAE,CAAC;IACrB,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5E,kBAAkB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5F,kBAAkB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACjF,CAyLI,CAAC;AAEN;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,GAAG;IACtD,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;CACnD,CAgFA,CAAC"}
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Columns Block
3
+ * Flex/grid container with configurable column layouts
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React from 'react';
9
+ import { Plus, Trash2 } from 'lucide-react';
10
+ import { BlockEditProps, BlockPreviewProps } from '../../../types/block';
11
+ import { LayoutContainer } from '../../../views/CanvasEditor/LayoutContainer';
12
+ import { COLUMN_LAYOUTS, ColumnLayout } from '../index';
13
+ import { Block } from '../../../types/block';
14
+
15
+ /**
16
+ * Columns Block Edit Component
17
+ */
18
+ export const ColumnsEdit: React.FC<BlockEditProps & {
19
+ childBlocks: Block[];
20
+ onChildBlockAdd: (type: string, index: number, containerId: string) => void;
21
+ onChildBlockUpdate: (id: string, data: Partial<Block['data']>, containerId: string) => void;
22
+ onChildBlockDelete: (id: string, containerId: string) => void;
23
+ onChildBlockMove: (id: string, newIndex: number, containerId: string) => void;
24
+ }> = ({
25
+ block,
26
+ onUpdate,
27
+ isSelected,
28
+ childBlocks = [],
29
+ onChildBlockAdd,
30
+ onChildBlockUpdate,
31
+ onChildBlockDelete,
32
+ onChildBlockMove,
33
+ }) => {
34
+ // Support both old layout-based system and new dynamic column count
35
+ const columnCount = block.data.columnCount as number | undefined;
36
+ const layout: ColumnLayout | undefined = block.data.layout as ColumnLayout | undefined;
37
+
38
+ // Determine number of columns: use columnCount if set, otherwise derive from layout
39
+ let numColumns: number;
40
+ let gridClass: string;
41
+ let columnWidths: number[];
42
+
43
+ // Grid class mapping for Tailwind (must be explicit for dynamic classes)
44
+ const gridClassMap: Record<number, string> = {
45
+ 1: 'grid-cols-1',
46
+ 2: 'grid-cols-2',
47
+ 3: 'grid-cols-3',
48
+ 4: 'grid-cols-4',
49
+ 5: 'grid-cols-5',
50
+ 6: 'grid-cols-6',
51
+ };
52
+
53
+ if (columnCount !== undefined && columnCount > 0) {
54
+ // Dynamic column system
55
+ numColumns = columnCount;
56
+ // Create equal-width columns
57
+ const widthPercent = Math.floor(100 / numColumns);
58
+ columnWidths = Array(numColumns).fill(widthPercent);
59
+ // Use explicit grid class from map, fallback to inline style if needed
60
+ gridClass = gridClassMap[numColumns] || `grid-cols-${numColumns}`;
61
+ } else if (layout && COLUMN_LAYOUTS[layout]) {
62
+ // Legacy layout-based system
63
+ const layoutConfig = COLUMN_LAYOUTS[layout];
64
+ numColumns = layoutConfig.widths.length;
65
+ gridClass = layoutConfig.grid;
66
+ columnWidths = layoutConfig.widths;
67
+ } else {
68
+ // Default to 2 columns
69
+ numColumns = 2;
70
+ gridClass = 'grid-cols-2';
71
+ columnWidths = [50, 50];
72
+ }
73
+
74
+ // Split child blocks into columns based on columnIndex in meta, or round-robin
75
+ const columns: Block[][] = Array.from({ length: numColumns }, () => []);
76
+ childBlocks.forEach((childBlock) => {
77
+ const columnIndex = childBlock.meta?.columnIndex;
78
+ if (typeof columnIndex === 'number' && columnIndex >= 0 && columnIndex < numColumns) {
79
+ columns[columnIndex].push(childBlock);
80
+ } else {
81
+ // Fallback to round-robin if no columnIndex specified
82
+ const index = childBlocks.indexOf(childBlock);
83
+ columns[index % numColumns].push(childBlock);
84
+ }
85
+ });
86
+
87
+ // Get column widths from data or use equal widths
88
+ const storedWidths = block.data.columnWidths as number[] | undefined;
89
+ const currentWidths = storedWidths && storedWidths.length === numColumns
90
+ ? storedWidths
91
+ : columnWidths; // Use calculated equal widths if not set
92
+
93
+ // Add column handler
94
+ const addColumn = () => {
95
+ if (numColumns >= 4) return; // Max 4 columns
96
+ const newColumnCount = numColumns + 1;
97
+ // Calculate new equal widths
98
+ const newWidthPercent = Math.floor(100 / newColumnCount);
99
+ const newWidths = Array(newColumnCount).fill(newWidthPercent);
100
+ onUpdate({
101
+ ...block.data,
102
+ columnCount: newColumnCount,
103
+ columnWidths: newWidths,
104
+ // Clear layout if using dynamic columns
105
+ layout: undefined,
106
+ });
107
+ };
108
+
109
+ // Delete column handler
110
+ const deleteColumn = (colIndex: number) => {
111
+ if (numColumns <= 1) return; // Don't allow deleting the last column
112
+
113
+ // Move blocks from deleted column to the last column (or previous column if deleting last)
114
+ const blocksToMove = columns[colIndex] || [];
115
+ const targetColumnIndex = colIndex === numColumns - 1 ? numColumns - 2 : numColumns - 1;
116
+ const targetColumn = columns[targetColumnIndex] || [];
117
+
118
+ // Move each block to the target column using moveBlock (which will update meta.columnIndex)
119
+ blocksToMove.forEach((blockToMove, blockIndex) => {
120
+ const newIndex = targetColumn.length + blockIndex;
121
+ onChildBlockMove(blockToMove.id, newIndex, `${block.id}-col-${targetColumnIndex}`);
122
+ });
123
+
124
+ // Update column count and widths after moving blocks
125
+ const newColumnCount = numColumns - 1;
126
+ // Remove the deleted column's width and redistribute
127
+ const newWidths = currentWidths.filter((_, i) => i !== colIndex);
128
+ // Normalize to ensure they sum to 100
129
+ const total = newWidths.reduce((sum, w) => sum + w, 0);
130
+ const normalizedWidths = newWidths.map(w => Math.round((w / total) * 100));
131
+
132
+ onUpdate({
133
+ ...block.data,
134
+ columnCount: newColumnCount,
135
+ columnWidths: normalizedWidths,
136
+ layout: undefined,
137
+ });
138
+ };
139
+
140
+ return (
141
+ <div className="rounded-xl bg-white relative">
142
+ {/* Column Grid */}
143
+ <div
144
+ className="grid gap-8 p-6"
145
+ style={{
146
+ gridTemplateColumns: currentWidths.map(w => `${w}%`).join(' '),
147
+ }}
148
+ >
149
+ {Array.from({ length: numColumns }).map((_, colIndex) => (
150
+ <div
151
+ key={colIndex}
152
+ className={`group/col min-h-[200px] rounded-xl border border-dashed transition-all relative ${isSelected
153
+ ? 'border-primary/20'
154
+ : 'border-gray-200/50'
155
+ }`}
156
+ >
157
+ <div className="p-4">
158
+ <div className="mb-3 flex items-center justify-between">
159
+ <span className="text-[10px] font-black uppercase tracking-widest text-gray-400">
160
+ Column {colIndex + 1}
161
+ </span>
162
+ <div className="flex items-center gap-2">
163
+ <span className="text-[9px] text-gray-500">
164
+ {currentWidths[colIndex]}%
165
+ </span>
166
+ {/* Delete Column Button - Similar to table, appears on column hover */}
167
+ {numColumns > 1 && (
168
+ <button
169
+ onClick={(e) => {
170
+ e.stopPropagation();
171
+ deleteColumn(colIndex);
172
+ }}
173
+ className="opacity-0 group-hover/col:opacity-100 p-1 text-neutral-400 hover:text-red-500 transition-all rounded"
174
+ title="Delete Column"
175
+ aria-label="Delete Column"
176
+ >
177
+ <Trash2 size={10} />
178
+ </button>
179
+ )}
180
+ </div>
181
+ </div>
182
+
183
+ <LayoutContainer
184
+ blocks={columns[colIndex] || []}
185
+ containerId={`${block.id}-col-${colIndex}`}
186
+ onBlockAdd={onChildBlockAdd}
187
+ onBlockUpdate={onChildBlockUpdate}
188
+ onBlockDelete={onChildBlockDelete}
189
+ onBlockMove={onChildBlockMove}
190
+ emptyLabel={`Drop blocks in column ${colIndex + 1}`}
191
+ />
192
+ </div>
193
+ </div>
194
+ ))}
195
+ </div>
196
+
197
+ {/* Add Column Button - Similar to table, pinned top right */}
198
+ {numColumns < 4 && (
199
+ <button
200
+ onClick={addColumn}
201
+ className="absolute top-0 right-0 h-8 w-8 flex items-center justify-center bg-white border-l border-b border-gray-200 text-primary hover:bg-primary hover:text-white transition-all z-10 rounded-br-xl"
202
+ title="Add Column"
203
+ >
204
+ <Plus size={16} />
205
+ </button>
206
+ )}
207
+ </div>
208
+ );
209
+ };
210
+
211
+ /**
212
+ * Columns Block Preview Component
213
+ */
214
+ export const ColumnsPreview: React.FC<BlockPreviewProps & {
215
+ childBlocks?: Block[];
216
+ renderChild?: (block: Block) => React.ReactNode;
217
+ }> = ({ block, childBlocks = [], renderChild, context }) => {
218
+ // Support both old layout-based system and new dynamic column count
219
+ const columnCount = block.data.columnCount as number | undefined;
220
+ const layout: ColumnLayout | undefined = block.data.layout as ColumnLayout | undefined;
221
+
222
+ // Determine number of columns: use columnCount if set, otherwise derive from layout
223
+ let numColumns: number;
224
+ let gridClass: string;
225
+
226
+ // Grid class mapping for Tailwind (must be explicit for dynamic classes)
227
+ const gridClassMap: Record<number, string> = {
228
+ 1: 'grid-cols-1',
229
+ 2: 'grid-cols-2',
230
+ 3: 'grid-cols-3',
231
+ 4: 'grid-cols-4',
232
+ 5: 'grid-cols-5',
233
+ 6: 'grid-cols-6',
234
+ };
235
+
236
+ // Get column widths
237
+ const storedWidths = block.data.columnWidths as number[] | undefined;
238
+
239
+ if (columnCount !== undefined && columnCount > 0) {
240
+ // Dynamic column system
241
+ numColumns = columnCount;
242
+ gridClass = gridClassMap[numColumns] || `grid-cols-${numColumns}`;
243
+ } else if (layout && COLUMN_LAYOUTS[layout]) {
244
+ // Legacy layout-based system
245
+ const layoutConfig = COLUMN_LAYOUTS[layout];
246
+ numColumns = layoutConfig.widths.length;
247
+ gridClass = layoutConfig.grid;
248
+ } else {
249
+ // Default to 2 columns
250
+ numColumns = 2;
251
+ gridClass = 'grid-cols-2';
252
+ }
253
+
254
+ // Use stored widths if available, otherwise use equal widths
255
+ const columnWidths = storedWidths && storedWidths.length === numColumns
256
+ ? storedWidths
257
+ : Array(numColumns).fill(Math.floor(100 / numColumns));
258
+
259
+ // If childBlocks are provided, use them; otherwise get from block.children
260
+ const children = childBlocks.length > 0
261
+ ? childBlocks
262
+ : (block.children && Array.isArray(block.children) && typeof block.children[0] === 'object'
263
+ ? block.children as Block[]
264
+ : []);
265
+
266
+ // Split child blocks into columns based on columnIndex in meta, or round-robin
267
+ const columns: Block[][] = Array.from({ length: numColumns }, () => []);
268
+ children.forEach((childBlock) => {
269
+ const columnIndex = childBlock.meta?.columnIndex;
270
+ if (typeof columnIndex === 'number' && columnIndex >= 0 && columnIndex < numColumns) {
271
+ columns[columnIndex].push(childBlock);
272
+ } else {
273
+ // Fallback to round-robin if no columnIndex specified
274
+ const index = children.indexOf(childBlock);
275
+ columns[index % numColumns].push(childBlock);
276
+ }
277
+ });
278
+
279
+ return (
280
+ <div
281
+ className="grid gap-8 my-8"
282
+ style={{
283
+ gridTemplateColumns: columnWidths.map(w => `${w}%`).join(' '),
284
+ }}
285
+ >
286
+ {Array.from({ length: numColumns }).map((_, colIndex) => (
287
+ <div key={colIndex} className="min-h-[100px]">
288
+ {columns[colIndex]?.map((childBlock) => (
289
+ <React.Fragment key={childBlock.id}>
290
+ {renderChild ? renderChild(childBlock) : null}
291
+ </React.Fragment>
292
+ ))}
293
+ </div>
294
+ ))}
295
+ </div>
296
+ );
297
+ };
298
+
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Columns Block
3
+ * Flex/grid container with configurable column layouts
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React from 'react';
9
+ import { Plus, Trash2 } from 'lucide-react';
10
+ import { BlockEditProps, BlockPreviewProps } from '../../../types/block';
11
+ import { LayoutContainer } from '../../../views/CanvasEditor/LayoutContainer';
12
+ import { COLUMN_LAYOUTS, ColumnLayout } from '../index';
13
+ import { Block } from '../../../types/block';
14
+
15
+ /**
16
+ * Columns Block Edit Component
17
+ */
18
+ export const ColumnsEdit: React.FC<BlockEditProps & {
19
+ childBlocks: Block[];
20
+ onChildBlockAdd: (type: string, index: number, containerId: string) => void;
21
+ onChildBlockDelete: (blockId: string) => void;
22
+ onChildBlockMove: (blockId: string, newIndex: number) => void;
23
+ }> = ({
24
+ block,
25
+ childBlocks,
26
+ onChildBlockAdd,
27
+ onChildBlockDelete,
28
+ onChildBlockMove,
29
+ }) => {
30
+ // Support both old layout-based system and new dynamic column count
31
+ const columnCount = block.data.columnCount as number | undefined;
32
+ const layout: ColumnLayout | undefined = block.data.layout as ColumnLayout | undefined;
33
+
34
+ // Determine number of columns: use columnCount if set, otherwise derive from layout
35
+ let numColumns: number;
36
+ let gridClass: string;
37
+ let columnWidths: number[];
38
+
39
+ // Grid class mapping for Tailwind (must be explicit for dynamic classes)
40
+ const gridClassMap: Record<number, string> = {
41
+ 1: 'grid-cols-1',
42
+ 2: 'grid-cols-2',
43
+ 3: 'grid-cols-3',
44
+ 4: 'grid-cols-4',
45
+ 5: 'grid-cols-5',
46
+ 6: 'grid-cols-6',
47
+ };
48
+
49
+ if (columnCount !== undefined && columnCount > 0) {
50
+ // Dynamic column system
51
+ numColumns = columnCount;
52
+ // Create equal-width columns
53
+ const widthPercent = Math.floor(100 / numColumns);
54
+ columnWidths = Array(numColumns).fill(widthPercent);
55
+ // Use explicit grid class from map, fallback to inline style if needed
56
+ gridClass = gridClassMap[numColumns] || \`grid-cols-\${numColumns}\`;
57
+ } else if (layout && COLUMN_LAYOUTS[layout]) {
58
+ // Legacy layout-based system
59
+ const layoutConfig = COLUMN_LAYOUTS[layout];
60
+ numColumns = layoutConfig.widths.length;
61
+ gridClass = layoutConfig.grid;
62
+ columnWidths = layoutConfig.widths;
63
+ } else {
64
+ // Default to 2 columns
65
+ numColumns = 2;
66
+ gridClass = 'grid-cols-2';
67
+ columnWidths = [50, 50];
68
+ }
69
+
70
+ // Split child blocks into columns based on columnIndex in meta, or round-robin
71
+ const columns: Block[][] = Array.from({ length: numColumns }, () => []);
72
+ childBlocks.forEach((childBlock) => {
73
+ const columnIndex = childBlock.meta?.columnIndex;
74
+ if (typeof columnIndex === 'number' && columnIndex >= 0 && columnIndex < numColumns) {
75
+ columns[columnIndex].push(childBlock);
76
+ } else {
77
+ // Fallback to round-robin if no columnIndex specified
78
+ const index = childBlocks.indexOf(childBlock);
79
+ columns[index % numColumns].push(childBlock);
80
+ }
81
+ });