create-noxion 0.1.1 → 0.3.0

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 (97) hide show
  1. package/README.md +2 -2
  2. package/dist/add.d.ts +13 -0
  3. package/dist/add.d.ts.map +1 -0
  4. package/dist/add.js +141 -0
  5. package/dist/add.js.map +1 -0
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +195 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/scaffold.d.ts +9 -1
  11. package/dist/scaffold.d.ts.map +1 -1
  12. package/dist/scaffold.js +20 -3
  13. package/dist/scaffold.js.map +1 -1
  14. package/dist/templates/docs/.env +8 -0
  15. package/dist/templates/docs/.env.example +9 -0
  16. package/dist/templates/docs/app/[slug]/page.tsx +49 -0
  17. package/dist/templates/docs/app/api/notion-webhook/route.ts +12 -0
  18. package/dist/templates/docs/app/api/revalidate/route.ts +12 -0
  19. package/dist/templates/docs/app/globals.css +31 -0
  20. package/dist/templates/docs/app/layout.tsx +36 -0
  21. package/dist/templates/docs/app/not-found.tsx +38 -0
  22. package/dist/templates/docs/app/page.tsx +45 -0
  23. package/dist/templates/docs/app/robots.ts +7 -0
  24. package/dist/templates/docs/app/site-layout.tsx +28 -0
  25. package/dist/templates/docs/app/sitemap.ts +9 -0
  26. package/dist/templates/docs/app/tailwind.css +3 -0
  27. package/dist/templates/docs/app/theme-script.tsx +16 -0
  28. package/dist/templates/docs/lib/config.ts +24 -0
  29. package/dist/templates/docs/lib/notion.ts +48 -0
  30. package/dist/templates/docs/next.config.ts +26 -0
  31. package/dist/templates/docs/noxion.config.ts +23 -0
  32. package/dist/templates/docs/package.json +25 -0
  33. package/dist/templates/docs/tsconfig.json +19 -0
  34. package/dist/templates/full/.env +10 -0
  35. package/dist/templates/full/.env.example +11 -0
  36. package/dist/templates/full/app/[slug]/page.tsx +55 -0
  37. package/dist/templates/full/app/api/notion-webhook/route.ts +12 -0
  38. package/dist/templates/full/app/api/revalidate/route.ts +12 -0
  39. package/dist/templates/full/app/globals.css +31 -0
  40. package/dist/templates/full/app/home-content.tsx +59 -0
  41. package/dist/templates/full/app/layout.tsx +36 -0
  42. package/dist/templates/full/app/not-found.tsx +38 -0
  43. package/dist/templates/full/app/page.tsx +50 -0
  44. package/dist/templates/full/app/robots.ts +7 -0
  45. package/dist/templates/full/app/site-layout.tsx +32 -0
  46. package/dist/templates/full/app/sitemap.ts +9 -0
  47. package/dist/templates/full/app/tag/[tag]/page.tsx +66 -0
  48. package/dist/templates/full/app/tailwind.css +3 -0
  49. package/dist/templates/full/app/theme-script.tsx +16 -0
  50. package/dist/templates/full/lib/config.ts +24 -0
  51. package/dist/templates/full/lib/notion.ts +66 -0
  52. package/dist/templates/full/next.config.ts +26 -0
  53. package/dist/templates/full/noxion.config.ts +40 -0
  54. package/dist/templates/full/package.json +25 -0
  55. package/dist/templates/full/tsconfig.json +19 -0
  56. package/dist/templates/nextjs/.env.example +1 -0
  57. package/dist/templates/nextjs/app/api/notion-webhook/route.ts +12 -0
  58. package/dist/templates/nextjs/app/home-content.tsx +17 -26
  59. package/dist/templates/nextjs/app/layout.tsx +4 -3
  60. package/dist/templates/nextjs/app/page.tsx +3 -3
  61. package/dist/templates/nextjs/app/site-layout.tsx +28 -0
  62. package/dist/templates/nextjs/app/tag/[tag]/page.tsx +4 -4
  63. package/dist/templates/nextjs/app/tailwind.css +3 -0
  64. package/dist/templates/nextjs/lib/config.ts +1 -0
  65. package/dist/templates/nextjs/lib/notion.ts +5 -5
  66. package/dist/templates/nextjs/package.json +3 -3
  67. package/dist/templates/plugin/noxion-plugin.json +10 -0
  68. package/dist/templates/plugin/package.json +41 -0
  69. package/dist/templates/plugin/src/__tests__/plugin.test.ts +39 -0
  70. package/dist/templates/plugin/src/index.ts +20 -0
  71. package/dist/templates/plugin/tsconfig.json +20 -0
  72. package/dist/templates/portfolio/.env +8 -0
  73. package/dist/templates/portfolio/.env.example +9 -0
  74. package/dist/templates/portfolio/app/[slug]/page.tsx +48 -0
  75. package/dist/templates/portfolio/app/api/notion-webhook/route.ts +12 -0
  76. package/dist/templates/portfolio/app/api/revalidate/route.ts +12 -0
  77. package/dist/templates/portfolio/app/globals.css +31 -0
  78. package/dist/templates/portfolio/app/layout.tsx +36 -0
  79. package/dist/templates/portfolio/app/not-found.tsx +38 -0
  80. package/dist/templates/portfolio/app/page.tsx +70 -0
  81. package/dist/templates/portfolio/app/robots.ts +7 -0
  82. package/dist/templates/portfolio/app/site-layout.tsx +31 -0
  83. package/dist/templates/portfolio/app/sitemap.ts +9 -0
  84. package/dist/templates/portfolio/app/tailwind.css +3 -0
  85. package/dist/templates/portfolio/app/theme-script.tsx +16 -0
  86. package/dist/templates/portfolio/lib/config.ts +24 -0
  87. package/dist/templates/portfolio/lib/notion.ts +48 -0
  88. package/dist/templates/portfolio/next.config.ts +26 -0
  89. package/dist/templates/portfolio/noxion.config.ts +23 -0
  90. package/dist/templates/portfolio/package.json +25 -0
  91. package/dist/templates/portfolio/tsconfig.json +19 -0
  92. package/dist/templates/theme/package.json +42 -0
  93. package/dist/templates/theme/src/index.ts +41 -0
  94. package/dist/templates/theme/styles/theme.css +23 -0
  95. package/dist/templates/theme/tsconfig.json +20 -0
  96. package/package.json +1 -1
  97. package/dist/templates/nextjs/app/providers.tsx +0 -45
