create-kide-app 0.0.7 → 0.0.9
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/index.js +143 -51
- package/package.json +1 -1
- package/templates/base/.env.example +2 -0
- package/templates/base/package.json +39 -0
- package/templates/base/src/cms/adapters/db.ts +49 -0
- package/templates/base/src/cms/adapters/email.ts +34 -0
- package/templates/base/src/cms/adapters/storage.ts +29 -0
- package/templates/base/src/cms/cms.config.ts +7 -0
- package/templates/base/src/cms/collections/users.ts +20 -0
- package/templates/base/src/cms/internals/create-admin.ts +40 -0
- package/templates/base/src/cms/internals/generator.ts +10 -0
- package/templates/base/src/cms/internals/runtime.ts +76 -0
- package/templates/base/src/env.d.ts +12 -0
- package/templates/base/src/pages/index.astro +11 -0
- package/templates/base/src/styles/admin.css +253 -0
- package/templates/base/src/styles/public.css +2 -0
- package/templates/base/tsconfig.json +11 -0
- package/templates/cloudflare/astro.config.mjs +1 -1
- package/templates/cloudflare/uploads-route.ts +1 -1
- package/templates/cloudflare/wrangler.toml +1 -0
- package/templates/demo/src/cms/cms.config.ts +17 -0
- package/templates/demo/src/cms/collections/authors.ts +20 -0
- package/templates/demo/src/cms/collections/front-page.ts +37 -0
- package/templates/demo/src/cms/collections/menus.ts +18 -0
- package/templates/demo/src/cms/collections/pages.ts +59 -0
- package/templates/demo/src/cms/collections/posts.ts +55 -0
- package/templates/demo/src/cms/collections/taxonomies.ts +18 -0
- package/templates/demo/src/cms/collections/users.ts +20 -0
- package/templates/demo/src/cms/internals/seed.data.ts +506 -0
- package/templates/demo/src/cms/internals/seed.ts +7 -0
- package/templates/demo/src/components/BlockRenderer.astro +32 -0
- package/templates/demo/src/components/RichTextContent.astro +18 -0
- package/templates/demo/src/components/blocks/Faq.astro +22 -0
- package/templates/demo/src/components/blocks/Hero.astro +16 -0
- package/templates/demo/src/components/blocks/Image.astro +25 -0
- package/templates/demo/src/components/blocks/Text.astro +16 -0
- package/templates/demo/src/layouts/PublicLayout.astro +54 -0
- package/templates/demo/src/pages/[...slug].astro +31 -0
- package/templates/demo/src/pages/blog/[slug].astro +41 -0
- package/templates/demo/src/pages/index.astro +51 -0
- package/templates/demo/src/scripts/preview.ts +33 -0
- package/templates/local/astro.config.mjs +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "@/styles/public.css";
|
|
3
|
+
import { cms } from "@/cms/.generated/api";
|
|
4
|
+
|
|
5
|
+
const { title = "Kide CMS", description = "A code-first CMS built inside Astro." } = Astro.props;
|
|
6
|
+
type MenuItem = { id: string; label?: string; href?: string; target?: string; children: MenuItem[] };
|
|
7
|
+
|
|
8
|
+
const menu = await cms.menus.findOne({ slug: "main" });
|
|
9
|
+
const menuItems: MenuItem[] = menu?.items ? (Array.isArray(menu.items) ? menu.items : []) : [];
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<!doctype html>
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="utf-8" />
|
|
16
|
+
<meta name="viewport" content="width=device-width" />
|
|
17
|
+
<meta name="description" content={description} />
|
|
18
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
19
|
+
<link rel="icon" href="/favicon.ico" />
|
|
20
|
+
<title>{title}</title>
|
|
21
|
+
</head>
|
|
22
|
+
<body class="min-h-screen bg-white pb-24 text-gray-900 antialiased">
|
|
23
|
+
<header class="mx-auto flex max-w-4xl items-center gap-8 px-4 py-4">
|
|
24
|
+
<a href="/" class="text-lg font-semibold no-underline">Demo website</a>
|
|
25
|
+
{
|
|
26
|
+
menuItems.length > 0 && (
|
|
27
|
+
<nav>
|
|
28
|
+
<ul class="flex list-none gap-6 p-0">
|
|
29
|
+
{menuItems.map((item) => (
|
|
30
|
+
<li>
|
|
31
|
+
<a
|
|
32
|
+
href={String(item.href ?? "#")}
|
|
33
|
+
target={item.target || undefined}
|
|
34
|
+
class="text-sm text-gray-500 no-underline transition-colors hover:text-gray-900"
|
|
35
|
+
>
|
|
36
|
+
{String(item.label ?? "")}
|
|
37
|
+
</a>
|
|
38
|
+
</li>
|
|
39
|
+
))}
|
|
40
|
+
</ul>
|
|
41
|
+
</nav>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
<a
|
|
45
|
+
href="/admin"
|
|
46
|
+
class="ml-auto rounded-md border border-teal-700 px-3 py-1.5 text-sm font-medium text-teal-700 no-underline transition-colors hover:bg-teal-50"
|
|
47
|
+
>
|
|
48
|
+
Open admin
|
|
49
|
+
</a>
|
|
50
|
+
</header>
|
|
51
|
+
<slot />
|
|
52
|
+
<script src="@/scripts/preview.ts"></script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
import PublicLayout from "@/layouts/PublicLayout.astro";
|
|
3
|
+
import BlockRenderer from "@/components/BlockRenderer.astro";
|
|
4
|
+
import { cms } from "@/cms/.generated/api";
|
|
5
|
+
import { cacheTags, parseBlocks } from "@kidecms/core";
|
|
6
|
+
|
|
7
|
+
const isPreview = Astro.url.searchParams.has("preview");
|
|
8
|
+
const doc = await cms.pages.findOne({ slug: Astro.params.slug!, status: isPreview ? "any" : "published" });
|
|
9
|
+
if (!doc) return Astro.redirect("/");
|
|
10
|
+
|
|
11
|
+
if (!isPreview) Astro.cache.set({ tags: cacheTags("pages", doc._id) });
|
|
12
|
+
|
|
13
|
+
const blocks = parseBlocks(doc.blocks);
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<PublicLayout title={doc.title}>
|
|
17
|
+
<article class="mx-auto max-w-2xl px-4 py-8">
|
|
18
|
+
<a href="/" class="text-sm text-teal-700">← Back</a>
|
|
19
|
+
<h1 data-cms="title" class="mt-4 mb-2 text-2xl font-semibold">{doc.title}</h1>
|
|
20
|
+
{
|
|
21
|
+
doc.summary && (
|
|
22
|
+
<p data-cms="summary" class="mb-6 text-gray-500">
|
|
23
|
+
{doc.summary}
|
|
24
|
+
</p>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
<div data-cms="blocks">
|
|
28
|
+
<BlockRenderer blocks={blocks} />
|
|
29
|
+
</div>
|
|
30
|
+
</article>
|
|
31
|
+
</PublicLayout>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
import PublicLayout from "@/layouts/PublicLayout.astro";
|
|
3
|
+
import RichTextContent from "@/components/RichTextContent.astro";
|
|
4
|
+
import { cms } from "@/cms/.generated/api";
|
|
5
|
+
import { cacheTags, cmsImage, cmsSrcset } from "@kidecms/core";
|
|
6
|
+
|
|
7
|
+
const isPreview = Astro.url.searchParams.has("preview");
|
|
8
|
+
const doc = await cms.posts.findOne({ slug: Astro.params.slug!, status: isPreview ? "any" : "published" });
|
|
9
|
+
if (!doc) return Astro.redirect("/");
|
|
10
|
+
|
|
11
|
+
if (!isPreview) Astro.cache.set({ tags: cacheTags("posts", doc._id) });
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<PublicLayout title={doc.title}>
|
|
15
|
+
<article class="mx-auto max-w-2xl px-4 py-8">
|
|
16
|
+
<a href="/" class="text-sm text-teal-700">← Back</a>
|
|
17
|
+
<h1 data-cms="title" class="mt-4 mb-2 text-2xl font-semibold">{doc.title}</h1>
|
|
18
|
+
{
|
|
19
|
+
doc.image && (
|
|
20
|
+
<img
|
|
21
|
+
src={cmsImage(doc.image, 1024)}
|
|
22
|
+
srcset={cmsSrcset(doc.image)}
|
|
23
|
+
sizes="(max-width: 640px) 100vw, 640px"
|
|
24
|
+
alt={doc.title}
|
|
25
|
+
loading="eager"
|
|
26
|
+
class="mb-6 h-auto w-full rounded-lg"
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
{
|
|
31
|
+
doc.excerpt && (
|
|
32
|
+
<p data-cms="excerpt" class="mb-6 text-gray-500">
|
|
33
|
+
{doc.excerpt}
|
|
34
|
+
</p>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
<div data-cms="body" class="prose">
|
|
38
|
+
<RichTextContent content={doc.body} />
|
|
39
|
+
</div>
|
|
40
|
+
</article>
|
|
41
|
+
</PublicLayout>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
import PublicLayout from "@/layouts/PublicLayout.astro";
|
|
3
|
+
import BlockRenderer from "@/components/BlockRenderer.astro";
|
|
4
|
+
import type { PostsDocument } from "@/cms/.generated/types";
|
|
5
|
+
import { cms } from "@/cms/.generated/api";
|
|
6
|
+
import { parseBlocks } from "@kidecms/core";
|
|
7
|
+
|
|
8
|
+
const isPreview = Astro.url.searchParams.has("preview");
|
|
9
|
+
const page = await cms["front-page"].findOne({ status: isPreview ? "any" : "published" });
|
|
10
|
+
const blocks = parseBlocks(page?.blocks);
|
|
11
|
+
|
|
12
|
+
// Resolve hero ctaHref relation (page ID → slug URL)
|
|
13
|
+
for (const block of blocks) {
|
|
14
|
+
if (block.type === "hero" && block.ctaHref) {
|
|
15
|
+
const linked = await cms.pages.findOne({ where: { _id: String(block.ctaHref) } });
|
|
16
|
+
if (linked) block.ctaHref = `/${linked.slug}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const posts = await cms.posts.find({
|
|
21
|
+
status: "published",
|
|
22
|
+
sort: { field: "sortOrder", direction: "desc" },
|
|
23
|
+
limit: 10,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!isPreview) Astro.cache.set({ tags: ["front-page", "posts", "home"] });
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<PublicLayout title="Kide CMS">
|
|
30
|
+
<div class="mx-auto flex max-w-4xl gap-8 px-4 py-8">
|
|
31
|
+
<main class="min-w-0 flex-1">
|
|
32
|
+
{blocks.length > 0 ? <BlockRenderer blocks={blocks} /> : <p class="text-gray-500">No content yet.</p>}
|
|
33
|
+
</main>
|
|
34
|
+
|
|
35
|
+
<aside class="hidden w-64 shrink-0 lg:block">
|
|
36
|
+
<h2 class="mb-3 text-sm font-semibold text-gray-900">Recent posts</h2>
|
|
37
|
+
<ul class="space-y-2">
|
|
38
|
+
{
|
|
39
|
+
posts.map((post: PostsDocument) => (
|
|
40
|
+
<li>
|
|
41
|
+
<a href={`/blog/${post.slug}`} class="text-sm text-teal-700 hover:underline">
|
|
42
|
+
{post.title}
|
|
43
|
+
</a>
|
|
44
|
+
</li>
|
|
45
|
+
))
|
|
46
|
+
}
|
|
47
|
+
</ul>
|
|
48
|
+
{posts.length === 0 && <p class="text-sm text-gray-500">No published posts yet.</p>}
|
|
49
|
+
</aside>
|
|
50
|
+
</div>
|
|
51
|
+
</PublicLayout>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
if (new URLSearchParams(location.search).has("preview")) {
|
|
2
|
+
const ch = new BroadcastChannel("cms-preview");
|
|
3
|
+
let pending = 0;
|
|
4
|
+
|
|
5
|
+
ch.onmessage = (e: MessageEvent) => {
|
|
6
|
+
const d = e.data;
|
|
7
|
+
|
|
8
|
+
if (d.type === "reload") {
|
|
9
|
+
location.reload();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const els = document.querySelectorAll<HTMLElement>(`[data-cms="${d.field}"]`);
|
|
14
|
+
if (!els.length) return;
|
|
15
|
+
|
|
16
|
+
if (d.render) {
|
|
17
|
+
const id = ++pending;
|
|
18
|
+
fetch("/api/cms/preview/render", {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: { "Content-Type": "application/json" },
|
|
21
|
+
body: JSON.stringify({ type: d.render, data: d.value }),
|
|
22
|
+
})
|
|
23
|
+
.then((r) => r.text())
|
|
24
|
+
.then((html) => {
|
|
25
|
+
if (id === pending) els.forEach((el) => (el.innerHTML = html));
|
|
26
|
+
});
|
|
27
|
+
} else if (d.html != null) {
|
|
28
|
+
els.forEach((el) => (el.innerHTML = d.html));
|
|
29
|
+
} else {
|
|
30
|
+
els.forEach((el) => (el.textContent = d.value));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -3,7 +3,7 @@ import node from "@astrojs/node";
|
|
|
3
3
|
import react from "@astrojs/react";
|
|
4
4
|
import tailwindcss from "@tailwindcss/vite";
|
|
5
5
|
import { defineConfig, memoryCache } from "astro/config";
|
|
6
|
-
import cmsIntegration from "
|
|
6
|
+
import cmsIntegration from "@kidecms/core/integration";
|
|
7
7
|
|
|
8
8
|
// https://astro.build/config
|
|
9
9
|
export default defineConfig({
|