first commit
This commit is contained in:
74
templates/starter/src/layouts/Base.astro
Normal file
74
templates/starter/src/layouts/Base.astro
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
import { getMenu } from "emdash";
|
||||
import {
|
||||
WidgetArea,
|
||||
EmDashHead,
|
||||
EmDashBodyStart,
|
||||
EmDashBodyEnd,
|
||||
} from "emdash/ui";
|
||||
import { createPublicPageContext } from "emdash/page";
|
||||
import LiveSearch from "emdash/ui/search";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
image?: string | null;
|
||||
canonical?: string | null;
|
||||
/** Pass content reference for plugin page contributions on content pages */
|
||||
content?: { collection: string; id: string; slug?: string | null };
|
||||
}
|
||||
|
||||
const { title, description, image, canonical, content } = Astro.props;
|
||||
|
||||
const menu = await getMenu("primary");
|
||||
|
||||
const pageCtx = createPublicPageContext({
|
||||
Astro,
|
||||
kind: content ? "content" : "custom",
|
||||
pageType: "website",
|
||||
title,
|
||||
description,
|
||||
canonical,
|
||||
image,
|
||||
content,
|
||||
});
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{title}</title>
|
||||
{description && <meta name="description" content={description} />}
|
||||
{canonical && <link rel="canonical" href={canonical} />}
|
||||
<EmDashHead page={pageCtx} />
|
||||
</head>
|
||||
<body>
|
||||
<EmDashBodyStart page={pageCtx} />
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">{title}</a>
|
||||
{
|
||||
menu?.items.map((item) => (
|
||||
<a href={item.url} target={item.target}>
|
||||
{item.label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<LiveSearch placeholder="Search..." collections={["posts", "pages"]} />
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<WidgetArea name="sidebar" />
|
||||
</footer>
|
||||
|
||||
<EmDashBodyEnd page={pageCtx} />
|
||||
</body>
|
||||
</html>
|
||||
6
templates/starter/src/live.config.ts
Normal file
6
templates/starter/src/live.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { defineLiveCollection } from "astro:content";
|
||||
import { emdashLoader } from "emdash/runtime";
|
||||
|
||||
export const collections = {
|
||||
_emdash: defineLiveCollection({ loader: emdashLoader() }),
|
||||
};
|
||||
9
templates/starter/src/pages/404.astro
Normal file
9
templates/starter/src/pages/404.astro
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
import Base from "../layouts/Base.astro";
|
||||
---
|
||||
|
||||
<Base title="Not Found">
|
||||
<h1>Page not found</h1>
|
||||
<p>The page you're looking for doesn't exist.</p>
|
||||
<a href="/">Go home</a>
|
||||
</Base>
|
||||
40
templates/starter/src/pages/[slug].astro
Normal file
40
templates/starter/src/pages/[slug].astro
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
import { getEmDashEntry, getSeoMeta } from "emdash";
|
||||
import { PortableText } from "emdash/ui";
|
||||
import Base from "../layouts/Base.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect("/404");
|
||||
}
|
||||
|
||||
const { entry: page, cacheHint } = await getEmDashEntry("pages", slug);
|
||||
|
||||
if (!page) {
|
||||
return Astro.redirect("/404");
|
||||
}
|
||||
|
||||
Astro.cache.set(cacheHint);
|
||||
|
||||
const seo = getSeoMeta(page, {
|
||||
siteTitle: "My Site",
|
||||
siteUrl: Astro.url.origin,
|
||||
path: `/${slug}`,
|
||||
});
|
||||
---
|
||||
|
||||
<Base
|
||||
title={seo.title}
|
||||
description={seo.description}
|
||||
canonical={seo.canonical}
|
||||
content={{ collection: "pages", id: page.data.id, slug }}
|
||||
>
|
||||
<article>
|
||||
<h1 {...page.edit.title}>{page.data.title}</h1>
|
||||
|
||||
<div {...page.edit.content}>
|
||||
<PortableText value={page.data.content} />
|
||||
</div>
|
||||
</article>
|
||||
</Base>
|
||||
42
templates/starter/src/pages/category/[slug].astro
Normal file
42
templates/starter/src/pages/category/[slug].astro
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
import { getTerm, getEmDashCollection } from "emdash";
|
||||
import { Image } from "emdash/ui";
|
||||
import Base from "../../layouts/Base.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const term = slug ? await getTerm("category", slug) : null;
|
||||
|
||||
if (!term) {
|
||||
return Astro.redirect("/404");
|
||||
}
|
||||
|
||||
const { entries: posts } = await getEmDashCollection("posts", {
|
||||
where: { category: term.slug },
|
||||
orderBy: { published_at: "desc" },
|
||||
});
|
||||
---
|
||||
|
||||
<Base title={`${term.label} posts`} description={`Posts in ${term.label}`}>
|
||||
<h1>{term.label}</h1>
|
||||
<p>{posts.length} {posts.length === 1 ? "post" : "posts"}</p>
|
||||
|
||||
{
|
||||
posts.length === 0 ? (
|
||||
<p>No posts in this category yet.</p>
|
||||
) : (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li>
|
||||
<a href={`/posts/${post.id}`}>
|
||||
{post.data.featured_image && (
|
||||
<Image image={post.data.featured_image} />
|
||||
)}
|
||||
<h2>{post.data.title}</h2>
|
||||
</a>
|
||||
{post.data.excerpt && <p>{post.data.excerpt}</p>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
46
templates/starter/src/pages/index.astro
Normal file
46
templates/starter/src/pages/index.astro
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
import { getEmDashCollection } from "emdash";
|
||||
import { Image } from "emdash/ui";
|
||||
import Base from "../layouts/Base.astro";
|
||||
|
||||
const { entries: posts, cacheHint } = await getEmDashCollection("posts", {
|
||||
orderBy: { published_at: "desc" },
|
||||
});
|
||||
Astro.cache.set(cacheHint);
|
||||
---
|
||||
|
||||
<Base title="My Site">
|
||||
<h1>Recent Posts</h1>
|
||||
|
||||
{
|
||||
posts.length === 0 ? (
|
||||
<p>
|
||||
No posts yet.{" "}
|
||||
<a href="/_emdash/admin/content/posts/new">Create one</a>.
|
||||
</p>
|
||||
) : (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li>
|
||||
<a href={`/posts/${post.id}`}>
|
||||
{post.data.featured_image && (
|
||||
<Image image={post.data.featured_image} />
|
||||
)}
|
||||
<h2>{post.data.title}</h2>
|
||||
</a>
|
||||
{post.data.excerpt && <p>{post.data.excerpt}</p>}
|
||||
{post.data.publishedAt && (
|
||||
<time datetime={post.data.publishedAt.toISOString()}>
|
||||
{post.data.publishedAt.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</time>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
90
templates/starter/src/pages/posts/[slug].astro
Normal file
90
templates/starter/src/pages/posts/[slug].astro
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
import { getEmDashEntry, getEntryTerms, getSeoMeta } from "emdash";
|
||||
import { Image, PortableText, WidgetArea } from "emdash/ui";
|
||||
import Base from "../../layouts/Base.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
|
||||
if (!slug) {
|
||||
return Astro.redirect("/404");
|
||||
}
|
||||
|
||||
const { entry: post, cacheHint } = await getEmDashEntry("posts", slug);
|
||||
|
||||
if (!post) {
|
||||
return Astro.redirect("/404");
|
||||
}
|
||||
|
||||
Astro.cache.set(cacheHint);
|
||||
|
||||
// SEO meta from content fields
|
||||
const seo = getSeoMeta(post, {
|
||||
siteTitle: "My Site",
|
||||
siteUrl: Astro.url.origin,
|
||||
path: `/posts/${slug}`,
|
||||
});
|
||||
|
||||
// Taxonomy terms for this post (use post.data.id, not post.id)
|
||||
const tags = await getEntryTerms("posts", post.data.id, "tag");
|
||||
const categories = await getEntryTerms("posts", post.data.id, "category");
|
||||
---
|
||||
|
||||
<Base
|
||||
title={seo.title}
|
||||
description={seo.description}
|
||||
canonical={seo.canonical}
|
||||
image={seo.ogImage}
|
||||
content={{ collection: "posts", id: post.data.id, slug }}
|
||||
>
|
||||
<article>
|
||||
{
|
||||
post.data.featured_image && (
|
||||
<div {...post.edit.featured_image}>
|
||||
<Image image={post.data.featured_image} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<h1 {...post.edit.title}>{post.data.title}</h1>
|
||||
|
||||
{
|
||||
post.data.publishedAt && (
|
||||
<time datetime={post.data.publishedAt.toISOString()}>
|
||||
{post.data.publishedAt.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</time>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
categories.length > 0 && (
|
||||
<div>
|
||||
{categories.map((c) => (
|
||||
<a href={`/category/${c.slug}`}>{c.label}</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div {...post.edit.content}>
|
||||
<PortableText value={post.data.content} />
|
||||
</div>
|
||||
|
||||
{
|
||||
tags.length > 0 && (
|
||||
<div>
|
||||
{tags.map((t) => (
|
||||
<a href={`/tag/${t.slug}`}>{t.label}</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</article>
|
||||
|
||||
<aside>
|
||||
<WidgetArea name="sidebar" />
|
||||
</aside>
|
||||
</Base>
|
||||
49
templates/starter/src/pages/posts/index.astro
Normal file
49
templates/starter/src/pages/posts/index.astro
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
import { getEmDashCollection, getEntryTerms } from "emdash";
|
||||
import { Image } from "emdash/ui";
|
||||
import Base from "../../layouts/Base.astro";
|
||||
|
||||
const { entries: posts, cacheHint } = await getEmDashCollection("posts", {
|
||||
orderBy: { published_at: "desc" },
|
||||
});
|
||||
Astro.cache.set(cacheHint);
|
||||
|
||||
// Fetch tags for each post
|
||||
const postsWithTags = await Promise.all(
|
||||
posts.map(async (post) => {
|
||||
const tags = await getEntryTerms("posts", post.data.id, "tag");
|
||||
return { post, tags };
|
||||
})
|
||||
);
|
||||
---
|
||||
|
||||
<Base title="All Posts">
|
||||
<h1>Posts</h1>
|
||||
|
||||
{
|
||||
postsWithTags.length === 0 ? (
|
||||
<p>No posts yet.</p>
|
||||
) : (
|
||||
<ul>
|
||||
{postsWithTags.map(({ post, tags }) => (
|
||||
<li>
|
||||
<a href={`/posts/${post.id}`}>
|
||||
{post.data.featured_image && (
|
||||
<Image image={post.data.featured_image} />
|
||||
)}
|
||||
<h2>{post.data.title}</h2>
|
||||
</a>
|
||||
{post.data.excerpt && <p>{post.data.excerpt}</p>}
|
||||
{tags.length > 0 && (
|
||||
<div>
|
||||
{tags.map((t) => (
|
||||
<a href={`/tag/${t.slug}`}>{t.label}</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
45
templates/starter/src/pages/tag/[slug].astro
Normal file
45
templates/starter/src/pages/tag/[slug].astro
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
import { getTerm, getEmDashCollection } from "emdash";
|
||||
import { Image } from "emdash/ui";
|
||||
import Base from "../../layouts/Base.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const term = slug ? await getTerm("tag", slug) : null;
|
||||
|
||||
if (!term) {
|
||||
return Astro.redirect("/404");
|
||||
}
|
||||
|
||||
const { entries: posts } = await getEmDashCollection("posts", {
|
||||
where: { tag: term.slug },
|
||||
orderBy: { published_at: "desc" },
|
||||
});
|
||||
---
|
||||
|
||||
<Base
|
||||
title={`Posts tagged "${term.label}"`}
|
||||
description={`Posts tagged ${term.label}`}
|
||||
>
|
||||
<h1>Tag: {term.label}</h1>
|
||||
<p>{posts.length} {posts.length === 1 ? "post" : "posts"}</p>
|
||||
|
||||
{
|
||||
posts.length === 0 ? (
|
||||
<p>No posts with this tag yet.</p>
|
||||
) : (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li>
|
||||
<a href={`/posts/${post.id}`}>
|
||||
{post.data.featured_image && (
|
||||
<Image image={post.data.featured_image} />
|
||||
)}
|
||||
<h2>{post.data.title}</h2>
|
||||
</a>
|
||||
{post.data.excerpt && <p>{post.data.excerpt}</p>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
Reference in New Issue
Block a user