create-nextblock 0.2.33 → 0.2.34

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 (32) hide show
  1. package/package.json +1 -1
  2. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +67 -67
  3. package/templates/nextblock-template/app/[slug]/page.tsx +4 -4
  4. package/templates/nextblock-template/app/cms/blocks/actions.ts +5 -5
  5. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -350
  6. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +13 -16
  7. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +1 -1
  8. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +24 -42
  9. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +6 -6
  10. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +35 -56
  11. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -81
  12. package/templates/nextblock-template/app/cms/media/actions.ts +12 -12
  13. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +3 -3
  14. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +1 -1
  15. package/templates/nextblock-template/app/cms/media/page.tsx +120 -120
  16. package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -87
  17. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +10 -16
  18. package/templates/nextblock-template/app/cms/revisions/service.ts +344 -344
  19. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +1 -1
  20. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +0 -1
  21. package/templates/nextblock-template/app/providers.tsx +2 -2
  22. package/templates/nextblock-template/components/BlockRenderer.tsx +9 -9
  23. package/templates/nextblock-template/components/ResponsiveNav.tsx +22 -22
  24. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  25. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +26 -26
  26. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +41 -41
  27. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +7 -7
  28. package/templates/nextblock-template/components/theme-switcher.tsx +78 -78
  29. package/templates/nextblock-template/eslint.config.mjs +35 -37
  30. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +10 -10
  31. package/templates/nextblock-template/next-env.d.ts +6 -6
  32. package/templates/nextblock-template/package.json +1 -1
@@ -82,7 +82,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
82
82
  const [activeBlock, setActiveBlock] = useState<Block | null>(null);
83
83
  const [insertionIndex, setInsertionIndex] = useState<number | null>(null);
84
84
  const [editingNestedBlockInfo, setEditingNestedBlockInfo] = useState<EditingNestedBlockInfo | null>(null);
85
- const [NestedBlockEditorComponent, setNestedBlockEditorComponent] = useState<ComponentType<Record<string, unknown>> | null>(null);
85
+ const [NestedBlockEditorComponent, setNestedBlockEditorComponent] = useState<ComponentType<any> | null>(null);
86
86
  const [tempNestedBlockContent, setTempNestedBlockContent] = useState<Json | null>(null);
87
87
 
