create-kide-app 0.0.36 → 0.1.2

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 (50) hide show
  1. package/README.md +12 -11
  2. package/index.js +95 -420
  3. package/package.json +2 -3
  4. package/templates/base/.env.example +0 -2
  5. package/templates/base/CLAUDE.md +0 -73
  6. package/templates/base/gitignore +0 -8
  7. package/templates/base/package.json +0 -39
  8. package/templates/base/src/cms/adapters/db.ts +0 -49
  9. package/templates/base/src/cms/adapters/email.ts +0 -34
  10. package/templates/base/src/cms/adapters/storage.ts +0 -38
  11. package/templates/base/src/cms/cms.config.ts +0 -7
  12. package/templates/base/src/cms/collections/users.ts +0 -20
  13. package/templates/base/src/cms/internals/create-admin.ts +0 -40
  14. package/templates/base/src/cms/internals/generator.ts +0 -10
  15. package/templates/base/src/cms/internals/runtime.ts +0 -76
  16. package/templates/base/src/components/BlockRenderer.astro +0 -28
  17. package/templates/base/src/env.d.ts +0 -12
  18. package/templates/base/src/pages/index.astro +0 -11
  19. package/templates/base/src/styles/admin.css +0 -253
  20. package/templates/base/src/styles/public.css +0 -2
  21. package/templates/base/tsconfig.json +0 -11
  22. package/templates/cloudflare/astro.config.mjs +0 -16
  23. package/templates/cloudflare/db.ts +0 -23
  24. package/templates/cloudflare/drizzle.config.ts +0 -24
  25. package/templates/cloudflare/storage.ts +0 -25
  26. package/templates/cloudflare/uploads-route.ts +0 -36
  27. package/templates/cloudflare/wrangler.toml +0 -17
  28. package/templates/demo/src/cms/cms.config.ts +0 -17
  29. package/templates/demo/src/cms/collections/authors.ts +0 -20
  30. package/templates/demo/src/cms/collections/front-page.ts +0 -37
  31. package/templates/demo/src/cms/collections/menus.ts +0 -18
  32. package/templates/demo/src/cms/collections/pages.ts +0 -59
  33. package/templates/demo/src/cms/collections/posts.ts +0 -55
  34. package/templates/demo/src/cms/collections/taxonomies.ts +0 -18
  35. package/templates/demo/src/cms/collections/users.ts +0 -20
  36. package/templates/demo/src/cms/internals/seed.data.ts +0 -506
  37. package/templates/demo/src/cms/internals/seed.ts +0 -7
  38. package/templates/demo/src/components/BlockRenderer.astro +0 -32
  39. package/templates/demo/src/components/RichTextContent.astro +0 -18
  40. package/templates/demo/src/components/blocks/Faq.astro +0 -22
  41. package/templates/demo/src/components/blocks/Hero.astro +0 -19
  42. package/templates/demo/src/components/blocks/Image.astro +0 -25
  43. package/templates/demo/src/components/blocks/Text.astro +0 -16
  44. package/templates/demo/src/layouts/PublicLayout.astro +0 -53
  45. package/templates/demo/src/pages/[...slug].astro +0 -31
  46. package/templates/demo/src/pages/blog/[slug].astro +0 -41
  47. package/templates/demo/src/pages/index.astro +0 -51
  48. package/templates/local/astro.config.mjs +0 -23
  49. package/templates/local/db.ts +0 -48
  50. package/templates/local/drizzle.config.ts +0 -15
