@notionx/create-notionx-app 1.0.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.
- package/README.md +139 -0
- package/dist/answers.js +332 -0
- package/dist/answers.js.map +1 -0
- package/dist/cli-notionx.js +388 -0
- package/dist/cli-notionx.js.map +1 -0
- package/dist/cli-notionx.test.js +277 -0
- package/dist/cli-notionx.test.js.map +1 -0
- package/dist/diff.js +40 -0
- package/dist/diff.js.map +1 -0
- package/dist/diff.test.js +90 -0
- package/dist/diff.test.js.map +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/locale-add/apply.js +39 -0
- package/dist/locale-add/apply.js.map +1 -0
- package/dist/locale-add/format.js +38 -0
- package/dist/locale-add/format.js.map +1 -0
- package/dist/locale-add/list.js +44 -0
- package/dist/locale-add/list.js.map +1 -0
- package/dist/locale-add/list.test.js +45 -0
- package/dist/locale-add/list.test.js.map +1 -0
- package/dist/locale-add/plan.js +128 -0
- package/dist/locale-add/plan.js.map +1 -0
- package/dist/locale-add/validate.js +46 -0
- package/dist/locale-add/validate.js.map +1 -0
- package/dist/metadata.js +41 -0
- package/dist/metadata.js.map +1 -0
- package/dist/notion-translation-sources/apply.js +61 -0
- package/dist/notion-translation-sources/apply.js.map +1 -0
- package/dist/notion-translation-sources/index.js +3 -0
- package/dist/notion-translation-sources/index.js.map +1 -0
- package/dist/notion-translation-sources/plan.js +33 -0
- package/dist/notion-translation-sources/plan.js.map +1 -0
- package/dist/notionx-source.js +142 -0
- package/dist/notionx-source.js.map +1 -0
- package/dist/notionx-source.test.js +144 -0
- package/dist/notionx-source.test.js.map +1 -0
- package/dist/password.js +18 -0
- package/dist/password.js.map +1 -0
- package/dist/presets.js +83 -0
- package/dist/presets.js.map +1 -0
- package/dist/presets.test.js +50 -0
- package/dist/presets.test.js.map +1 -0
- package/dist/prompt.js +218 -0
- package/dist/prompt.js.map +1 -0
- package/dist/provision/cloudflare.js +236 -0
- package/dist/provision/cloudflare.js.map +1 -0
- package/dist/provision/dependencies.js +219 -0
- package/dist/provision/dependencies.js.map +1 -0
- package/dist/provision/index.js +681 -0
- package/dist/provision/index.js.map +1 -0
- package/dist/provision/index.test.js +54 -0
- package/dist/provision/index.test.js.map +1 -0
- package/dist/provision/inspect.js +109 -0
- package/dist/provision/inspect.js.map +1 -0
- package/dist/provision/inspect.test.js +75 -0
- package/dist/provision/inspect.test.js.map +1 -0
- package/dist/provision/notion.js +1981 -0
- package/dist/provision/notion.js.map +1 -0
- package/dist/provision/notion.test.js +542 -0
- package/dist/provision/notion.test.js.map +1 -0
- package/dist/provision/ntn-credentials.js +198 -0
- package/dist/provision/ntn-credentials.js.map +1 -0
- package/dist/provision/options.js +15 -0
- package/dist/provision/options.js.map +1 -0
- package/dist/provision/password-hash.js +78 -0
- package/dist/provision/password-hash.js.map +1 -0
- package/dist/provision/prompts.js +115 -0
- package/dist/provision/prompts.js.map +1 -0
- package/dist/provision/repair.js +48 -0
- package/dist/provision/repair.js.map +1 -0
- package/dist/provision/repair.test.js +141 -0
- package/dist/provision/repair.test.js.map +1 -0
- package/dist/provision/shell.js +84 -0
- package/dist/provision/shell.js.map +1 -0
- package/dist/provision/wire.js +78 -0
- package/dist/provision/wire.js.map +1 -0
- package/dist/registry/doctor.js +181 -0
- package/dist/registry/doctor.js.map +1 -0
- package/dist/registry/doctor.test.js +180 -0
- package/dist/registry/doctor.test.js.map +1 -0
- package/dist/registry/install.js +217 -0
- package/dist/registry/install.js.map +1 -0
- package/dist/registry/install.test.js +168 -0
- package/dist/registry/install.test.js.map +1 -0
- package/dist/registry/load-registry.js +24 -0
- package/dist/registry/load-registry.js.map +1 -0
- package/dist/registry/load-registry.test.js +59 -0
- package/dist/registry/load-registry.test.js.map +1 -0
- package/dist/registry/migration-planner.js +204 -0
- package/dist/registry/migration-planner.js.map +1 -0
- package/dist/registry/migration-planner.test.js +340 -0
- package/dist/registry/migration-planner.test.js.map +1 -0
- package/dist/registry/migrations-store.js +125 -0
- package/dist/registry/migrations-store.js.map +1 -0
- package/dist/registry/migrations-store.test.js +163 -0
- package/dist/registry/migrations-store.test.js.map +1 -0
- package/dist/registry/migrations-types.js +25 -0
- package/dist/registry/migrations-types.js.map +1 -0
- package/dist/registry/project-meta.js +84 -0
- package/dist/registry/project-meta.js.map +1 -0
- package/dist/registry/registry-items.js +354 -0
- package/dist/registry/registry-items.js.map +1 -0
- package/dist/registry/registry-items.test.js +99 -0
- package/dist/registry/registry-items.test.js.map +1 -0
- package/dist/registry/registry-store.js +232 -0
- package/dist/registry/registry-store.js.map +1 -0
- package/dist/registry/registry-store.test.js +136 -0
- package/dist/registry/registry-store.test.js.map +1 -0
- package/dist/registry/registry-types.js +18 -0
- package/dist/registry/registry-types.js.map +1 -0
- package/dist/registry/registry-types.test.js +146 -0
- package/dist/registry/registry-types.test.js.map +1 -0
- package/dist/registry/render-content-source-files.js +158 -0
- package/dist/registry/render-content-source-files.js.map +1 -0
- package/dist/registry/render-multi-source.js +296 -0
- package/dist/registry/render-multi-source.js.map +1 -0
- package/dist/registry/render-multi-source.test.js +110 -0
- package/dist/registry/render-multi-source.test.js.map +1 -0
- package/dist/registry/text-utils.js +42 -0
- package/dist/registry/text-utils.js.map +1 -0
- package/dist/registry/uninstall.js +250 -0
- package/dist/registry/uninstall.js.map +1 -0
- package/dist/registry/uninstall.test.js +264 -0
- package/dist/registry/uninstall.test.js.map +1 -0
- package/dist/registry/update.js +280 -0
- package/dist/registry/update.js.map +1 -0
- package/dist/registry/update.test.js +229 -0
- package/dist/registry/update.test.js.map +1 -0
- package/dist/render.js +549 -0
- package/dist/render.js.map +1 -0
- package/dist/render.test.js +414 -0
- package/dist/render.test.js.map +1 -0
- package/dist/templates/.dev.vars.example.tmpl +32 -0
- package/dist/templates/.gitignore.tmpl +58 -0
- package/dist/templates/README.md.tmpl +417 -0
- package/dist/templates/app/[slug]/page.tsx.tmpl +55 -0
- package/dist/templates/app/admin/account/page.tsx.tmpl +18 -0
- package/dist/templates/app/admin/content-models/page.tsx.tmpl +6 -0
- package/dist/templates/app/admin/layout.tsx.tmpl +90 -0
- package/dist/templates/app/admin/loading.tsx.tmpl +6 -0
- package/dist/templates/app/admin/page.tsx.tmpl +17 -0
- package/dist/templates/app/api/auth/google/callback/route.ts.tmpl +3 -0
- package/dist/templates/app/api/auth/google/route.ts.tmpl +3 -0
- package/dist/templates/app/api/auth/verify-email/route.ts.tmpl +3 -0
- package/dist/templates/app/api/auth/viewer/route.ts.tmpl +3 -0
- package/dist/templates/app/api/health/route.ts.tmpl +3 -0
- package/dist/templates/app/api/{{contentSourceId}}/[slug]/route.ts.tmpl +27 -0
- package/dist/templates/app/api/{{contentSourceId}}/route.ts.tmpl +18 -0
- package/dist/templates/app/globals.css.tmpl +109 -0
- package/dist/templates/app/layout.tsx.tmpl +56 -0
- package/dist/templates/app/login/page.tsx.tmpl +154 -0
- package/dist/templates/app/page.fallback.tsx.tmpl +31 -0
- package/dist/templates/app/page.tsx.tmpl +42 -0
- package/dist/templates/app/register/page.tsx.tmpl +138 -0
- package/dist/templates/app/{{contentSourceListPath}}/[slug]/page.tsx.tmpl +113 -0
- package/dist/templates/app/{{contentSourceListPath}}/page.tsx.tmpl +74 -0
- package/dist/templates/components/content/post-card.tsx.tmpl +80 -0
- package/dist/templates/components/notion-blocks.tsx.tmpl +668 -0
- package/dist/templates/components/page-blocks/feature-grid-block.tsx.tmpl +68 -0
- package/dist/templates/components/page-blocks/hero-block.tsx.tmpl +73 -0
- package/dist/templates/components/page-blocks/latest-posts-block.tsx.tmpl +59 -0
- package/dist/templates/components/page-blocks/story-block.tsx.tmpl +70 -0
- package/dist/templates/components/page-blocks.fallback.tsx.tmpl +17 -0
- package/dist/templates/components/page-blocks.tsx.tmpl +32 -0
- package/dist/templates/components/search/search-dialog.tsx.tmpl +171 -0
- package/dist/templates/components/site/locale-switcher.tsx.tmpl +65 -0
- package/dist/templates/components/site/site-footer.tsx.tmpl +106 -0
- package/dist/templates/components/site/site-header.tsx.tmpl +80 -0
- package/dist/templates/components/site/site-shell.tsx.tmpl +20 -0
- package/dist/templates/components/site/theme-bootstrap.tsx.tmpl +51 -0
- package/dist/templates/components/theme-provider.tsx.tmpl +14 -0
- package/dist/templates/components/theme-toggle.tsx.tmpl +38 -0
- package/dist/templates/components/ui/accordion.tsx.tmpl +56 -0
- package/dist/templates/components/ui/alert.tsx.tmpl +59 -0
- package/dist/templates/components/ui/aspect-ratio.tsx.tmpl +8 -0
- package/dist/templates/components/ui/avatar.tsx.tmpl +44 -0
- package/dist/templates/components/ui/badge.tsx.tmpl +33 -0
- package/dist/templates/components/ui/button.tsx.tmpl +56 -0
- package/dist/templates/components/ui/card.tsx.tmpl +61 -0
- package/dist/templates/components/ui/checkbox.tsx.tmpl +28 -0
- package/dist/templates/components/ui/dialog.tsx.tmpl +104 -0
- package/dist/templates/components/ui/dropdown-menu.tsx.tmpl +183 -0
- package/dist/templates/components/ui/input.tsx.tmpl +21 -0
- package/dist/templates/components/ui/label.tsx.tmpl +25 -0
- package/dist/templates/components/ui/popover.tsx.tmpl +30 -0
- package/dist/templates/components/ui/radio-group.tsx.tmpl +44 -0
- package/dist/templates/components/ui/select.tsx.tmpl +150 -0
- package/dist/templates/components/ui/separator.tsx.tmpl +30 -0
- package/dist/templates/components/ui/sheet.tsx.tmpl +125 -0
- package/dist/templates/components/ui/skeleton.tsx.tmpl +15 -0
- package/dist/templates/components/ui/sonner.tsx.tmpl +30 -0
- package/dist/templates/components/ui/switch.tsx.tmpl +29 -0
- package/dist/templates/components/ui/table.tsx.tmpl +107 -0
- package/dist/templates/components/ui/tabs.tsx.tmpl +55 -0
- package/dist/templates/components/ui/textarea.tsx.tmpl +24 -0
- package/dist/templates/components/ui/tooltip.tsx.tmpl +30 -0
- package/dist/templates/components.json.tmpl +21 -0
- package/dist/templates/env.d.ts.tmpl +32 -0
- package/dist/templates/lib/admin/actions.ts.tmpl +43 -0
- package/dist/templates/lib/admin/context.tsx.tmpl +209 -0
- package/dist/templates/lib/admin/nav.ts.tmpl +23 -0
- package/dist/templates/lib/auth.config.fallback.ts.tmpl +10 -0
- package/dist/templates/lib/auth.config.ts.tmpl +45 -0
- package/dist/templates/lib/blocks/translations.ts.tmpl +44 -0
- package/dist/templates/lib/blog/translations.ts.tmpl +52 -0
- package/dist/templates/lib/content/models.ts.tmpl +53 -0
- package/dist/templates/lib/i18n/config.ts.tmpl +18 -0
- package/dist/templates/lib/i18n/index.ts.tmpl +1 -0
- package/dist/templates/lib/locale-contract/built-in.ts.tmpl +19 -0
- package/dist/templates/lib/locale-contract/index.ts.tmpl +3 -0
- package/dist/templates/lib/locale-contract/paths.ts.tmpl +29 -0
- package/dist/templates/lib/pages/model.ts.tmpl +16 -0
- package/dist/templates/lib/pages/source.ts.tmpl +566 -0
- package/dist/templates/lib/pages/translations.ts.tmpl +34 -0
- package/dist/templates/lib/search/config.fallback.ts.tmpl +11 -0
- package/dist/templates/lib/search/config.ts.tmpl +25 -0
- package/dist/templates/lib/site/config.ts.tmpl +120 -0
- package/dist/templates/lib/site/request-env.ts.tmpl +71 -0
- package/dist/templates/lib/site/settings.fallback.ts.tmpl +21 -0
- package/dist/templates/lib/site/settings.ts.tmpl +320 -0
- package/dist/templates/lib/site/translations.ts.tmpl +30 -0
- package/dist/templates/lib/utils.ts.tmpl +9 -0
- package/dist/templates/migrations/0001_init.sql.tmpl +57 -0
- package/dist/templates/migrations/0002_admin_seed.sql.tmpl +30 -0
- package/dist/templates/migrations/0003_search_index.sql.tmpl +29 -0
- package/dist/templates/next.config.ts.tmpl +18 -0
- package/dist/templates/package.json.tmpl +40 -0
- package/dist/templates/shims/cloudflare-workers-empty.mjs +4 -0
- package/dist/templates/shims/next-headers-empty.mjs +4 -0
- package/dist/templates/tests/smoke.test.ts.tmpl +83 -0
- package/dist/templates/tsconfig.json.tmpl +31 -0
- package/dist/templates/vite.config.ts.tmpl +53 -0
- package/dist/templates/vitest.config.ts.tmpl +13 -0
- package/dist/templates/worker/index.ts.tmpl +52 -0
- package/dist/templates/wrangler.jsonc.tmpl +44 -0
- package/dist/ui-presets.js +60 -0
- package/dist/ui-presets.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
const Table = React.forwardRef<
|
|
5
|
+
HTMLTableElement,
|
|
6
|
+
React.HTMLAttributes<HTMLTableElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div className="relative w-full overflow-auto">
|
|
9
|
+
<table
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
));
|
|
16
|
+
Table.displayName = "Table";
|
|
17
|
+
|
|
18
|
+
const TableHeader = React.forwardRef<
|
|
19
|
+
HTMLTableSectionElement,
|
|
20
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
21
|
+
>(({ className, ...props }, ref) => (
|
|
22
|
+
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
|
23
|
+
));
|
|
24
|
+
TableHeader.displayName = "TableHeader";
|
|
25
|
+
|
|
26
|
+
const TableBody = React.forwardRef<
|
|
27
|
+
HTMLTableSectionElement,
|
|
28
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
29
|
+
>(({ className, ...props }, ref) => (
|
|
30
|
+
<tbody
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
));
|
|
36
|
+
TableBody.displayName = "TableBody";
|
|
37
|
+
|
|
38
|
+
const TableFooter = React.forwardRef<
|
|
39
|
+
HTMLTableSectionElement,
|
|
40
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
41
|
+
>(({ className, ...props }, ref) => (
|
|
42
|
+
<tfoot
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
));
|
|
48
|
+
TableFooter.displayName = "TableFooter";
|
|
49
|
+
|
|
50
|
+
const TableRow = React.forwardRef<
|
|
51
|
+
HTMLTableRowElement,
|
|
52
|
+
React.HTMLAttributes<HTMLTableRowElement>
|
|
53
|
+
>(({ className, ...props }, ref) => (
|
|
54
|
+
<tr
|
|
55
|
+
ref={ref}
|
|
56
|
+
className={cn("border-b transition-colors hover:bg-muted/50", className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
));
|
|
60
|
+
TableRow.displayName = "TableRow";
|
|
61
|
+
|
|
62
|
+
const TableHead = React.forwardRef<
|
|
63
|
+
HTMLTableCellElement,
|
|
64
|
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
65
|
+
>(({ className, ...props }, ref) => (
|
|
66
|
+
<th
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn(
|
|
69
|
+
"h-12 px-4 text-left align-middle font-medium text-muted-foreground",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
));
|
|
75
|
+
TableHead.displayName = "TableHead";
|
|
76
|
+
|
|
77
|
+
const TableCell = React.forwardRef<
|
|
78
|
+
HTMLTableCellElement,
|
|
79
|
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
80
|
+
>(({ className, ...props }, ref) => (
|
|
81
|
+
<td ref={ref} className={cn("p-4 align-middle", className)} {...props} />
|
|
82
|
+
));
|
|
83
|
+
TableCell.displayName = "TableCell";
|
|
84
|
+
|
|
85
|
+
const TableCaption = React.forwardRef<
|
|
86
|
+
HTMLTableCaptionElement,
|
|
87
|
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
88
|
+
>(({ className, ...props }, ref) => (
|
|
89
|
+
<caption
|
|
90
|
+
ref={ref}
|
|
91
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
));
|
|
95
|
+
TableCaption.displayName = "TableCaption";
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
Table,
|
|
99
|
+
TableHeader,
|
|
100
|
+
TableBody,
|
|
101
|
+
TableFooter,
|
|
102
|
+
TableHead,
|
|
103
|
+
TableRow,
|
|
104
|
+
TableCell,
|
|
105
|
+
TableCaption,
|
|
106
|
+
};
|
|
107
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
const Tabs = TabsPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const TabsList = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<TabsPrimitive.List
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
));
|
|
22
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
23
|
+
|
|
24
|
+
const TabsTrigger = React.forwardRef<
|
|
25
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
26
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
27
|
+
>(({ className, ...props }, ref) => (
|
|
28
|
+
<TabsPrimitive.Trigger
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cn(
|
|
31
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
));
|
|
37
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
38
|
+
|
|
39
|
+
const TabsContent = React.forwardRef<
|
|
40
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
41
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
42
|
+
>(({ className, ...props }, ref) => (
|
|
43
|
+
<TabsPrimitive.Content
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn(
|
|
46
|
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
));
|
|
52
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
53
|
+
|
|
54
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
55
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface TextareaProps
|
|
5
|
+
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
|
6
|
+
|
|
7
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
8
|
+
({ className, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
className={cn(
|
|
12
|
+
"flex min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
ref={ref}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
Textarea.displayName = "Textarea";
|
|
22
|
+
|
|
23
|
+
export { Textarea };
|
|
24
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
8
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
9
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
10
|
+
|
|
11
|
+
const TooltipContent = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
14
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
15
|
+
<TooltipPrimitive.Portal>
|
|
16
|
+
<TooltipPrimitive.Content
|
|
17
|
+
ref={ref}
|
|
18
|
+
sideOffset={sideOffset}
|
|
19
|
+
className={cn(
|
|
20
|
+
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
</TooltipPrimitive.Portal>
|
|
26
|
+
));
|
|
27
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
28
|
+
|
|
29
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
30
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
|
|
3
|
+
// Bindings declared in `wrangler.jsonc`. Add more entries here as
|
|
4
|
+
// you wire up new bindings (KV, R2, queues, durable objects, etc.).
|
|
5
|
+
|
|
6
|
+
interface Env {
|
|
7
|
+
ASSETS: Fetcher;
|
|
8
|
+
IMAGES: ImagesBinding;
|
|
9
|
+
DB: D1Database;
|
|
10
|
+
CONTENT_CACHE: KVNamespace;
|
|
11
|
+
VINEXT_KV_CACHE: KVNamespace;
|
|
12
|
+
ASSETS_BUCKET: R2Bucket;
|
|
13
|
+
NOTION_TOKEN?: string;
|
|
14
|
+
NOTION_DATA_SOURCE_ID?: string;
|
|
15
|
+
NOTION_PAGES_DATA_SOURCE_ID?: string;
|
|
16
|
+
// Read by `lib/site/settings.ts`. The scaffolder writes the
|
|
17
|
+
// real id into wrangler.jsonc#vars. Leave the empty string
|
|
18
|
+
// here as a sentinel for "Notion not configured" — the loader
|
|
19
|
+
// will fall back to the static values in `lib/site/config.ts`.
|
|
20
|
+
NOTION_SITE_SETTINGS_DATA_SOURCE_ID?: string;
|
|
21
|
+
SITE_URL?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ImagesBinding {
|
|
25
|
+
input(stream: ReadableStream): {
|
|
26
|
+
transform(options: Record<string, unknown>): {
|
|
27
|
+
output(options: { format: string; quality: number }): Promise<{
|
|
28
|
+
response(): Response;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { redirect } from "next/navigation";
|
|
4
|
+
import {
|
|
5
|
+
changeUserPassword,
|
|
6
|
+
getCurrentUser,
|
|
7
|
+
setUserSessionCookie,
|
|
8
|
+
userToSession,
|
|
9
|
+
validatePasswordStrength,
|
|
10
|
+
} from "@notionx/core/auth";
|
|
11
|
+
|
|
12
|
+
export async function changePasswordAction(formData: FormData): Promise<void> {
|
|
13
|
+
const user = await getCurrentUser();
|
|
14
|
+
if (!user) redirect("/login");
|
|
15
|
+
|
|
16
|
+
const currentPassword = String(formData.get("currentPassword") ?? "");
|
|
17
|
+
const newPassword = String(formData.get("newPassword") ?? "");
|
|
18
|
+
const confirmPassword = String(formData.get("confirmPassword") ?? "");
|
|
19
|
+
|
|
20
|
+
const passwordError = validatePasswordStrength(newPassword);
|
|
21
|
+
if (passwordError) {
|
|
22
|
+
redirect(`/admin/account?error=${encodeURIComponent(passwordError)}`);
|
|
23
|
+
}
|
|
24
|
+
if (newPassword !== confirmPassword) {
|
|
25
|
+
redirect("/admin/account?error=两次输入的新密码不一致");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await changeUserPassword({
|
|
29
|
+
userId: user.uid,
|
|
30
|
+
currentPassword,
|
|
31
|
+
newPassword,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!result.ok) {
|
|
35
|
+
if (result.reason === "no_password") {
|
|
36
|
+
redirect("/admin/account?error=当前账号未设置密码");
|
|
37
|
+
}
|
|
38
|
+
redirect("/admin/account?error=当前密码不正确");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await setUserSessionCookie(userToSession(result.user));
|
|
42
|
+
redirect("/admin/account?saved=1");
|
|
43
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Badge } from "@/components/ui/badge";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from "@/components/ui/card";
|
|
11
|
+
import { Input } from "@/components/ui/input";
|
|
12
|
+
import { Label } from "@/components/ui/label";
|
|
13
|
+
import { Separator } from "@/components/ui/separator";
|
|
14
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
15
|
+
import {
|
|
16
|
+
contentSources,
|
|
17
|
+
managedContentSources,
|
|
18
|
+
{{contentSourceVarName}},
|
|
19
|
+
} from "@/lib/content/models";
|
|
20
|
+
import { siteConfig } from "@/lib/site/config";
|
|
21
|
+
import { changePasswordAction } from "./actions";
|
|
22
|
+
import {
|
|
23
|
+
getAuthViewer,
|
|
24
|
+
getCurrentUser,
|
|
25
|
+
getUserById,
|
|
26
|
+
} from "@notionx/core/auth";
|
|
27
|
+
import { getContentModelAdminSummaries } from "@notionx/core/content";
|
|
28
|
+
import {
|
|
29
|
+
getNotionEditBaseUrl,
|
|
30
|
+
listGenericNotionContent,
|
|
31
|
+
} from "@notionx/core/notion";
|
|
32
|
+
import type { AdminPageContext } from "@notionx/core/admin/pages";
|
|
33
|
+
import { getAppSettings, isAdminEmail } from "@notionx/core/internal-admin";
|
|
34
|
+
import { getSiteUrl, workerEnv } from "@notionx/core/util";
|
|
35
|
+
import { cn } from "@/lib/utils";
|
|
36
|
+
|
|
37
|
+
function Table({ children }: { children: ReactNode }) {
|
|
38
|
+
return (
|
|
39
|
+
<div className="w-full overflow-auto">
|
|
40
|
+
<table className="w-full caption-bottom text-sm">{children}</table>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function TableHeader({ children }: { children: ReactNode }) {
|
|
46
|
+
return <thead className="[&_tr]:border-b">{children}</thead>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function TableBody({ children }: { children: ReactNode }) {
|
|
50
|
+
return <tbody className="[&_tr:last-child]:border-0">{children}</tbody>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function TableRow({ children }: { children: ReactNode }) {
|
|
54
|
+
return (
|
|
55
|
+
<tr className="border-b transition-colors hover:bg-muted/50">
|
|
56
|
+
{children}
|
|
57
|
+
</tr>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function TableHead({
|
|
62
|
+
className,
|
|
63
|
+
children,
|
|
64
|
+
}: {
|
|
65
|
+
className?: string;
|
|
66
|
+
children?: ReactNode;
|
|
67
|
+
}) {
|
|
68
|
+
return (
|
|
69
|
+
<th
|
|
70
|
+
className={cn(
|
|
71
|
+
"h-10 px-4 text-left align-middle font-medium text-muted-foreground",
|
|
72
|
+
className
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
{children}
|
|
76
|
+
</th>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function TableCell({
|
|
81
|
+
className,
|
|
82
|
+
children,
|
|
83
|
+
colSpan,
|
|
84
|
+
}: {
|
|
85
|
+
className?: string;
|
|
86
|
+
children?: ReactNode;
|
|
87
|
+
colSpan?: number;
|
|
88
|
+
}) {
|
|
89
|
+
return (
|
|
90
|
+
<td className={cn("p-4 align-middle", className)} colSpan={colSpan}>
|
|
91
|
+
{children}
|
|
92
|
+
</td>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function Alert({
|
|
97
|
+
variant,
|
|
98
|
+
className,
|
|
99
|
+
children,
|
|
100
|
+
}: {
|
|
101
|
+
variant?: "default" | "destructive";
|
|
102
|
+
className?: string;
|
|
103
|
+
children: ReactNode;
|
|
104
|
+
}) {
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
className={cn(
|
|
108
|
+
"rounded-md border px-4 py-3 text-sm",
|
|
109
|
+
variant === "destructive"
|
|
110
|
+
? "border-destructive/50 text-destructive"
|
|
111
|
+
: "border-border",
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
role="alert"
|
|
115
|
+
>
|
|
116
|
+
{children}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function AlertTitle({ children }: { children: ReactNode }) {
|
|
122
|
+
return <div className="mb-1 font-medium leading-none">{children}</div>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function AlertDescription({ children }: { children: ReactNode }) {
|
|
126
|
+
return <div className="text-sm opacity-90">{children}</div>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function Passthrough({ children }: { children: ReactNode }) {
|
|
130
|
+
return <>{children}</>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function getNotionPostsMeta() {
|
|
134
|
+
const items = await listGenericNotionContent({{contentSourceVarName}});
|
|
135
|
+
return items.map((item) => ({
|
|
136
|
+
slug: item.slug,
|
|
137
|
+
title: item.title,
|
|
138
|
+
author: "Notion",
|
|
139
|
+
date: item.date,
|
|
140
|
+
tags: item.tags,
|
|
141
|
+
editUrl: item.editUrl,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function getAdminViewer() {
|
|
146
|
+
const viewer = await getAuthViewer();
|
|
147
|
+
if (!viewer) return null;
|
|
148
|
+
return {
|
|
149
|
+
viewer: { email: viewer.email },
|
|
150
|
+
viewerEmail: viewer.email,
|
|
151
|
+
admin: viewer.isAdmin,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function buildAdminPageContext(): AdminPageContext {
|
|
156
|
+
return {
|
|
157
|
+
ui: {
|
|
158
|
+
Button: Button as AdminPageContext["ui"]["Button"],
|
|
159
|
+
Input: Input as AdminPageContext["ui"]["Input"],
|
|
160
|
+
Label: Label as AdminPageContext["ui"]["Label"],
|
|
161
|
+
Card: Card as AdminPageContext["ui"]["Card"],
|
|
162
|
+
CardHeader: CardHeader as AdminPageContext["ui"]["CardHeader"],
|
|
163
|
+
CardTitle: CardTitle as AdminPageContext["ui"]["CardTitle"],
|
|
164
|
+
CardDescription:
|
|
165
|
+
CardDescription as AdminPageContext["ui"]["CardDescription"],
|
|
166
|
+
CardContent: CardContent as AdminPageContext["ui"]["CardContent"],
|
|
167
|
+
Badge: Badge as AdminPageContext["ui"]["Badge"],
|
|
168
|
+
Table,
|
|
169
|
+
TableHeader,
|
|
170
|
+
TableBody,
|
|
171
|
+
TableRow,
|
|
172
|
+
TableHead,
|
|
173
|
+
TableCell,
|
|
174
|
+
Alert,
|
|
175
|
+
AlertTitle,
|
|
176
|
+
AlertDescription,
|
|
177
|
+
Separator: Separator as AdminPageContext["ui"]["Separator"],
|
|
178
|
+
Skeleton: Skeleton as AdminPageContext["ui"]["Skeleton"],
|
|
179
|
+
AlertDialog: Passthrough,
|
|
180
|
+
AlertDialogTrigger: Passthrough,
|
|
181
|
+
AlertDialogContent: Passthrough,
|
|
182
|
+
AlertDialogHeader: Passthrough,
|
|
183
|
+
AlertDialogTitle: Passthrough,
|
|
184
|
+
AlertDialogDescription: Passthrough,
|
|
185
|
+
AlertDialogFooter: Passthrough,
|
|
186
|
+
AlertDialogAction: Passthrough,
|
|
187
|
+
AlertDialogCancel: Passthrough,
|
|
188
|
+
},
|
|
189
|
+
actions: {
|
|
190
|
+
changePassword: changePasswordAction,
|
|
191
|
+
},
|
|
192
|
+
data: {
|
|
193
|
+
getNotionPostsMeta,
|
|
194
|
+
getNotionEditBaseUrl,
|
|
195
|
+
getAdminViewer,
|
|
196
|
+
isAdminEmail,
|
|
197
|
+
getCurrentUser,
|
|
198
|
+
getAppSettings,
|
|
199
|
+
getSiteUrl,
|
|
200
|
+
workerEnv: { TURNSTILE_SECRET_KEY: workerEnv.TURNSTILE_SECRET_KEY },
|
|
201
|
+
getUserById,
|
|
202
|
+
getContentModelAdminSummaries: () =>
|
|
203
|
+
getContentModelAdminSummaries(managedContentSources),
|
|
204
|
+
},
|
|
205
|
+
extra: {
|
|
206
|
+
siteName: siteConfig.name,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Sidebar navigation for `/admin/*`. The package's `createAdminNav`
|
|
2
|
+
// factory sorts items by `order` and filters out any that require a
|
|
3
|
+
// role the viewer does not have.
|
|
4
|
+
|
|
5
|
+
import { createAdminNav } from "@notionx/core/admin";
|
|
6
|
+
|
|
7
|
+
export const adminNav = createAdminNav([
|
|
8
|
+
{ href: "/admin", labelKey: "admin.nav.dashboard", icon: "Home", order: 10 },
|
|
9
|
+
{
|
|
10
|
+
href: "/admin/content-models",
|
|
11
|
+
labelKey: "admin.nav.models",
|
|
12
|
+
icon: "Database",
|
|
13
|
+
order: 20,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
href: "/admin/account",
|
|
17
|
+
labelKey: "admin.nav.account",
|
|
18
|
+
icon: "User",
|
|
19
|
+
order: 60,
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export default adminNav;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Auth is disabled in this project (scaffolded with `--no-auth` or
|
|
2
|
+
// `notionx remove auth`). This stub exports `undefined` so the
|
|
3
|
+
// worker entry point can pass it to `createNotionxWorker`, which
|
|
4
|
+
// skips session cookie reading and the admin gate entirely.
|
|
5
|
+
|
|
6
|
+
import type { AuthConfig } from "@notionx/core/types";
|
|
7
|
+
|
|
8
|
+
export const authConfig: AuthConfig | undefined = undefined;
|
|
9
|
+
|
|
10
|
+
export default authConfig;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Auth configuration consumed by `createAuth` and the auth helpers
|
|
2
|
+
// from `@notionx/core`. The package uses the `databaseBinding`
|
|
3
|
+
// name to look up the D1 binding on the platform runtime, the
|
|
4
|
+
// `tables` map to know which tables back users, sessions, password
|
|
5
|
+
// resets, email verifications, and rate-limit buckets, and the
|
|
6
|
+
// `sessionCookie` / `roles` / `turnstile` / `email` / `oauth` blocks
|
|
7
|
+
// to bind the corresponding cookie names, role names, captcha
|
|
8
|
+
// settings, email provider, and OAuth client to the auth helpers.
|
|
9
|
+
|
|
10
|
+
import type { AuthConfig } from "@notionx/core/types";
|
|
11
|
+
|
|
12
|
+
export const authConfig: AuthConfig = {
|
|
13
|
+
databaseBinding: "DB",
|
|
14
|
+
tables: {
|
|
15
|
+
users: "users",
|
|
16
|
+
sessions: "sessions",
|
|
17
|
+
passwordResets: "password_resets",
|
|
18
|
+
emailVerifications: "email_verifications",
|
|
19
|
+
authRateLimits: "auth_rate_limits",
|
|
20
|
+
},
|
|
21
|
+
sessionCookie: {
|
|
22
|
+
name: "vinext_session",
|
|
23
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
24
|
+
secure: true,
|
|
25
|
+
},
|
|
26
|
+
turnstile: {
|
|
27
|
+
siteKeyEnv: "TURNSTILE_SITE_KEY",
|
|
28
|
+
secretKeyEnv: "TURNSTILE_SECRET_KEY",
|
|
29
|
+
},
|
|
30
|
+
email: {
|
|
31
|
+
provider: "resend",
|
|
32
|
+
fromEnv: "RESEND_FROM",
|
|
33
|
+
apiKeyEnv: "RESEND_API_KEY",
|
|
34
|
+
},
|
|
35
|
+
oauth: {
|
|
36
|
+
google: {
|
|
37
|
+
clientIdEnv: "GOOGLE_CLIENT_ID",
|
|
38
|
+
clientSecretEnv: "GOOGLE_CLIENT_SECRET",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
roles: { default: "user", vip: "vip", admin: "admin" },
|
|
42
|
+
password: { minLength: 8 },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default authConfig;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Locale-aware block lookup. Uses the default-locale fallback rule:
|
|
2
|
+
// a block without a translation in the target locale resolves to the
|
|
3
|
+
// default-locale copy so page shells stay usable.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
pickTranslation,
|
|
7
|
+
pickTranslationOrDefault,
|
|
8
|
+
} from "@notionx/core";
|
|
9
|
+
import { i18n } from "@/lib/i18n";
|
|
10
|
+
import { blocksContract } from "@/lib/locale-contract";
|
|
11
|
+
|
|
12
|
+
export type BlockTranslation = {
|
|
13
|
+
pageId: string;
|
|
14
|
+
sourcePageId: string;
|
|
15
|
+
locale: string;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
eyebrow: string;
|
|
19
|
+
headline: string;
|
|
20
|
+
subheadline: string;
|
|
21
|
+
body: string;
|
|
22
|
+
quote: string;
|
|
23
|
+
quoteAttribution: string;
|
|
24
|
+
primaryCtaLabel: string;
|
|
25
|
+
primaryCtaHref: string;
|
|
26
|
+
secondaryCtaLabel: string;
|
|
27
|
+
secondaryCtaHref: string;
|
|
28
|
+
published: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function pickBlockTranslation(
|
|
32
|
+
rows: readonly BlockTranslation[],
|
|
33
|
+
locale: string
|
|
34
|
+
) {
|
|
35
|
+
return (
|
|
36
|
+
pickTranslation(rows, locale, blocksContract, i18n.defaultLocale) ??
|
|
37
|
+
pickTranslationOrDefault(
|
|
38
|
+
rows,
|
|
39
|
+
locale,
|
|
40
|
+
i18n.defaultLocale,
|
|
41
|
+
blocksContract
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
}
|