EmDash 为 Astro 站点添加管理应用、数据库支持的集合、媒体、菜单、分类法、设置、修订和预览功能。页面和组件仍然是普通的 Astro 文件。
EmDash 添加了什么
| 功能 | 提供的内容 |
|---|---|
| 管理后台 | 在 /_emdash/admin 进行基于浏览器的集合、媒体、菜单、分类法和设置管理 |
| 数据库集合 | 在请求时查询的编辑器管理内容 |
| 媒体库 | 具有模板媒体字段值的存储图片和文件 |
| 草稿、修订和预览 | 发布前的编辑工作 |
| 菜单和小部件区域 | 条目字段之外的有序、可编辑站点区域 |
| 站点设置 | 共享的身份和显示值,如标题、标语、徽标和分页大小 |
| 插件 | 钩子、路由、存储和可选的管理扩展 |
这些功能与 Astro 共存,而非替代它。Astro 仍然控制路由、布局、渲染、样式和部署适配器。
EmDash 和 Astro 集合
Astro 内容集合和 EmDash 集合可以共存。对仓库拥有的内容使用 Astro 集合,对通过 /_emdash/admin 管理的内容使用 EmDash。
| Astro 内容集合 | EmDash 集合 | |
|---|---|---|
| 存储 | 项目中的文件 | SQL 数据库 |
| 编辑 | 仓库工作流 | EmDash 管理后台 |
| 查询 | getCollection() | getEmDashCollection() |
| 富文本 | Markdown 或 MDX | Portable Text |
| 交付 | 构建时或实时加载器 | 运行时实时加载器 |
当所有权不同时使用两个集合系统。例如,产品站点可以将开发者撰写的发行说明保存在 Astro 内容集合中,将编辑器撰写的文章保存在 EmDash 中:
---
import { getCollection } from "astro:content";
import { getEmDashCollection } from "emdash";
const [releaseNotes, { entries: articles }] = await Promise.all([
getCollection("releases"),
getEmDashCollection("articles", { limit: 3 }),
]);
---
两个结果保持分离。EmDash 不会将基于文件的条目复制到其数据库中。
配置站点
当前的 Node 模板将 Astro 配置为服务器输出,添加 EmDash 集成,并使用 SQLite 和本地存储适配器。
以下简化配置包含这些必需部分:
import node from "@astrojs/node";
import react from "@astrojs/react";
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
output: "server",
adapter: node({ mode: "standalone" }),
integrations: [
react(),
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
EmDash 还提供了为 D1 和 R2 配置的 Cloudflare 模板。请从部署目标的模板开始,而不是手动转换 Node 适配器。
注册实时集合
模板通过一个名为 _emdash 的 Astro 实时集合公开 EmDash 内容:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({ loader: emdashLoader() }),
};
getEmDashCollection() 和 getEmDashEntry() 通过此加载器选择请求的内容类型。
查询集合
以下查询读取最近发布的文章。orderBy 使用存储的字段名,并将每个名称映射到 "asc" 或 "desc":
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
orderBy: { published_at: "desc" },
limit: 10,
});
if (error) return new Response("无法加载文章", { status: 500 });
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
{posts.map((post) => (
<article>
<h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
</article>
))}
匿名查询返回已发布内容。显式 status 过滤器在经过身份验证或支持预览的代码中很有用。where 接受内容字段和分类法名称;完整的过滤和分页形式请参见查询内容。
查询单个条目
将 slug 或数据库 ID 传递给 getEmDashEntry()。以下路由使用其 URL slug:
---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");
const { entry: post, error, cacheHint } = await getEmDashEntry("posts", slug);
if (error) return new Response("无法加载文章", { status: 500 });
if (!post) return Astro.redirect("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
返回的 entry.id 是 Astro 的路由标识符,通常是 slug。数据库内容 ID 是 entry.data.id。在需要存储内容 ID 的辅助函数中使用 data.id。
使用动态 CMS 功能
EmDash 导出服务器辅助函数,用于不属于单个集合条目的数据:
---
import { getMenu, getSiteSettings } from "emdash";
import { WidgetArea } from "emdash/ui";
const [menu, settings] = await Promise.all([
getMenu("primary"),
getSiteSettings(),
]);
---
<header>
<a href="/">{settings.title}</a>
<nav>
{menu?.items.map((item) => <a href={item.url}>{item.label}</a>)}
</nav>
</header>
<main><slot /></main>
<aside><WidgetArea name="sidebar" /></aside>
选择插件格式
沙盒插件和原生插件有不同的包结构。沙盒插件使用 emdash-plugin.jsonc 加上默认导出的 src/plugin.ts 对象。原生插件导出描述符工厂和使用 definePlugin() 构建的 createPlugin()。
添加插件前请阅读选择插件格式。不要将原生 definePlugin() 示例复制到沙盒包中。