@@ -1,253 +0,0 @@
1
- @import "tailwindcss";
2
- @custom-variant dark (&:is(.dark *));
3
-
4
- /* ── Light: cool blue-gray, neutral surfaces ─────────────────────── */
5
- :root {
6
- --background: oklch(0.985 0.002 247);
7
- --foreground: oklch(0.145 0.015 285);
8
- --card: oklch(1 0 0);
9
- --card-foreground: oklch(0.145 0.015 285);
10
- --popover: oklch(1 0 0);
11
- --popover-foreground: oklch(0.145 0.015 285);
12
- --primary: oklch(0.145 0.015 285);
13
- --primary-foreground: oklch(0.985 0.002 247);
14
- --secondary: oklch(0.968 0.003 264);
15
- --secondary-foreground: oklch(0.373 0.013 285);
16
- --muted: oklch(0.968 0.003 264);
17
- --muted-foreground: oklch(0.556 0.01 264);
18
- --accent: oklch(0.968 0.003 264);
19
- --accent-foreground: oklch(0.145 0.015 285);
20
- --destructive: oklch(0.577 0.245 27.325);
21
- --border: oklch(0.929 0.006 264);
22
- --input: oklch(0.872 0.009 264);
23
- --ring: oklch(0.556 0.01 264);
24
- --chart-1: oklch(0.623 0.214 259.815);
25
- --chart-2: oklch(0.546 0.245 262.881);
26
- --chart-3: oklch(0.488 0.243 264.376);
27
- --chart-4: oklch(0.424 0.199 265.638);
28
- --chart-5: oklch(0.809 0.105 251.813);
29
- --sidebar: oklch(0.985 0.002 247);
30
- --sidebar-foreground: oklch(0.145 0.015 285);
31
- --sidebar-primary: oklch(0.145 0.015 285);
32
- --sidebar-primary-foreground: oklch(0.985 0.002 247);
33
- --sidebar-accent: oklch(0.968 0.003 264);
34
- --sidebar-accent-foreground: oklch(0.373 0.013 285);
35
- --sidebar-border: oklch(0.929 0.006 264);
36
- --sidebar-ring: oklch(0.556 0.01 264);
37
- --radius: 0.375rem;
38
-
39
- /* Extended palette */
40
- --color-surface: oklch(1 0 0);
41
- --color-muted-strong: oklch(0.929 0.006 264);
42
- --color-foreground-secondary: oklch(0.373 0.013 285);
43
- --color-foreground-tertiary: oklch(0.446 0.011 264);
44
- --color-placeholder: oklch(0.708 0.01 264);
45
- --color-input-border: oklch(0.872 0.009 264);
46
- --color-hover: oklch(0.968 0.003 264);
47
- --color-destructive-subtle: oklch(0.971 0.013 17.38);
48
- --color-accent-subtle: oklch(0.932 0.032 255);
49
- }
50
-
51
- /* ── Dark: deep blue-gray, elevated surfaces ─────────────────────── */
52
- /* .dark class is set by inline script before paint (handles system/dark/light) */
53
- .dark {
54
- --background: oklch(0.175 0.014 265);
55
- --foreground: oklch(0.96 0.004 265);
56
- --card: oklch(0.215 0.014 265);
57
- --card-foreground: oklch(0.96 0.004 265);
58
- --popover: oklch(0.215 0.014 265);
59
- --popover-foreground: oklch(0.96 0.004 265);
60
- --primary: oklch(0.96 0.004 265);
61
- --primary-foreground: oklch(0.175 0.014 265);
62
- --secondary: oklch(0.255 0.014 265);
63
- --secondary-foreground: oklch(0.78 0.006 265);
64
- --muted: oklch(0.255 0.014 265);
65
- --muted-foreground: oklch(0.6 0.006 265);
66
- --accent: oklch(0.255 0.014 265);
67
- --accent-foreground: oklch(0.96 0.004 265);
68
- --destructive: oklch(0.704 0.191 22.216);
69
- --border: oklch(0.3 0.012 265);
70
- --input: oklch(0.36 0.012 265);
71
- --ring: oklch(0.6 0.006 265);
72
- --sidebar: oklch(0.175 0.014 265);
73
- --sidebar-foreground: oklch(0.96 0.004 265);
74
- --sidebar-primary: oklch(0.623 0.214 259.815);
75
- --sidebar-primary-foreground: oklch(0.96 0.004 265);
76
- --sidebar-accent: oklch(0.255 0.014 265);
77
- --sidebar-accent-foreground: oklch(0.78 0.006 265);
78
- --sidebar-border: oklch(0.3 0.012 265);
79
- --sidebar-ring: oklch(0.6 0.006 265);
80
-
81
- --color-surface: oklch(0.215 0.014 265);
82
- --color-muted-strong: oklch(0.31 0.014 265);
83
- --color-foreground-secondary: oklch(0.78 0.006 265);
84
- --color-foreground-tertiary: oklch(0.72 0.006 265);
85
- --color-placeholder: oklch(0.48 0.006 265);
86
- --color-input-border: oklch(0.36 0.012 265);
87
- --color-hover: oklch(0.245 0.014 265);
88
- --color-destructive-subtle: oklch(0.24 0.05 25);
89
- --color-accent-subtle: oklch(0.24 0.03 265);
90
- }
91
-
92
- @theme inline {
93
- --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
94
- --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", monospace;
95
-
96
- --color-background: var(--background);
97
- --color-foreground: var(--foreground);
98
- --color-card: var(--card);
99
- --color-card-foreground: var(--card-foreground);
100
- --color-popover: var(--popover);
101
- --color-popover-foreground: var(--popover-foreground);
102
- --color-primary: var(--primary);
103
- --color-primary-foreground: var(--primary-foreground);
104
- --color-secondary: var(--secondary);
105
- --color-secondary-foreground: var(--secondary-foreground);
106
- --color-muted: var(--muted);
107
- --color-muted-foreground: var(--muted-foreground);
108
- --color-accent: var(--accent);
109
- --color-accent-foreground: var(--accent-foreground);
110
- --color-destructive: var(--destructive);
111
- --color-border: var(--border);
112
- --color-input: var(--input);
113
- --color-ring: var(--ring);
114
- --color-chart-1: var(--chart-1);
115
- --color-chart-2: var(--chart-2);
116
- --color-chart-3: var(--chart-3);
117
- --color-chart-4: var(--chart-4);
118
- --color-chart-5: var(--chart-5);
119
- --color-sidebar: var(--sidebar);
120
- --color-sidebar-foreground: var(--sidebar-foreground);
121
- --color-sidebar-primary: var(--sidebar-primary);
122
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
123
- --color-sidebar-accent: var(--sidebar-accent);
124
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
125
- --color-sidebar-border: var(--sidebar-border);
126
- --color-sidebar-ring: var(--sidebar-ring);
127
-
128
- --radius-sm: calc(var(--radius) - 2px);
129
- --radius-md: var(--radius);
130
- --radius-lg: calc(var(--radius) + 2px);
131
- --radius-xl: calc(var(--radius) + 6px);
132
- }
133
-
134
- @layer base {
135
- * {
136
- @apply border-border outline-ring/50;
137
- }
138
- body {
139
- @apply bg-background text-foreground;
140
- }
141
- html {
142
- @apply font-sans;
143
- }
144
- }
145
-
146
- *:focus-visible {
147
- outline: none;
148
- }
149
-
150
- /* ── Dark mode: native input controls, select arrow, focus ring ──── */
151
- .dark input::-webkit-calendar-picker-indicator {
152
- filter: invert(1);
153
- }
154
-
155
- .dark select {
156
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
157
- }
158
-
159
- .dark *:focus-visible {
160
- --tw-ring-offset-color: var(--color-surface);
161
- }
162
-
163
- /* ── Strip focus styles from command input ────────────────────────── */
164
- [data-slot="command-input"] {
165
- border: none;
166
- background: transparent;
167
- padding: 0;
168
- box-shadow: none;
169
- outline: none;
170
- }
171
-
172
- [data-slot="command-input"]:focus,
173
- [data-slot="command-input"]:focus-visible {
174
- border: none;
175
- box-shadow: none;
176
- outline: none;
177
- --tw-ring-shadow: none;
178
- --tw-ring-offset-shadow: none;
179
- }
180
-
181
- /* ── Tiptap editor styles ────────────────────────────────────────── */
182
- .tiptap {
183
- outline: none;
184
- }
185
-
186
- .tiptap p {
187
- margin: 0.5em 0;
188
- color: inherit;
189
- font-size: inherit;
190
- line-height: 1.6;
191
- }
192
-
193
- .tiptap h2 {
194
- font-size: 1.25rem;
195
- font-weight: 600;
196
- margin: 1em 0 0.5em;
197
- }
198
-
199
- .tiptap h3 {
200
- font-size: 1.1rem;
201
- font-weight: 600;
202
- margin: 0.75em 0 0.4em;
203
- }
204
-
205
- .tiptap ul {
206
- padding-left: 1.5em;
207
- margin: 0.5em 0;
208
- list-style-type: disc;
209
- }
210
-
211
- .tiptap ol {
212
- padding-left: 1.5em;
213
- margin: 0.5em 0;
214
- list-style-type: decimal;
215
- }
216
-
217
- .tiptap li {
218
- margin: 0.2em 0;
219
- }
220
-
221
- .tiptap blockquote {
222
- border-left: 3px solid var(--border);
223
- padding-left: 1em;
224
- margin: 0.5em 0;
225
- color: var(--muted-foreground);
226
- }
227
-
228
- /* ── Toast animations ────────────────────────────────────────────── */
229
- @keyframes toast-in {
230
- from {
231
- opacity: 0;
232
- transform: translateY(-12px) scale(0.96);
233
- }
234
- to {
235
- opacity: 1;
236
- transform: translateY(0) scale(1);
237
- }
238
- }
239
- @keyframes toast-out {
240
- from {
241
- opacity: 1;
242
- transform: translateY(0) scale(1);
243
- }
244
- to {
245
- opacity: 0;
246
- transform: translateY(-8px) scale(0.96);
247
- }
248
- }
249
-
250
- /* ── Dark mode: Tiptap placeholder ───────────────────────────────── */
251
- .dark .tiptap p.is-editor-empty:first-child::before {
252
- color: var(--color-placeholder) !important;
253
- }
@@ -1,2 +0,0 @@
1
- @import "tailwindcss";
2
- @plugin "@tailwindcss/typography";
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "astro/tsconfigs/strict",
3
- "compilerOptions": {
4
- "baseUrl": ".",
5
- "paths": {
6
- "@/*": ["./src/*"]
7
- }
8
- },
9
- "include": [".astro/types.d.ts", "**/*"],
10
- "exclude": ["dist"]
11
- }
@@ -1,16 +0,0 @@
1
- // @ts-check
2
- import cloudflare from "@astrojs/cloudflare";
3
- import react from "@astrojs/react";
4
- import tailwindcss from "@tailwindcss/vite";
5
- import { defineConfig } from "astro/config";
6
- import cmsIntegration from "@kidecms/core/integration";
7
-
8
- // https://astro.build/config
9
- export default defineConfig({
10
- output: "server",
11
- integrations: [react(), cmsIntegration()],
12
- adapter: cloudflare(),
13
- vite: {
14
- plugins: [tailwindcss()],
15
- },
16
- });
@@ -1,23 +0,0 @@
1
- import { env } from "cloudflare:workers";
2
- import { drizzle } from "drizzle-orm/d1";
3
-
4
- let dbInstance: ReturnType<typeof drizzle> | null = null;
5
- let currentDb: D1Database | null = null;
6
-
7
- export const getDb = async () => {
8
- const db = (env as any).CMS_DB as D1Database | undefined;
9
- if (!db) {
10
- throw new Error("D1 database binding CMS_DB not found. Check wrangler.toml.");
11
- }
12
-
13
- if (dbInstance && currentDb === db) return dbInstance;
14
-
15
- currentDb = db;
16
- dbInstance = drizzle(db);
17
- return dbInstance;
18
- };
19
-
20
- export const closeDb = () => {
21
- dbInstance = null;
22
- currentDb = null;
23
- };
@@ -1,24 +0,0 @@
1
- import { existsSync, readdirSync } from "node:fs";
2
- import { defineConfig } from "drizzle-kit";
3
-
4
- // Look for the local D1 sqlite file (created by wrangler after first run).
5
- // Returns null if it doesn't exist yet — `drizzle-kit generate` doesn't need it.
6
- function getLocalD1Path(): string | null {
7
- const dir = ".wrangler/state/v3/d1/miniflare-D1DatabaseObject";
8
- if (!existsSync(dir)) return null;
9
- try {
10
- const file = readdirSync(dir).find((f) => f.endsWith(".sqlite") && f !== "*.sqlite");
11
- return file ? `${dir}/${file}` : null;
12
- } catch {
13
- return null;
14
- }
15
- }
16
-
17
- export default defineConfig({
18
- schema: "./src/cms/.generated/schema.ts",
19
- out: "./src/cms/migrations",
20
- dialect: "sqlite",
21
- dbCredentials: {
22
- url: getLocalD1Path() ?? ":memory:",
23
- },
24
- });
@@ -1,25 +0,0 @@
1
- import { env } from "cloudflare:workers";
2
-
3
- function getBucket(): R2Bucket {
4
- const bucket = (env as any).CMS_ASSETS as R2Bucket | undefined;
5
- if (!bucket) throw new Error("R2 bucket binding CMS_ASSETS not found. Check wrangler.toml.");
6
- return bucket;
7
- }
8
-
9
- function toKey(storagePath: string): string {
10
- return storagePath.startsWith("/") ? storagePath.slice(1) : storagePath;
11
- }
12
-
13
- export async function putFile(storagePath: string, data: ArrayBuffer | Uint8Array): Promise<void> {
14
- await getBucket().put(toKey(storagePath), data);
15
- }
16
-
17
- export async function getFile(storagePath: string): Promise<ArrayBuffer | null> {
18
- const obj = await getBucket().get(toKey(storagePath));
19
- if (!obj) return null;
20
- return obj.arrayBuffer();
21
- }
22
-
23
- export async function deleteFile(storagePath: string): Promise<void> {
24
- await getBucket().delete(toKey(storagePath));
25
- }
@@ -1,36 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { getFile } from "@/cms/adapters/storage";
3
-
4
- export const prerender = false;
5
-
6
- const MIME_TYPES: Record<string, string> = {
7
- ".jpg": "image/jpeg",
8
- ".jpeg": "image/jpeg",
9
- ".png": "image/png",
10
- ".gif": "image/gif",
11
- ".webp": "image/webp",
12
- ".avif": "image/avif",
13
- ".svg": "image/svg+xml",
14
- ".pdf": "application/pdf",
15
- ".mp4": "video/mp4",
16
- ".webm": "video/webm",
17
- };
18
-
19
- export const GET: APIRoute = async ({ params }) => {
20
- const filePath = `/uploads/${params.path}`;
21
- const data = await getFile(filePath);
22
-
23
- if (!data) {
24
- return new Response("Not found", { status: 404 });
25
- }
26
-
27
- const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
28
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
29
-
30
- return new Response(data, {
31
- headers: {
32
- "Content-Type": contentType,
33
- "Cache-Control": "public, max-age=31536000, immutable",
34
- },
35
- });
36
- };
@@ -1,17 +0,0 @@
1
- name = "{{PROJECT_NAME}}"
2
- compatibility_date = "2025-01-01"
3
- compatibility_flags = ["nodejs_compat"]
4
-
5
- [[d1_databases]]
6
- binding = "CMS_DB"
7
- database_name = "{{PROJECT_NAME}}-db"
8
- database_id = "" # Run: npx wrangler d1 create {{PROJECT_NAME}}-db
9
- migrations_dir = "src/cms/migrations"
10
-
11
- [triggers]
12
- crons = ["* * * * *"] # scheduled publishing — every minute
13
-
14
- [[r2_buckets]]
15
- binding = "CMS_ASSETS"
16
- bucket_name = "{{PROJECT_NAME}}-assets"
17
- # Run: npx wrangler r2 bucket create {{PROJECT_NAME}}-assets
@@ -1,17 +0,0 @@
1
- import { defineConfig } from "@kidecms/core";
2
- import users from "./collections/users";
3
- import authors from "./collections/authors";
4
- import posts from "./collections/posts";
5
- import taxonomies from "./collections/taxonomies";
6
- import menus from "./collections/menus";
7
- import frontPage from "./collections/front-page";
8
- import pages from "./collections/pages";
9
-
10
- export default defineConfig({
11
- database: { dialect: "sqlite" },
12
- locales: {
13
- default: "en",
14
- supported: ["en"],
15
- },
16
- collections: [users, authors, posts, taxonomies, menus, frontPage, pages],
17
- });
@@ -1,20 +0,0 @@
1
- import { defineCollection, fields } from "@kidecms/core";
2
-
3
- export default defineCollection({
4
- slug: "authors",
5
- labels: { singular: "Author", plural: "Authors" },
6
- labelField: "name",
7
- timestamps: true,
8
- views: {
9
- list: { columns: ["name", "title", "_updatedAt"] },
10
- },
11
- fields: {
12
- name: fields.text({ required: true }),
13
- description: fields.text({ translatable: true }),
14
- slug: fields.slug({ from: "name", unique: true, admin: { position: "sidebar" } }),
15
- title: fields.text({ required: true }),
16
- avatar: fields.image({
17
- admin: { placeholder: "https://images.example.com/avatar.jpg" },
18
- }),
19
- },
20
- });
@@ -1,37 +0,0 @@
1
- import { defineCollection, fields, hasRole } from "@kidecms/core";
2
-
3
- export default defineCollection({
4
- slug: "front-page",
5
- labels: { singular: "Front Page", plural: "Front Page" },
6
- singleton: true,
7
- preview: "/",
8
- timestamps: true,
9
- drafts: true,
10
- access: {
11
- publish: hasRole("admin"),
12
- },
13
- fields: {
14
- blocks: fields.blocks({
15
- translatable: true,
16
- types: {
17
- hero: {
18
- eyebrow: fields.text(),
19
- heading: fields.text({ required: true }),
20
- body: fields.text(),
21
- ctaLabel: fields.text(),
22
- ctaHref: fields.relation({ collection: "pages" }),
23
- },
24
- text: {
25
- heading: fields.text(),
26
- content: fields.richText(),
27
- },
28
- faq: {
29
- heading: fields.text(),
30
- items: fields.json({
31
- admin: { component: "repeater", help: "Add question and answer pairs" },
32
- }),
33
- },
34
- },
35
- }),
36
- },
37
- });
@@ -1,18 +0,0 @@
1
- import { defineCollection, fields } from "@kidecms/core";
2
-
3
- export default defineCollection({
4
- slug: "menus",
5
- labels: { singular: "Menu", plural: "Menus" },
6
- timestamps: true,
7
- views: {
8
- list: { columns: ["name", "slug", "_updatedAt"] },
9
- },
10
- fields: {
11
- name: fields.text({ required: true }),
12
- slug: fields.slug({ from: "name", unique: true, admin: { position: "sidebar" } }),
13
- items: fields.json({
14
- translatable: true,
15
- admin: { component: "menu-items" },
16
- }),
17
- },
18
- });
@@ -1,59 +0,0 @@
1
- import { defineCollection, fields, hasRole } from "@kidecms/core";
2
-
3
- export default defineCollection({
4
- slug: "pages",
5
- labels: { singular: "Page", plural: "Pages" },
6
- preview: true,
7
- timestamps: true,
8
- drafts: true,
9
- versions: { max: 20 },
10
- views: {
11
- list: { columns: ["title", "_status", "_updatedAt"] },
12
- },
13
- fields: {
14
- title: fields.text({ required: true, translatable: true }),
15
- slug: fields.slug({ from: "title", unique: true, translatable: true, admin: { position: "sidebar" } }),
16
- summary: fields.text({
17
- translatable: true,
18
- admin: { rows: 3 },
19
- access: { read: hasRole("admin") },
20
- }),
21
- image: fields.image(),
22
- relatedPosts: fields.relation({ collection: "posts", hasMany: true, admin: { position: "sidebar" } }),
23
- seoDescription: fields.text({
24
- maxLength: 160,
25
- translatable: true,
26
- admin: { rows: 3, help: "Meta description for search engines. Max 160 characters.", position: "sidebar" },
27
- access: { update: hasRole("admin") },
28
- }),
29
- blocks: fields.blocks({
30
- translatable: true,
31
- types: {
32
- text: {
33
- heading: fields.text(),
34
- content: fields.richText(),
35
- },
36
- image: {
37
- images: fields.array({ of: fields.image(), defaultValue: [] }),
38
- },
39
- faq: {
40
- heading: fields.text(),
41
- items: fields.json({
42
- admin: { component: "repeater", help: "Add question and answer pairs" },
43
- }),
44
- },
45
- },
46
- }),
47
- },
48
- hooks: {
49
- afterPublish(doc, context) {
50
- context.cache?.invalidate({ tags: ["pages", "home", `page:${doc._id}`] });
51
- },
52
- afterUpdate(doc, context) {
53
- context.cache?.invalidate({ tags: ["pages", `page:${doc._id}`] });
54
- },
55
- afterDelete(doc, context) {
56
- context.cache?.invalidate({ tags: ["pages", "home", `page:${doc._id}`] });
57
- },
58
- },
59
- });
@@ -1,55 +0,0 @@
1
- import { defineCollection, fields, richTextToPlainText } from "@kidecms/core";
2
-
3
- export default defineCollection({
4
- slug: "posts",
5
- labels: { singular: "Post", plural: "Posts" },
6
- pathPrefix: "blog",
7
- timestamps: true,
8
- drafts: true,
9
- versions: { max: 20 },
10
- views: {
11
- list: { columns: ["title", "category", "_status", "_updatedAt"] },
12
- },
13
- fields: {
14
- title: fields.text({
15
- required: true,
16
- indexed: true,
17
- translatable: true,
18
- }),
19
- slug: fields.slug({ from: "title", unique: true, translatable: true, admin: { position: "sidebar" } }),
20
- excerpt: fields.text({
21
- maxLength: 300,
22
- translatable: true,
23
- admin: { rows: 3 },
24
- }),
25
- image: fields.image(),
26
- body: fields.richText({ translatable: true, admin: { rows: 14 } }),
27
- category: fields.text({
28
- admin: { component: "taxonomy-select", placeholder: "categories", position: "sidebar" },
29
- }),
30
- author: fields.relation({ collection: "authors", admin: { position: "sidebar" } }),
31
- seoDescription: fields.text({
32
- maxLength: 160,
33
- translatable: true,
34
- admin: { rows: 3, help: "Meta description for search engines. Max 160 characters.", position: "sidebar" },
35
- }),
36
- },
37
- hooks: {
38
- beforeCreate(data) {
39
- if (!data.excerpt && typeof data.body === "object" && data.body) {
40
- const text = richTextToPlainText(data.body as never);
41
- if (text) data.excerpt = text.slice(0, 180);
42
- }
43
- return data;
44
- },
45
- afterPublish(doc, context) {
46
- context.cache?.invalidate({ tags: ["posts", "home", `post:${doc._id}`] });
47
- },
48
- afterUpdate(doc, context) {
49
- context.cache?.invalidate({ tags: ["posts", `post:${doc._id}`] });
50
- },
51
- afterDelete(doc, context) {
52
- context.cache?.invalidate({ tags: ["posts", "home", `post:${doc._id}`] });
53
- },
54
- },
55
- });
@@ -1,18 +0,0 @@
1
- import { defineCollection, fields } from "@kidecms/core";
2
-
3
- export default defineCollection({
4
- slug: "taxonomies",
5
- labels: { singular: "Taxonomy", plural: "Taxonomies" },
6
- timestamps: true,
7
- views: {
8
- list: { columns: ["name", "slug", "_updatedAt"] },
9
- },
10
- fields: {
11
- name: fields.text({ required: true }),
12
- slug: fields.slug({ from: "name", unique: true, admin: { position: "sidebar" } }),
13
- terms: fields.json({
14
- translatable: true,
15
- admin: { component: "taxonomy-terms" },
16
- }),
17
- },
18
- });
@@ -1,20 +0,0 @@
1
- import { defineCollection, fields } from "@kidecms/core";
2
-
3
- export default defineCollection({
4
- slug: "users",
5
- labels: { singular: "User", plural: "Users" },
6
- auth: true,
7
- timestamps: true,
8
- views: {
9
- list: { columns: ["name", "email", "role", "_updatedAt"] },
10
- },
11
- fields: {
12
- name: fields.text({ required: true }),
13
- email: fields.email({ required: true, unique: true }),
14
- role: fields.select({
15
- options: ["admin", "editor", "viewer"],
16
- defaultValue: "editor",
17
- }),
18
- password: fields.text({ admin: { hidden: true } }),
19
- },
20
- });