88
88
  useEffect(() => {
@@ -140,30 +140,30 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
140
140
  useEffect(() => {
141
141
  if (editingNestedBlockInfo) {
142
142
  const blockType = editingNestedBlockInfo.blockData.block_type;
143
- let SelectedEditor: React.ComponentType<Record<string, unknown>> | null = null;
143
+ let SelectedEditor: React.ComponentType<any> | null = null;
144
144
 
145
145
  try {
146
146
  switch (blockType) {
147
147
  case 'text':
148
- SelectedEditor = DynamicTextBlockEditor as unknown as ComponentType<Record<string, unknown>>;
148
+ SelectedEditor = DynamicTextBlockEditor;
149
149
  break;
150
150
  case 'heading':
151
- SelectedEditor = DynamicHeadingBlockEditor as unknown as ComponentType<Record<string, unknown>>;
151
+ SelectedEditor = DynamicHeadingBlockEditor;
152
152
  break;
153
153
  case 'image':
154
- SelectedEditor = DynamicImageBlockEditor as unknown as ComponentType<Record<string, unknown>>;
154
+ SelectedEditor = DynamicImageBlockEditor;
155
155
  break;
156
156
  case 'button':
157
- SelectedEditor = DynamicButtonBlockEditor as unknown as ComponentType<Record<string, unknown>>;
157
+ SelectedEditor = DynamicButtonBlockEditor;
158
158
  break;
159
159
  case 'posts_grid':
160
- SelectedEditor = DynamicPostsGridBlockEditor as unknown as ComponentType<Record<string, unknown>>;
160
+ SelectedEditor = DynamicPostsGridBlockEditor;
161
161
  break;
162
162
  case 'video_embed':
163
- SelectedEditor = DynamicVideoEmbedBlockEditor as unknown as ComponentType<Record<string, unknown>>;
163
+ SelectedEditor = DynamicVideoEmbedBlockEditor;
164
164
  break;
165
165
  case 'section':
166
- SelectedEditor = DynamicSectionBlockEditor as unknown as ComponentType<Record<string, unknown>>;
166
+ SelectedEditor = DynamicSectionBlockEditor;
167
167
  break;
168
168
  default:
169
169
  console.warn(`No dynamic editor configured for nested block type: ${blockType}`);
@@ -389,10 +389,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
389
389
  parentBlockId: parentBlockIdStr,
390
390
  columnIndex,
391
391
  blockIndexInColumn,
392
- blockData: {
393
- ...nestedBlockData,
394
- content: nestedBlockData.content as unknown as Json
395
- },
392
+ blockData: nestedBlockData,
396
393
  });
397
394
  } else {
398
395
  console.error("Nested block not found at specified indices:", { parentBlockIdStr, columnIndex, blockIndexInColumn });
@@ -475,8 +472,8 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
475
472
  <EditableBlock
476
473
  block={activeBlock}
477
474
  className="h-full"
478
- onDelete={() => {}}
479
- onContentChange={() => {}}
475
+ onDelete={() => {}} // eslint-disable-line @typescript-eslint/no-empty-function
476
+ onContentChange={() => {}} // eslint-disable-line @typescript-eslint/no-empty-function
480
477
  />
481
478
  </div>
482
479
  ) : null}
@@ -525,7 +522,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
525
522
  if (blockType === "posts_grid") {
526
523
  const fullBlockForEditor: Block = {
527
524
  block_type: editingNestedBlockInfo.blockData.block_type,
528
- content: tempNestedBlockContent as Json,
525
+ content: tempNestedBlockContent,
529
526
  id: 0, // Temporary ID for nested blocks
530
527
  language_id: languageId,
531
528
  order: 0, // Temporary order for nested blocks
@@ -33,7 +33,7 @@ type BlockEditorModalProps = {
33
33
  isOpen: boolean;
34
34
  onClose: () => void;
35
35
  onSave: (updatedContent: unknown) => void;
36
- EditorComponent: LazyExoticComponent<ComponentType<any>>;
36
+ EditorComponent: LazyExoticComponent<ComponentType<BlockEditorProps<unknown>>>;
37
37
  };
38
38
 
39
39
  export function BlockEditorModal({
@@ -28,19 +28,6 @@ interface SortableColumnBlockProps {
28
28
  onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
29
29
  }
30
30
 
31
- type AnyBlockContent = {
32
- html_content?: string;
33
- text_content?: string;
34
- level?: number;
35
- alt_text?: string;
36
- media_id?: string;
37
- text?: string;
38
- url?: string;
39
- title?: string;
40
- columns?: number;
41
- postsPerPage?: number;
42
- };
43
-
44
31
  function SortableColumnBlock({ block, index, columnIndex, onEdit, onDelete, blockType, onClick }: SortableColumnBlockProps) {
45
32
  const {
46
33
  attributes,
@@ -98,35 +85,30 @@ function SortableColumnBlock({ block, index, columnIndex, onEdit, onDelete, bloc
98
85
  </div>
99
86
  </div>
100
87
  <div className="text-xs text-gray-500 dark:text-gray-400">
101
- {(() => {
102
- const content = block.content as AnyBlockContent;
103
- return (
104
- <>
105
- {block.block_type === 'text' && (
106
- <div dangerouslySetInnerHTML={{ __html: (content.html_content || 'Empty text').substring(0, 50) + (content.html_content && content.html_content.length > 50 ? '...' : '') }} />
107
- )}
108
- {block.block_type === 'heading' && (
109
- <div>H{content.level || 1}: {(content.text_content || 'Empty heading').substring(0, 30) + (content.text_content && content.text_content.length > 30 ? '...' : '')}</div>
110
- )}
111
- {block.block_type === 'image' && (
112
- <div>Image: {content.alt_text || content.media_id ? 'Image selected' : 'No image selected'}</div>
113
- )}
114
- {block.block_type === 'button' && (
115
- <div>Button: {content.text || 'No text'} → {content.url || '#'}</div>
116
- )}
117
- {block.block_type === 'video_embed' && (
118
- <div>Video: {content.title || content.url || 'No URL set'}</div>
119
- )}
120
- {block.block_type === 'posts_grid' && (
121
- <div>Posts Grid: {content.columns || 3} cols, {content.postsPerPage || 12} posts</div>
122
- )}
123
- </>
124
- );
125
- })()}
88
+ {block.block_type === 'text' && (
89
+ <div dangerouslySetInnerHTML={{ __html: (block.content.html_content || 'Empty text').substring(0, 50) + (block.content.html_content && block.content.html_content.length > 50 ? '...' : '') }} />
90
+ )}
91
+ {block.block_type === 'heading' && (
92
+ <div>H{block.content.level || 1}: {(block.content.text_content || 'Empty heading').substring(0, 30) + (block.content.text_content && block.content.text_content.length > 30 ? '...' : '')}</div>
93
+ )}
94
+ {block.block_type === 'image' && (
95
+ <div>Image: {block.content.alt_text || block.content.media_id ? 'Image selected' : 'No image selected'}</div>
96
+ )}
97
+ {block.block_type === 'button' && (
98
+ <div>Button: {block.content.text || 'No text'} {block.content.url || '#'}</div>
99
+ )}
100
+ {block.block_type === 'video_embed' && (
101
+ <div>Video: {block.content.title || block.content.url || 'No URL set'}</div>
102
+ )}
103
+ {block.block_type === 'posts_grid' && (
104
+ <div>Posts Grid: {block.content.columns || 3} cols, {block.content.postsPerPage || 12} posts</div>
105
+ )}
126
106
  </div>
127
107
  </div>
128
108
  );
129
109
  }
110
+
111
+ // Column editor component
130
112
  export interface ColumnEditorProps {
131
113
  columnIndex: number;
132
114
  blocks: ColumnBlock[];
@@ -139,7 +121,7 @@ type EditingBlock = ColumnBlock & { index: number };
139
121
  export default function ColumnEditor({ columnIndex, blocks, onBlocksChange, blockType }: ColumnEditorProps) {
140
122
  const [editingBlock, setEditingBlock] = useState<EditingBlock | null>(null);
141
123
  const [isBlockSelectorOpen, setIsBlockSelectorOpen] = useState(false);
142
- const [LazyEditor, setLazyEditor] = useState<React.LazyExoticComponent<React.ComponentType<Record<string, unknown>>> | null>(null);
124
+ const [LazyEditor, setLazyEditor] = useState<React.LazyExoticComponent<React.ComponentType<any>> | null>(null);
143
125
  const [isConfirmOpen, setIsConfirmOpen] = useState(false);
144
126
  const [blockToDeleteIndex, setBlockToDeleteIndex] = useState<number | null>(null);
145
127
 
@@ -152,7 +134,7 @@ export default function ColumnEditor({ columnIndex, blocks, onBlocksChange, bloc
152
134
  const initialContent = getInitialContent(selectedBlockType);
153
135
  const newBlock: ColumnBlock = {
154
136
  block_type: selectedBlockType,
155
- content: (initialContent as Record<string, unknown>) || {},
137
+ content: initialContent || {},
156
138
  temp_id: `temp-${Date.now()}-${Math.random()}`
157
139
  };
158
140
  onBlocksChange([...blocks, newBlock]);
@@ -196,13 +178,13 @@ export default function ColumnEditor({ columnIndex, blocks, onBlocksChange, bloc
196
178
  }
197
179
  };
198
180
 
199
- const handleSave = (newContent: unknown) => {
181
+ const handleSave = (newContent: any) => {
200
182
  if (editingBlock === null) return;
201
183
 
202
184
  const updatedBlocks = [...blocks];
203
185
  updatedBlocks[editingBlock.index] = {
204
186
  ...updatedBlocks[editingBlock.index],
205
- content: newContent as Record<string, unknown>,
187
+ content: newContent,
206
188
  };
207
189
  onBlocksChange(updatedBlocks);
208
190
  setEditingBlock(null);
@@ -16,8 +16,8 @@ import { cn } from '@nextblock-cms/utils';
16
16
  export interface EditableBlockProps {
17
17
  block: Block;
18
18
  onDelete: (blockId: number) => void;
19
- onContentChange: (blockId: number, newContent: Record<string, unknown>) => void;
20
- dragHandleProps?: Record<string, unknown>;
19
+ onContentChange: (blockId: number, newContent: Record<string, any>) => void;
20
+ dragHandleProps?: Record<string, any>;
21
21
  onEditNestedBlock?: (parentBlockId: string, columnIndex: number, blockIndexInColumn: number) => void;
22
22
  className?: string;
23
23
  }
@@ -34,7 +34,7 @@ export default function EditableBlock({
34
34
  // Move all hooks to the top before any conditional returns
35
35
  const [isConfigPanelOpen, setIsConfigPanelOpen] = useState(false);
36
36
  const [editingBlock, setEditingBlock] = useState<Block | null>(null);
37
- const [LazyEditor, setLazyEditor] = useState<LazyExoticComponent<ComponentType<Record<string, unknown>>> | null>(null);
37
+ const [LazyEditor, setLazyEditor] = useState<LazyExoticComponent<ComponentType<any>> | null>(null);
38
38
 
39
39
  const SectionEditor = useMemo(() => {
40
40
  if (block?.block_type === 'section' || block?.block_type === 'hero') {
@@ -156,7 +156,7 @@ export default function EditableBlock({
156
156
  {isSection ? (
157
157
  <div className="mt-2 min-h-[200px]">
158
158
  <Suspense fallback={<div className="flex justify-center items-center h-full"><p>Loading Editor...</p></div>}>
159
- {SectionEditor && <SectionEditor block={block} content={block.content || {}} onChange={(newContent: Record<string, unknown>) => onContentChange(block.id, newContent)} blockType={block.block_type as 'section' | 'hero'} isConfigPanelOpen={isConfigPanelOpen} />}
159
+ {SectionEditor && <SectionEditor block={block} content={block.content || {}} onChange={(newContent: Record<string, any>) => onContentChange(block.id, newContent)} blockType={block.block_type as 'section' | 'hero'} isConfigPanelOpen={isConfigPanelOpen} />}
160
160
  </Suspense>
161
161
  </div>
162
162
  ) : renderPreview()}
@@ -170,8 +170,8 @@ export default function EditableBlock({
170
170
  setEditingBlock(null);
171
171
  setLazyEditor(null);
172
172
  }}
173
- onSave={(newContent: unknown) => {
174
- onContentChange(block.id, newContent as Record<string, unknown>);
173
+ onSave={(newContent: any) => {
174
+ onContentChange(block.id, newContent);
175
175
  setEditingBlock(null);
176
176
  setLazyEditor(null);
177
177
  }}
@@ -38,19 +38,6 @@ interface SectionBlockEditorProps {
38
38
  blockType: 'section' | 'hero';
39
39
  }
40
40
 
41
- type AnyBlockContent = {
42
- html_content?: string;
43
- text_content?: string;
44
- level?: number;
45
- alt_text?: string;
46
- media_id?: string;
47
- text?: string;
48
- url?: string;
49
- title?: string;
50
- columns?: number;
51
- postsPerPage?: number;
52
- };
53
-
54
41
  export default function SectionBlockEditor({
55
42
  content,
56
43
  onChange,
@@ -80,9 +67,8 @@ export default function SectionBlockEditor({
80
67
  }, [content]);
81
68
 
82
69
 
83
- type ColumnBlock = SectionBlockContent['column_blocks'][0][0];
84
70
  const [activeId, setActiveId] = useState<string | null>(null);
85
- const [draggedBlock, setDraggedBlock] = useState<ColumnBlock | null>(null);
71
+ const [draggedBlock, setDraggedBlock] = useState<any>(null);
86
72
 
87
73
  // DND sensors for cross-column dragging
88
74
  const sensors = useSensors(
@@ -306,47 +292,40 @@ return (
306
292
  </span>
307
293
  </div>
308
294
  <div className="text-xs text-blue-600 dark:text-blue-400 mt-1">
309
- {(() => {
310
- const content = draggedBlock.content as AnyBlockContent;
311
- return (
312
- <>
313
- {draggedBlock.block_type === "text" && (
314
- <div
315
- dangerouslySetInnerHTML={{
316
- __html:
317
- (
318
- content.html_content || "Empty text"
319
- ).substring(0, 30) + "...",
320
- }}
321
- />
322
- )}
323
- {draggedBlock.block_type === "heading" && (
324
- <div>
325
- H{content.level || 1}:{" "}
326
- {(
327
- content.text_content || "Empty heading"
328
- ).substring(0, 20) + "..."}
329
- </div>
330
- )}
331
- {draggedBlock.block_type === "image" && (
332
- <div>
333
- Image: {content.alt_text || "No alt text"}
334
- </div>
335
- )}
336
- {draggedBlock.block_type === "button" && (
337
- <div>Button: {content.text || "No text"}</div>
338
- )}
339
- {draggedBlock.block_type === "video_embed" && (
340
- <div>Video: {content.title || "No title"}</div>
341
- )}
342
- {draggedBlock.block_type === "posts_grid" && (
343
- <div>
344
- Posts Grid: {content.columns || 3} cols
345
- </div>
346
- )}
347
- </>
348
- );
349
- })()}
295
+ {draggedBlock.block_type === "text" && (
296
+ <div
297
+ dangerouslySetInnerHTML={{
298
+ __html:
299
+ (
300
+ draggedBlock.content.html_content || "Empty text"
301
+ ).substring(0, 30) + "...",
302
+ }}
303
+ />
304
+ )}
305
+ {draggedBlock.block_type === "heading" && (
306
+ <div>
307
+ H{draggedBlock.content.level || 1}:{" "}
308
+ {(
309
+ draggedBlock.content.text_content || "Empty heading"
310
+ ).substring(0, 20) + "..."}
311
+ </div>
312
+ )}
313
+ {draggedBlock.block_type === "image" && (
314
+ <div>
315
+ Image: {draggedBlock.content.alt_text || "No alt text"}
316
+ </div>
317
+ )}
318
+ {draggedBlock.block_type === "button" && (
319
+ <div>Button: {draggedBlock.content.text || "No text"}</div>
320
+ )}
321
+ {draggedBlock.block_type === "video_embed" && (
322
+ <div>Video: {draggedBlock.content.title || "No title"}</div>
323
+ )}
324
+ {draggedBlock.block_type === "posts_grid" && (
325
+ <div>
326
+ Posts Grid: {draggedBlock.content.columns || 3} cols
327
+ </div>
328
+ )}
350
329
  </div>
351
330
  </div>
352
331
  ) : null}
@@ -1,81 +1,81 @@
1
- // app/cms/blocks/editors/TextBlockEditor.tsx
2
- 'use client';
3
-
4
- import React, { useId, useState, useRef, useCallback } from 'react';
5
- import dynamic from 'next/dynamic';
6
- import MediaPickerDialog from '@/app/cms/media/components/MediaPickerDialog';
7
- import { Label } from '@nextblock-cms/ui';
8
- import { BlockEditorProps } from '../components/BlockEditorModal';
9
-
10
- // Props expected by NotionEditor
11
- type NotionEditorProps = {
12
- content: string;
13
- onChange: (html: string) => void;
14
- openImagePicker?: () => Promise<{ src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null } | null>;
15
- };
16
-
17
- // Use the alias that resolves in your repo; if you mapped @nextblock-cms/editor, swap it here.
18
- const NotionEditor = dynamic<NotionEditorProps>(
19
- () => import('@nextblock-cms/editor').then((m) => m.NotionEditor),
20
- { ssr: false }
21
- );
22
-
23
- export type TextBlockContent = {
24
- html_content?: string;
25
- };
26
-
27
- export default function TextBlockEditor({
28
- content,
29
- onChange,
30
- }: BlockEditorProps<Partial<TextBlockContent>>) {
31
- const labelId = useId();
32
- const [pickerOpen, setPickerOpen] = useState(false);
33
- const resolverRef = useRef<null | ((v: { src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null }) => void)>(null);
34
- const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
35
- const openImagePicker = useCallback(() => {
36
- setPickerOpen(true);
37
- return new Promise<{ src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null } | null>((resolve) => {
38
- resolverRef.current = resolve;
39
- });
40
- }, []);
41
-
42
- return (
43
- <div className="h-full flex flex-col">
44
- <Label htmlFor={labelId} className="sr-only">
45
- Text Content
46
- </Label>
47
-
48
- <div id={labelId} role="group" aria-labelledby={labelId} className="flex-1 min-h-0 flex flex-col">
49
- <NotionEditor
50
- content={content?.html_content ?? ''}
51
- onChange={(html) => onChange({ html_content: html })}
52
- openImagePicker={openImagePicker}
53
- />
54
-
55
- {/* Hidden controlled MediaPickerDialog for image selection */}
56
- <div className="sr-only" aria-hidden>
57
- <MediaPickerDialog
58
- hideTrigger
59
- open={pickerOpen}
60
- onOpenChange={setPickerOpen}
61
- title="Select or Upload Image"
62
- accept={(m) => !!m.file_type?.startsWith('image/')}
63
- onSelect={(media) => {
64
- const src = `${R2_BASE_URL}/${media.object_key}`;
65
- resolverRef.current?.({
66
- src,
67
- alt: media.description || media.file_name || undefined,
68
- width: media.width ?? null,
69
- height: media.height ?? null,
70
- blurDataURL: media.blur_data_url ?? null,
71
- });
72
- resolverRef.current = null;
73
- setPickerOpen(false);
74
- }}
75
- />
76
- </div>
77
- </div>
78
- </div>
79
- );
80
- }
81
-
1
+ // app/cms/blocks/editors/TextBlockEditor.tsx
2
+ 'use client';
3
+
4
+ import React, { useId, useState, useRef, useCallback } from 'react';
5
+ import dynamic from 'next/dynamic';
6
+ import MediaPickerDialog from '@/app/cms/media/components/MediaPickerDialog';
7
+ import { Label } from '@nextblock-cms/ui';
8
+ import { BlockEditorProps } from '../components/BlockEditorModal';
9
+
10
+ // Props expected by NotionEditor
11
+ type NotionEditorProps = {
12
+ content: string;
13
+ onChange: (html: string) => void;
14
+ openImagePicker?: () => Promise<{ src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null } | null>;
15
+ };
16
+
17
+ // Use the alias that resolves in your repo; if you mapped @nextblock-cms/editor, swap it here.
18
+ const NotionEditor = dynamic<NotionEditorProps>(
19
+ () => import('@nextblock-cms/editor').then((m) => m.NotionEditor),
20
+ { ssr: false }
21
+ );
22
+
23
+ export type TextBlockContent = {
24
+ html_content?: string;
25
+ };
26
+
27
+ export default function TextBlockEditor({
28
+ content,
29
+ onChange,
30
+ }: BlockEditorProps<Partial<TextBlockContent>>) {
31
+ const labelId = useId();
32
+ const [pickerOpen, setPickerOpen] = useState(false);
33
+ const resolverRef = useRef<null | ((v: any) => void)>(null);
34
+ const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
35
+ const openImagePicker = useCallback(() => {
36
+ setPickerOpen(true);
37
+ return new Promise<{ src: string; alt?: string; width?: number | null; height?: number | null; blurDataURL?: string | null } | null>((resolve) => {
38
+ resolverRef.current = resolve;
39
+ });
40
+ }, []);
41
+
42
+ return (
43
+ <div className="h-full flex flex-col">
44
+ <Label htmlFor={labelId} className="sr-only">
45
+ Text Content
46
+ </Label>
47
+
48
+ <div id={labelId} role="group" aria-labelledby={labelId} className="flex-1 min-h-0 flex flex-col">
49
+ <NotionEditor
50
+ content={content?.html_content ?? ''}
51
+ onChange={(html) => onChange({ html_content: html })}
52
+ openImagePicker={openImagePicker}
53
+ />
54
+
55
+ {/* Hidden controlled MediaPickerDialog for image selection */}
56
+ <div className="sr-only" aria-hidden>
57
+ <MediaPickerDialog
58
+ hideTrigger
59
+ open={pickerOpen}
60
+ onOpenChange={setPickerOpen}
61
+ title="Select or Upload Image"
62
+ accept={(m) => !!m.file_type?.startsWith('image/')}
63
+ onSelect={(media) => {
64
+ const src = `${R2_BASE_URL}/${media.object_key}`;
65
+ resolverRef.current?.({
66
+ src,
67
+ alt: media.description || media.file_name || undefined,
68
+ width: media.width ?? null,
69
+ height: media.height ?? null,
70
+ blurDataURL: media.blur_data_url ?? null,
71
+ });
72
+ resolverRef.current = null;
73
+ setPickerOpen(false);
74
+ }}
75
+ />
76
+ </div>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
81
+
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { createClient } from "@nextblock-cms/db/server";
5
5
  import { revalidatePath } from "next/cache";
6
- import type { Database, Json } from "@nextblock-cms/db";
6
+ import type { Database } from "@nextblock-cms/db";
7
7
  import { encodedRedirect } from "@nextblock-cms/utils/server";
8
8
 
9
9
  type Media = Database['public']['Tables']['media']['Row'];
@@ -111,7 +111,7 @@ export async function recordMediaUpload(payload: {
111
111
  description: computedDescription,
112
112
  width: primaryVariant.width,
113
113
  height: primaryVariant.height,
114
- variants: allVariantsToStore as unknown as Json, // Store all variants including the original
114
+ variants: allVariantsToStore as any, // Store all variants including the original
115
115
  blur_data_url: payload.blurDataUrl || null, // Store if provided
116
116
  // Ensure all other required fields for 'Media' type are present or nullable
117
117
  };
@@ -382,9 +382,9 @@ export async function moveMultipleMediaItems(
382
382
  let newMainKey = `${folder}${getFilename(mediaRow.object_key)}`;
383
383
 
384
384
  // Build list of keys to move: primary + variant keys (if any)
385
- type Variant = ImageVariant & { [k: string]: unknown };
386
- const oldVariants: Variant[] = Array.isArray(mediaRow.variants) ? (mediaRow.variants as unknown as Variant[]) :
387
- (typeof mediaRow.variants === 'object' && mediaRow.variants !== null ? (mediaRow.variants as unknown as Variant[]) : []);
385
+ type Variant = { objectKey: string; url?: string; [k: string]: any };
386
+ const oldVariants: Variant[] = Array.isArray(mediaRow.variants) ? (mediaRow.variants as Variant[]) :
387
+ (typeof mediaRow.variants === 'object' && mediaRow.variants !== null ? (mediaRow.variants as any) : []);
388
388
 
389
389
  const variantMoves = (oldVariants || []).map((v) => ({
390
390
  oldKey: v.objectKey,
@@ -429,9 +429,9 @@ export async function moveMultipleMediaItems(
429
429
  await s3Client.send(new DeleteObjectCommand({ Bucket: R2_BUCKET_NAME, Key: oldKey }));
430
430
  movedKeys.add(oldKey);
431
431
  if (isMain) mainMoved = true;
432
- } catch (err: unknown) {
433
- const name = (err as Error)?.name || '';
434
- const message = (err as Error)?.message || String(err);
432
+ } catch (err: any) {
433
+ const name = err?.name || '';
434
+ const message = err?.message || String(err);
435
435
  if (isMain) {
436
436
  // Main object missing: attempt fallback to any existing variant
437
437
  let promoted = false;
@@ -499,7 +499,7 @@ export async function moveMultipleMediaItems(
499
499
  .map((v) => {
500
500
  const filename = getFilename(v.objectKey);
501
501
  const updatedKey = `${folder}${filename}`;
502
- const updated = { ...v, objectKey: updatedKey };
502
+ const updated = { ...v, objectKey: updatedKey } as any;
503
503
  if (R2_PUBLIC_URL_BASE) updated.url = `${R2_PUBLIC_URL_BASE}/${updatedKey}`;
504
504
  return updated;
505
505
  });
@@ -507,7 +507,7 @@ export async function moveMultipleMediaItems(
507
507
  // Update DB
508
508
  const { error: updateError } = await supabase
509
509
  .from('media')
510
- .update({ object_key: newMainKey, file_path: newMainKey, folder, variants: newVariants as unknown as Json })
510
+ .update({ object_key: newMainKey, file_path: newMainKey, folder, variants: newVariants as any })
511
511
  .eq('id', item.id);
512
512
  if (updateError) {
513
513
  results.push({ id: item.id, ok: false, error: updateError.message });
@@ -515,8 +515,8 @@ export async function moveMultipleMediaItems(
515
515
  }
516
516
 
517
517
  results.push({ id: item.id, ok: true });
518
- } catch (err: unknown) {
519
- const msg = (err as Error)?.name && (err as Error)?.message ? `${(err as Error).name}: ${(err as Error).message}` : ((err as Error)?.message || String(err));
518
+ } catch (err: any) {
519
+ const msg = err?.name && err?.message ? `${err.name}: ${err.message}` : (err?.message || String(err));
520
520
  results.push({ id: item.id, ok: false, error: msg });
521
521
  }
522
522
  }
@@ -103,17 +103,17 @@ export default function MediaGridClient({ initialMediaItems, r2BaseUrl }: MediaG
103
103
  let hadError = false;
104
104
  for (const item of selectedItems) {
105
105
  const res = await moveSingleMediaItem(item, dest);
106
- if ('error' in res && res.error) {
106
+ if ((res as any)?.error) {
107
107
  // Accumulate errors but keep going
108
108
  hadError = true;
109
- setMoveError((prev) => (prev ? prev + " | " : "") + res.error);
109
+ setMoveError((prev) => (prev ? prev + " | " : "") + (res as any).error);
110
110
  } else {
111
111
  // Update local list
112
112
  setMediaItems((prev) => prev.map((m) => {
113
113
  if (m.id !== item.id) return m;
114
114
  const filename = m.object_key.substring(m.object_key.lastIndexOf('/') + 1);
115
115
  const folder = ensureFolderSlash(dest);
116
- return { ...m, object_key: `${folder}${filename}`, folder };
116
+ return { ...m, object_key: `${folder}${filename}`, folder } as any;
117
117
  }));
118
118
  }
119
119
  moved += 1;
@@ -259,7 +259,7 @@ export default function MediaUploadForm({ onUploadSuccess, returnJustData, defau
259
259
  }
260
260
 
261
261
  } catch (err: unknown) {
262
- const isRedirect = (err instanceof Error && err.message === 'NEXT_REDIRECT') || (typeof (err as { digest?: unknown })?.digest === 'string' && ((err as { digest: string }).digest.startsWith('NEXT_REDIRECT')));
262
+ const isRedirect = (err instanceof Error && err.message === 'NEXT_REDIRECT') || (typeof (err as any)?.digest === 'string' && (err as any).digest.startsWith('NEXT_REDIRECT'));
263
263
 
264
264
  if (isRedirect && !returnJustData) {
265
265
  setUploadStatus("success");