create-nextblock 0.2.33 → 0.2.35
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.
- package/package.json +1 -1
- package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +67 -67
- package/templates/nextblock-template/app/[slug]/page.tsx +4 -4
- package/templates/nextblock-template/app/cms/blocks/actions.ts +5 -5
- package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -350
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +13 -16
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +1 -1
- package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +24 -42
- package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +6 -6
- package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +35 -56
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -81
- package/templates/nextblock-template/app/cms/media/actions.ts +12 -12
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +3 -3
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +1 -1
- package/templates/nextblock-template/app/cms/media/page.tsx +120 -120
- package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -87
- package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +10 -16
- package/templates/nextblock-template/app/cms/revisions/service.ts +344 -344
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +1 -1
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +0 -1
- package/templates/nextblock-template/app/providers.tsx +2 -2
- package/templates/nextblock-template/components/BlockRenderer.tsx +9 -9
- package/templates/nextblock-template/components/ResponsiveNav.tsx +22 -22
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +26 -26
- package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +41 -41
- package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +7 -7
- package/templates/nextblock-template/components/theme-switcher.tsx +78 -78
- package/templates/nextblock-template/eslint.config.mjs +35 -37
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +10 -10
- package/templates/nextblock-template/next-env.d.ts +6 -6
- package/templates/nextblock-template/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// app/[slug]/PageClientContent.tsx
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
-
import React, { useState, useEffect, useMemo } from 'react';
|
|
5
|
-
import { useRouter } from 'next/navigation'; // For navigation on lang switch
|
|
6
|
-
import type { Database } from "@nextblock-cms/db";
|
|
7
|
-
import { useLanguage } from '@/context/LanguageContext';
|
|
8
|
-
import { useCurrentContent } from '@/context/CurrentContentContext';
|
|
9
|
-
import Link from 'next/link';
|
|
10
|
-
import { createClient } from '@nextblock-cms/db';
|
|
11
|
-
|
|
12
|
-
type PageType = Database['public']['Tables']['pages']['Row'];
|
|
13
|
-
type BlockType = Database['public']['Tables']['blocks']['Row'];
|
|
14
|
-
|
|
15
|
-
interface PageClientContentProps {
|
|
16
|
-
initialPageData: (PageType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; }) | null;
|
|
4
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
5
|
+
import { useRouter } from 'next/navigation'; // For navigation on lang switch
|
|
6
|
+
import type { Database } from "@nextblock-cms/db";
|
|
7
|
+
import { useLanguage } from '@/context/LanguageContext';
|
|
8
|
+
import { useCurrentContent } from '@/context/CurrentContentContext';
|
|
9
|
+
import Link from 'next/link';
|
|
10
|
+
import { createClient } from '@nextblock-cms/db';
|
|
11
|
+
|
|
12
|
+
type PageType = Database['public']['Tables']['pages']['Row'];
|
|
13
|
+
type BlockType = Database['public']['Tables']['blocks']['Row'];
|
|
14
|
+
|
|
15
|
+
interface PageClientContentProps {
|
|
16
|
+
initialPageData: (PageType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; }) | null;
|
|
17
17
|
currentSlug: string; // The slug of the currently viewed page
|
|
18
18
|
children: React.ReactNode;
|
|
19
19
|
translatedSlugs?: { [key: string]: string };
|
|
@@ -43,62 +43,62 @@ interface PageClientContentProps {
|
|
|
43
43
|
// }
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
export default function PageClientContent({ initialPageData, currentSlug, children, translatedSlugs }: PageClientContentProps) {
|
|
47
|
-
const { currentLocale, isLoadingLanguages } = useLanguage();
|
|
48
|
-
const { currentContent, setCurrentContent } = useCurrentContent();
|
|
49
|
-
const router = useRouter();
|
|
50
|
-
// currentPageData is the data for the slug currently in the URL.
|
|
51
|
-
// It's initially set by the server for the slug it resolved.
|
|
52
|
-
const [currentPageData, setCurrentPageData] = useState(initialPageData);
|
|
53
|
-
const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false);
|
|
54
|
-
const supabase = useMemo(() => createClient(), []);
|
|
46
|
+
export default function PageClientContent({ initialPageData, currentSlug, children, translatedSlugs }: PageClientContentProps) {
|
|
47
|
+
const { currentLocale, isLoadingLanguages } = useLanguage();
|
|
48
|
+
const { currentContent, setCurrentContent } = useCurrentContent();
|
|
49
|
+
const router = useRouter();
|
|
50
|
+
// currentPageData is the data for the slug currently in the URL.
|
|
51
|
+
// It's initially set by the server for the slug it resolved.
|
|
52
|
+
const [currentPageData, setCurrentPageData] = useState(initialPageData);
|
|
53
|
+
const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false);
|
|
54
|
+
const supabase = useMemo(() => createClient(), []);
|
|
55
55
|
|
|
56
56
|
// Memoize pageId and pageSlug
|
|
57
|
-
const pageId = useMemo(() => currentPageData?.id, [currentPageData?.id]);
|
|
58
|
-
const pageSlug = useMemo(() => currentPageData?.slug, [currentPageData?.id, currentLocale]); // include locale so updates propagate
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (currentLocale && currentPageData && currentPageData.language_code !== currentLocale && translatedSlugs) {
|
|
62
|
-
// Current page's language doesn't match context, try to navigate to translated version
|
|
63
|
-
setIsLoadingTargetLang(true);
|
|
64
|
-
const targetSlug = translatedSlugs[currentLocale];
|
|
65
|
-
|
|
66
|
-
if (targetSlug && targetSlug !== currentSlug) {
|
|
67
|
-
router.push(`/${targetSlug}`); // Navigate to the translated slug's URL
|
|
68
|
-
} else if (targetSlug && targetSlug === currentSlug) {
|
|
69
|
-
// Same slug across languages - refetch the page in the target language and update content
|
|
70
|
-
(async () => {
|
|
71
|
-
const { data, error } = await supabase
|
|
72
|
-
.from("pages")
|
|
73
|
-
.select("*, languages!inner(code), blocks(*)")
|
|
74
|
-
.eq("slug", targetSlug)
|
|
75
|
-
.eq("languages.code", currentLocale)
|
|
76
|
-
.eq("status", "published")
|
|
77
|
-
.order('order', { foreignTable: 'blocks', ascending: true })
|
|
78
|
-
.maybeSingle();
|
|
79
|
-
|
|
80
|
-
if (!error && data) {
|
|
81
|
-
const langInfo = Array.isArray(data.languages) ? data.languages[0] : (data.languages as unknown as { code?: string });
|
|
82
|
-
setCurrentPageData({
|
|
83
|
-
...(data as PageType),
|
|
84
|
-
blocks: (data as any).blocks || [],
|
|
85
|
-
language_code: langInfo?.code || currentLocale,
|
|
86
|
-
language_id: data.language_id,
|
|
87
|
-
translation_group_id: data.translation_group_id || currentPageData.translation_group_id,
|
|
88
|
-
} as typeof currentPageData);
|
|
89
|
-
} else {
|
|
90
|
-
// fallback to refresh if fetch fails
|
|
91
|
-
router.refresh();
|
|
92
|
-
}
|
|
93
|
-
setIsLoadingTargetLang(false);
|
|
94
|
-
})();
|
|
95
|
-
} else {
|
|
96
|
-
console.warn(`No published translation found for group ${currentPageData.translation_group_id} in language ${currentLocale} using pre-fetched slugs.`);
|
|
97
|
-
// Optionally, provide feedback to the user that translation is not available
|
|
98
|
-
setIsLoadingTargetLang(false);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}, [currentLocale, currentPageData, currentSlug, router, initialPageData, translatedSlugs]); // Rerun if initialPageData changes (e.g. after revalidation)
|
|
57
|
+
const pageId = useMemo(() => currentPageData?.id, [currentPageData?.id]);
|
|
58
|
+
const pageSlug = useMemo(() => currentPageData?.slug, [currentPageData?.id, currentLocale]); // include locale so updates propagate
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (currentLocale && currentPageData && currentPageData.language_code !== currentLocale && translatedSlugs) {
|
|
62
|
+
// Current page's language doesn't match context, try to navigate to translated version
|
|
63
|
+
setIsLoadingTargetLang(true);
|
|
64
|
+
const targetSlug = translatedSlugs[currentLocale];
|
|
65
|
+
|
|
66
|
+
if (targetSlug && targetSlug !== currentSlug) {
|
|
67
|
+
router.push(`/${targetSlug}`); // Navigate to the translated slug's URL
|
|
68
|
+
} else if (targetSlug && targetSlug === currentSlug) {
|
|
69
|
+
// Same slug across languages - refetch the page in the target language and update content
|
|
70
|
+
(async () => {
|
|
71
|
+
const { data, error } = await supabase
|
|
72
|
+
.from("pages")
|
|
73
|
+
.select("*, languages!inner(code), blocks(*)")
|
|
74
|
+
.eq("slug", targetSlug)
|
|
75
|
+
.eq("languages.code", currentLocale)
|
|
76
|
+
.eq("status", "published")
|
|
77
|
+
.order('order', { foreignTable: 'blocks', ascending: true })
|
|
78
|
+
.maybeSingle();
|
|
79
|
+
|
|
80
|
+
if (!error && data) {
|
|
81
|
+
const langInfo = Array.isArray(data.languages) ? data.languages[0] : (data.languages as unknown as { code?: string });
|
|
82
|
+
setCurrentPageData({
|
|
83
|
+
...(data as PageType),
|
|
84
|
+
blocks: (data as any).blocks || [],
|
|
85
|
+
language_code: langInfo?.code || currentLocale,
|
|
86
|
+
language_id: data.language_id,
|
|
87
|
+
translation_group_id: data.translation_group_id || currentPageData.translation_group_id,
|
|
88
|
+
} as typeof currentPageData);
|
|
89
|
+
} else {
|
|
90
|
+
// fallback to refresh if fetch fails
|
|
91
|
+
router.refresh();
|
|
92
|
+
}
|
|
93
|
+
setIsLoadingTargetLang(false);
|
|
94
|
+
})();
|
|
95
|
+
} else {
|
|
96
|
+
console.warn(`No published translation found for group ${currentPageData.translation_group_id} in language ${currentLocale} using pre-fetched slugs.`);
|
|
97
|
+
// Optionally, provide feedback to the user that translation is not available
|
|
98
|
+
setIsLoadingTargetLang(false);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}, [currentLocale, currentPageData, currentSlug, router, initialPageData, translatedSlugs]); // Rerun if initialPageData changes (e.g. after revalidation)
|
|
102
102
|
|
|
103
103
|
// Update HTML lang attribute based on the *actually displayed* content's language
|
|
104
104
|
useEffect(() => {
|
|
@@ -40,7 +40,7 @@ export async function generateStaticParams(): Promise<ResolvedPageParams[]> {
|
|
|
40
40
|
console.error("SSG: Error fetching page slugs for static params:", error);
|
|
41
41
|
return [];
|
|
42
42
|
}
|
|
43
|
-
return pages.map((page
|
|
43
|
+
return pages.map((page) => ({ slug: page.slug }));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export async function generateMetadata(
|
|
@@ -87,8 +87,8 @@ export async function generateMetadata(
|
|
|
87
87
|
|
|
88
88
|
const alternates: { [key: string]: string } = {};
|
|
89
89
|
if (languages && pageTranslations) {
|
|
90
|
-
pageTranslations.forEach(
|
|
91
|
-
const langInfo = languages.find(
|
|
90
|
+
pageTranslations.forEach(pt => {
|
|
91
|
+
const langInfo = languages.find(l => l.id === pt.language_id);
|
|
92
92
|
if (langInfo) {
|
|
93
93
|
alternates[langInfo.code] = `${siteUrl}/${pt.slug}`;
|
|
94
94
|
}
|
|
@@ -151,7 +151,7 @@ export default async function DynamicPage({ params: paramsPromise }: PageProps)
|
|
|
151
151
|
const r2BaseUrl = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
|
|
152
152
|
|
|
153
153
|
if (pageData && pageData.blocks && r2BaseUrl) {
|
|
154
|
-
const heroBlock = pageData.blocks.find(
|
|
154
|
+
const heroBlock = pageData.blocks.find(block => block.block_type === 'hero');
|
|
155
155
|
if (heroBlock) {
|
|
156
156
|
const heroContent = heroBlock.content as unknown as HeroBlockContent;
|
|
157
157
|
if (
|
|
@@ -5,7 +5,7 @@ import { createClient } from "@nextblock-cms/db/server";
|
|
|
5
5
|
import { revalidatePath } from "next/cache";
|
|
6
6
|
import type { Database, Json } from "@nextblock-cms/db";
|
|
7
7
|
import { getInitialContent, isValidBlockType } from "../../../lib/blocks/blockRegistry";
|
|
8
|
-
import { getFullPageContent, getFullPostContent
|
|
8
|
+
import { getFullPageContent, getFullPostContent } from "../revisions/utils";
|
|
9
9
|
import { createPageRevision, createPostRevision } from "../revisions/service";
|
|
10
10
|
|
|
11
11
|
type Block = Database['public']['Tables']['blocks']['Row'];
|
|
@@ -160,7 +160,7 @@ export async function updateBlock(blockId: number, newContent: unknown, pageId?:
|
|
|
160
160
|
return { error: "Block not found." };
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
let prevContentAggregate:
|
|
163
|
+
let prevContentAggregate: Awaited<ReturnType<typeof getFullPageContent>> | Awaited<ReturnType<typeof getFullPostContent>> | null = null;
|
|
164
164
|
if (existingBlock.page_id) {
|
|
165
165
|
prevContentAggregate = await getFullPageContent(existingBlock.page_id);
|
|
166
166
|
} else if (existingBlock.post_id) {
|
|
@@ -189,7 +189,7 @@ export async function updateBlock(blockId: number, newContent: unknown, pageId?:
|
|
|
189
189
|
} else if (existingBlock.post_id) {
|
|
190
190
|
const nextContentAggregate = await getFullPostContent(existingBlock.post_id, { overrideBlockId: blockId, overrideBlockContent: newContent });
|
|
191
191
|
if (nextContentAggregate) {
|
|
192
|
-
await createPostRevision(existingBlock.post_id, user.id, prevContentAggregate as
|
|
192
|
+
await createPostRevision(existingBlock.post_id, user.id, prevContentAggregate as any, nextContentAggregate as any);
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -250,7 +250,7 @@ export async function deleteBlock(blockId: number, pageId?: number | null, postI
|
|
|
250
250
|
return { error: "Block not found." };
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
let previousAggregate:
|
|
253
|
+
let previousAggregate: Awaited<ReturnType<typeof getFullPageContent>> | Awaited<ReturnType<typeof getFullPostContent>> | null = null;
|
|
254
254
|
if (existingBlock.page_id) {
|
|
255
255
|
previousAggregate = await getFullPageContent(existingBlock.page_id);
|
|
256
256
|
} else if (existingBlock.post_id) {
|
|
@@ -274,7 +274,7 @@ export async function deleteBlock(blockId: number, pageId?: number | null, postI
|
|
|
274
274
|
} else if (existingBlock.post_id) {
|
|
275
275
|
const nextAggregate = await getFullPostContent(existingBlock.post_id, { excludeDeletedBlockId: blockId });
|
|
276
276
|
if (nextAggregate) {
|
|
277
|
-
await createPostRevision(existingBlock.post_id, user.id, previousAggregate as
|
|
277
|
+
await createPostRevision(existingBlock.post_id, user.id, previousAggregate as any, nextAggregate as any);
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
}
|