@@ -0,0 +1,12 @@
1
+ import { revalidatePath } from "next/cache";
2
+ import { createNotionWebhookHandler } from "@noxion/adapter-nextjs";
3
+ import { siteConfig } from "../../../lib/config";
4
+
5
+ const handler = createNotionWebhookHandler({
6
+ config: siteConfig,
7
+ revalidatePath,
8
+ });
9
+
10
+ export async function POST(request: Request) {
11
+ return handler(request);
12
+ }
@@ -0,0 +1,12 @@
1
+ import { revalidatePath } from "next/cache";
2
+ import { createRevalidateHandler } from "@noxion/adapter-nextjs";
3
+ import { siteConfig } from "../../../lib/config";
4
+
5
+ const handler = createRevalidateHandler({
6
+ config: siteConfig,
7
+ revalidatePath,
8
+ });
9
+
10
+ export async function POST(request: Request) {
11
+ return handler(request as never);
12
+ }
@@ -0,0 +1,31 @@
1
+ @import "@noxion/notion-renderer/styles";
2
+
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ margin: 0;
8
+ padding: 0;
9
+ }
10
+
11
+ html {
12
+ font-family: var(--noxion-font-sans, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+ }
16
+
17
+ body {
18
+ background-color: var(--noxion-background, #fff);
19
+ color: var(--noxion-foreground, #0a0a0a);
20
+ min-height: 100vh;
21
+ }
22
+
23
+ [data-theme="dark"] body {
24
+ background-color: var(--noxion-background, #0a0a0a);
25
+ color: var(--noxion-foreground, #fafafa);
26
+ }
27
+
28
+ a {
29
+ color: inherit;
30
+ text-decoration: none;
31
+ }
@@ -0,0 +1,36 @@
1
+ import type { Metadata } from "next";
2
+ import { generateNoxionListMetadata, generateWebSiteLD } from "@noxion/adapter-nextjs";
3
+ import { siteConfig } from "../lib/config";
4
+ import { ThemeScript } from "./theme-script";
5
+ import { SiteLayout } from "./site-layout";
6
+ import "./tailwind.css";
7
+ import "./globals.css";
8
+
9
+ export function generateMetadata(): Metadata {
10
+ return generateNoxionListMetadata(siteConfig);
11
+ }
12
+
13
+ export default function RootLayout({
14
+ children,
15
+ }: {
16
+ children: React.ReactNode;
17
+ }) {
18
+ const jsonLd = generateWebSiteLD(siteConfig);
19
+
20
+ return (
21
+ <html lang={siteConfig.language} suppressHydrationWarning>
22
+ <head>
23
+ <ThemeScript />
24
+ <script
25
+ type="application/ld+json"
26
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
27
+ />
28
+ </head>
29
+ <body>
30
+ <SiteLayout siteName={siteConfig.name} author={siteConfig.author}>
31
+ {children}
32
+ </SiteLayout>
33
+ </body>
34
+ </html>
35
+ );
36
+ }
@@ -0,0 +1,38 @@
1
+ import Link from "next/link";
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div
6
+ style={{
7
+ display: "flex",
8
+ flexDirection: "column",
9
+ alignItems: "center",
10
+ justifyContent: "center",
11
+ minHeight: "50vh",
12
+ textAlign: "center",
13
+ gap: "1rem",
14
+ }}
15
+ >
16
+ <h1 style={{ fontSize: "4rem", fontWeight: 700, color: "var(--noxion-mutedForeground, #737373)" }}>
17
+ 404
18
+ </h1>
19
+ <p style={{ fontSize: "1.125rem", color: "var(--noxion-mutedForeground, #737373)" }}>
20
+ This page could not be found.
21
+ </p>
22
+ <Link
23
+ href="/"
24
+ style={{
25
+ marginTop: "1rem",
26
+ padding: "0.5rem 1.5rem",
27
+ borderRadius: "var(--noxion-border-radius, 0.5rem)",
28
+ backgroundColor: "var(--noxion-primary, #2563eb)",
29
+ color: "var(--noxion-primaryForeground, #fff)",
30
+ fontSize: "0.875rem",
31
+ fontWeight: 500,
32
+ }}
33
+ >
34
+ Go Home
35
+ </Link>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,45 @@
1
+ import Link from "next/link";
2
+ import { getAllPages } from "../lib/notion";
3
+
4
+ export const revalidate = 3600;
5
+
6
+ export default async function HomePage() {
7
+ const pages = await getAllPages();
8
+
9
+ const sections = new Map<string, typeof pages>();
10
+ for (const page of pages) {
11
+ const section = (page.metadata.section as string) ?? "General";
12
+ if (!sections.has(section)) sections.set(section, []);
13
+ sections.get(section)!.push(page);
14
+ }
15
+
16
+ return (
17
+ <div>
18
+ <h1 style={{ fontSize: "1.5rem", fontWeight: 700, marginBottom: "2rem" }}>
19
+ Documentation
20
+ </h1>
21
+ {[...sections.entries()].map(([section, sectionPages]) => (
22
+ <div key={section} style={{ marginBottom: "2rem" }}>
23
+ <h2 style={{ fontSize: "1.125rem", fontWeight: 600, marginBottom: "0.75rem" }}>
24
+ {section}
25
+ </h2>
26
+ <ul style={{ listStyle: "none", display: "flex", flexDirection: "column", gap: "0.5rem" }}>
27
+ {sectionPages.map((page) => (
28
+ <li key={page.id}>
29
+ <Link
30
+ href={`/${page.slug}`}
31
+ style={{
32
+ color: "var(--noxion-primary, #2563eb)",
33
+ fontSize: "0.95rem",
34
+ }}
35
+ >
36
+ {page.title}
37
+ </Link>
38
+ </li>
39
+ ))}
40
+ </ul>
41
+ </div>
42
+ ))}
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,7 @@
1
+ import type { MetadataRoute } from "next";
2
+ import { generateNoxionRobots } from "@noxion/adapter-nextjs";
3
+ import { siteConfig } from "../lib/config";
4
+
5
+ export default function robots(): MetadataRoute.Robots {
6
+ return generateNoxionRobots(siteConfig);
7
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import { DocsLayout, Header, Footer } from "@noxion/theme-default";
4
+
5
+ interface SiteLayoutProps {
6
+ siteName: string;
7
+ author: string;
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ export function SiteLayout({ siteName, author, children }: SiteLayoutProps) {
12
+ const SiteHeader = () => (
13
+ <Header
14
+ siteName={siteName}
15
+ navigation={[{ label: "Home", href: "/" }]}
16
+ />
17
+ );
18
+
19
+ const SiteFooter = () => (
20
+ <Footer siteName={siteName} author={author} />
21
+ );
22
+
23
+ return (
24
+ <DocsLayout slots={{ header: SiteHeader, footer: SiteFooter }}>
25
+ {children}
26
+ </DocsLayout>
27
+ );
28
+ }
@@ -0,0 +1,9 @@
1
+ import type { MetadataRoute } from "next";
2
+ import { generateNoxionSitemap } from "@noxion/adapter-nextjs";
3
+ import { getAllPosts } from "../lib/notion";
4
+ import { siteConfig } from "../lib/config";
5
+
6
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
7
+ const posts = await getAllPosts();
8
+ return generateNoxionSitemap(posts, siteConfig);
9
+ }
@@ -0,0 +1,3 @@
1
+ @import "@noxion/theme-default/styles/tailwind";
2
+
3
+ @source "../**/*.{ts,tsx}";
@@ -0,0 +1,16 @@
1
+ export function ThemeScript() {
2
+ const script = `
3
+ (function() {
4
+ try {
5
+ var stored = localStorage.getItem('noxion-theme');
6
+ var theme = stored || 'system';
7
+ if (theme === 'system') {
8
+ theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
9
+ }
10
+ document.documentElement.dataset.theme = theme;
11
+ } catch (e) {}
12
+ })();
13
+ `;
14
+
15
+ return <script dangerouslySetInnerHTML={{ __html: script }} />;
16
+ }
@@ -0,0 +1,24 @@
1
+ import { loadConfig } from "@noxion/core";
2
+ import type { NoxionConfig } from "@noxion/core";
3
+ import noxionConfigInput from "../noxion.config";
4
+
5
+ function createConfig(): NoxionConfig {
6
+ try {
7
+ return loadConfig(noxionConfigInput);
8
+ } catch {
9
+ return {
10
+ name: noxionConfigInput.name ?? "{{SITE_NAME}}",
11
+ domain: noxionConfigInput.domain ?? "{{DOMAIN}}",
12
+ author: noxionConfigInput.author ?? "{{AUTHOR}}",
13
+ description: noxionConfigInput.description ?? "{{SITE_DESCRIPTION}}",
14
+ language: noxionConfigInput.language ?? "en",
15
+ defaultTheme: noxionConfigInput.defaultTheme ?? "system",
16
+ defaultPageType: "docs",
17
+ revalidate: noxionConfigInput.revalidate ?? 3600,
18
+ plugins: noxionConfigInput.plugins,
19
+ collections: noxionConfigInput.collections,
20
+ };
21
+ }
22
+ }
23
+
24
+ export const siteConfig = createConfig();
@@ -0,0 +1,48 @@
1
+ import { createNotionClient, fetchCollection, fetchPage, downloadImages, mapImages } from "@noxion/core";
2
+ import type { NoxionPage, ExtendedRecordMap } from "@noxion/core";
3
+ import { join } from "node:path";
4
+ import { siteConfig } from "./config";
5
+
6
+ const notion = createNotionClient({
7
+ authToken: process.env.NOTION_TOKEN || undefined,
8
+ });
9
+
10
+ export async function getAllPages(): Promise<NoxionPage[]> {
11
+ const collections = siteConfig.collections ?? [];
12
+ if (collections.length === 0) return [];
13
+
14
+ try {
15
+ const results = await Promise.all(
16
+ collections.map((col) => fetchCollection(notion, col))
17
+ );
18
+ return results.flat();
19
+ } catch (error) {
20
+ console.error("Failed to fetch pages:", error);
21
+ return [];
22
+ }
23
+ }
24
+
25
+ export async function getPageBySlug(slug: string): Promise<NoxionPage | undefined> {
26
+ const pages = await getAllPages();
27
+ return pages.find((p) => p.slug === slug);
28
+ }
29
+
30
+ export async function getPageRecordMap(pageId: string): Promise<ExtendedRecordMap> {
31
+ const recordMap = await fetchPage(notion, pageId);
32
+
33
+ if (process.env.NODE_ENV === "production") {
34
+ try {
35
+ const outputDir = join(process.cwd(), "public");
36
+ const urlMap = await downloadImages(recordMap, outputDir, { concurrency: 5 });
37
+ const localUrlMap: Record<string, string> = {};
38
+ for (const [originalUrl, localPath] of Object.entries(urlMap)) {
39
+ localUrlMap[originalUrl] = `/images/${localPath.split("/images/").pop()}`;
40
+ }
41
+ return mapImages(recordMap, localUrlMap);
42
+ } catch (error) {
43
+ console.error("Image download failed, using original URLs:", error);
44
+ }
45
+ }
46
+
47
+ return recordMap;
48
+ }
@@ -0,0 +1,26 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ output: "standalone",
5
+ transpilePackages: [
6
+ "@noxion/core",
7
+ "@noxion/renderer",
8
+ "@noxion/adapter-nextjs",
9
+ "@noxion/notion-renderer",
10
+ "notion-client",
11
+ "notion-types",
12
+ "notion-utils",
13
+ ],
14
+ images: {
15
+ remotePatterns: [
16
+ { protocol: "https", hostname: "www.notion.so" },
17
+ { protocol: "https", hostname: "notion.so" },
18
+ { protocol: "https", hostname: "images.unsplash.com" },
19
+ { protocol: "https", hostname: "s3.us-west-2.amazonaws.com" },
20
+ { protocol: "https", hostname: "prod-files-secure.s3.us-west-2.amazonaws.com" },
21
+ ],
22
+ },
23
+ staticPageGenerationTimeout: 300,
24
+ };
25
+
26
+ export default nextConfig;
@@ -0,0 +1,23 @@
1
+ import { defineConfig } from "@noxion/core";
2
+
3
+ const config = defineConfig({
4
+ name: process.env.SITE_NAME ?? "{{SITE_NAME}}",
5
+ domain: process.env.SITE_DOMAIN ?? "{{DOMAIN}}",
6
+ author: process.env.SITE_AUTHOR ?? "{{AUTHOR}}",
7
+ description: process.env.SITE_DESCRIPTION ?? "{{SITE_DESCRIPTION}}",
8
+ language: "en",
9
+ defaultTheme: "system",
10
+ defaultPageType: "docs",
11
+ revalidate: 3600,
12
+ revalidateSecret: process.env.REVALIDATE_SECRET,
13
+ collections: [
14
+ {
15
+ name: "docs",
16
+ databaseId: process.env.NOTION_PAGE_ID!,
17
+ pageType: "docs",
18
+ pathPrefix: "/docs",
19
+ },
20
+ ],
21
+ });
22
+
23
+ export default config;
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start"
9
+ },
10
+ "dependencies": {
11
+ "@noxion/core": "^0.3.0",
12
+ "@noxion/renderer": "^0.3.0",
13
+ "@noxion/adapter-nextjs": "^0.3.0",
14
+ "next": "^16.1.6",
15
+ "react": "^19.1.0",
16
+ "react-dom": "^19.1.0",
17
+ "notion-client": "^7.8.2"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.0.0",
21
+ "@types/react": "^19.2.14",
22
+ "@types/react-dom": "^19.2.3",
23
+ "typescript": "^5.7.0"
24
+ }
25
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "plugins": [{ "name": "next" }],
15
+ "outDir": "./dist"
16
+ },
17
+ "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
18
+ "exclude": ["node_modules", ".next", "out", "dist"]
19
+ }
@@ -0,0 +1,10 @@
1
+ NOTION_PAGE_ID={{NOTION_PAGE_ID}}
2
+ DOCS_NOTION_ID={{DOCS_NOTION_ID}}
3
+ PORTFOLIO_NOTION_ID={{PORTFOLIO_NOTION_ID}}
4
+ NOTION_TOKEN=
5
+ NOTION_SPACE_ID=
6
+ SITE_NAME={{SITE_NAME}}
7
+ SITE_DOMAIN={{DOMAIN}}
8
+ SITE_AUTHOR={{AUTHOR}}
9
+ SITE_DESCRIPTION={{SITE_DESCRIPTION}}
10
+ REVALIDATE_SECRET=
@@ -0,0 +1,11 @@
1
+ NOTION_PAGE_ID={{NOTION_PAGE_ID}}
2
+ DOCS_NOTION_ID={{DOCS_NOTION_ID}}
3
+ PORTFOLIO_NOTION_ID={{PORTFOLIO_NOTION_ID}}
4
+ NOTION_TOKEN=
5
+ NOTION_SPACE_ID=
6
+ SITE_NAME={{SITE_NAME}}
7
+ SITE_DOMAIN={{DOMAIN}}
8
+ SITE_AUTHOR={{AUTHOR}}
9
+ SITE_DESCRIPTION={{SITE_DESCRIPTION}}
10
+ REVALIDATE_SECRET=
11
+ NOTION_WEBHOOK_SECRET=
@@ -0,0 +1,55 @@
1
+ import { notFound } from "next/navigation";
2
+ import type { Metadata } from "next";
3
+ import { NotionPage } from "@noxion/renderer";
4
+ import { generateNoxionMetadata, generateBlogPostingLD } from "@noxion/adapter-nextjs";
5
+ import { getPageBySlug, getPageRecordMap, getAllPages } from "../../lib/notion";
6
+ import { siteConfig } from "../../lib/config";
7
+
8
+ export const revalidate = 3600;
9
+
10
+ export async function generateStaticParams() {
11
+ const pages = await getAllPages();
12
+ return pages.map((p) => ({ slug: p.slug }));
13
+ }
14
+
15
+ export async function generateMetadata({
16
+ params,
17
+ }: {
18
+ params: Promise<{ slug: string }>;
19
+ }): Promise<Metadata> {
20
+ const { slug } = await params;
21
+ const page = await getPageBySlug(slug);
22
+ if (!page) return { title: "Not Found" };
23
+ return generateNoxionMetadata(page, siteConfig);
24
+ }
25
+
26
+ export default async function PageDetail({
27
+ params,
28
+ }: {
29
+ params: Promise<{ slug: string }>;
30
+ }) {
31
+ const { slug } = await params;
32
+ const page = await getPageBySlug(slug);
33
+ if (!page) notFound();
34
+
35
+ const recordMap = await getPageRecordMap(page.id);
36
+
37
+ return (
38
+ <article>
39
+ {page.pageType === "blog" && (
40
+ <script
41
+ type="application/ld+json"
42
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(generateBlogPostingLD(page, siteConfig)) }}
43
+ />
44
+ )}
45
+ <NotionPage
46
+ recordMap={recordMap}
47
+ rootPageId={page.id}
48
+ fullPage
49
+ previewImages
50
+ showTableOfContents={page.pageType !== "portfolio"}
51
+ mapPageUrl={(pageId: string) => `/${pageId}`}
52
+ />
53
+ </article>
54
+ );
55
+ }
@@ -0,0 +1,12 @@
1
+ import { revalidatePath } from "next/cache";
2
+ import { createNotionWebhookHandler } from "@noxion/adapter-nextjs";
3
+ import { siteConfig } from "../../../lib/config";
4
+
5
+ const handler = createNotionWebhookHandler({
6
+ config: siteConfig,
7
+ revalidatePath,
8
+ });
9
+
10
+ export async function POST(request: Request) {
11
+ return handler(request);
12
+ }
@@ -0,0 +1,12 @@
1
+ import { revalidatePath } from "next/cache";
2
+ import { createRevalidateHandler } from "@noxion/adapter-nextjs";
3
+ import { siteConfig } from "../../../lib/config";
4
+
5
+ const handler = createRevalidateHandler({
6
+ config: siteConfig,
7
+ revalidatePath,
8
+ });
9
+
10
+ export async function POST(request: Request) {
11
+ return handler(request as never);
12
+ }
@@ -0,0 +1,31 @@
1
+ @import "@noxion/notion-renderer/styles";
2
+
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ margin: 0;
8
+ padding: 0;
9
+ }
10
+
11
+ html {
12
+ font-family: var(--noxion-font-sans, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+ }
16
+
17
+ body {
18
+ background-color: var(--noxion-background, #fff);
19
+ color: var(--noxion-foreground, #0a0a0a);
20
+ min-height: 100vh;
21
+ }
22
+
23
+ [data-theme="dark"] body {
24
+ background-color: var(--noxion-background, #0a0a0a);
25
+ color: var(--noxion-foreground, #fafafa);
26
+ }
27
+
28
+ a {
29
+ color: inherit;
30
+ text-decoration: none;
31
+ }
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { PostList, TagFilter, Search, ThemeToggle } from "@noxion/theme-default";
4
+ import { useSearch } from "@noxion/renderer";
5
+ import type { PostCardProps } from "@noxion/renderer";
6
+ import { useState, useCallback } from "react";
7
+
8
+ interface HomeContentProps {
9
+ posts: PostCardProps[];
10
+ allTags: string[];
11
+ }
12
+
13
+ export function HomeContent({ posts, allTags }: HomeContentProps) {
14
+ const [selectedTags, setSelectedTags] = useState<string[]>([]);
15
+
16
+ const tagFiltered = selectedTags.length > 0
17
+ ? posts.filter((p) => p.tags.some((t) => selectedTags.includes(t)))
18
+ : posts;
19
+
20
+ const { setQuery, filtered } = useSearch(tagFiltered);
21
+
22
+ const handleToggleTag = useCallback((tag: string) => {
23
+ setSelectedTags((prev) =>
24
+ prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]
25
+ );
26
+ }, []);
27
+
28
+ return (
29
+ <div>
30
+ <div
31
+ style={{
32
+ display: "flex",
33
+ alignItems: "center",
34
+ justifyContent: "space-between",
35
+ marginBottom: "1.5rem",
36
+ }}
37
+ >
38
+ <h1 style={{ fontSize: "1.5rem", fontWeight: 700 }}>Posts</h1>
39
+ <ThemeToggle />
40
+ </div>
41
+
42
+ <div style={{ marginBottom: "1.5rem" }}>
43
+ <Search onSearch={setQuery} placeholder="Search posts..." />
44
+ </div>
45
+
46
+ {allTags.length > 0 && (
47
+ <div style={{ marginBottom: "2rem" }}>
48
+ <TagFilter
49
+ tags={allTags}
50
+ selectedTags={selectedTags}
51
+ onToggle={handleToggleTag}
52
+ />
53
+ </div>
54
+ )}
55
+
56
+ <PostList posts={filtered} />
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,36 @@
1
+ import type { Metadata } from "next";
2
+ import { generateNoxionListMetadata, generateWebSiteLD } from "@noxion/adapter-nextjs";
3
+ import { siteConfig } from "../lib/config";
4
+ import { ThemeScript } from "./theme-script";
5
+ import { SiteLayout } from "./site-layout";
6
+ import "./tailwind.css";
7
+ import "./globals.css";
8
+
9
+ export function generateMetadata(): Metadata {
10
+ return generateNoxionListMetadata(siteConfig);
11
+ }
12
+
13
+ export default function RootLayout({
14
+ children,
15
+ }: {
16
+ children: React.ReactNode;
17
+ }) {
18
+ const jsonLd = generateWebSiteLD(siteConfig);
19
+
20
+ return (
21
+ <html lang={siteConfig.language} suppressHydrationWarning>
22
+ <head>
23
+ <ThemeScript />
24
+ <script
25
+ type="application/ld+json"
26
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
27
+ />
28
+ </head>
29
+ <body>
30
+ <SiteLayout siteName={siteConfig.name} author={siteConfig.author}>
31
+ {children}
32
+ </SiteLayout>
33
+ </body>
34
+ </html>
35
+ );
36
+ }