面向 Astro 开发者的 EmDash

本页内容

EmDash 为 Astro 站点添加了管理应用、由数据库支持的集合、媒体、菜单、分类法、设置、修订和预览。页面和组件仍然是普通的 Astro 文件。

EmDash 添加了什么

功能提供的能力
管理后台在 /_emdash/admin 通过浏览器管理集合、媒体、菜单、分类法和设置
数据库集合在请求时查询的、由编辑者管理的内容
媒体库已存储的图片和文件,并为模板提供媒体字段值
草稿、修订和预览发布之前的编辑工作
菜单和小部件区域位于条目字段之外的、有序且可编辑的站点区域
站点设置共享的标识和显示值,例如标题、标语、徽标和分页大小
插件钩子、路由、存储和可选的管理后台扩展

这些功能与 Astro 并存,而不是取代它。路由、布局、渲染、样式和部署适配器仍然由 Astro 控制。

EmDash 和 Astro 集合

Astro 内容集合和 EmDash 集合可以共存。仓库所有的内容使用 Astro 集合,通过 /_emdash/admin 管理的内容使用 EmDash。

Astro 内容集合EmDash 集合
存储项目中的文件SQL 数据库
编辑仓库工作流EmDash 管理后台
查询getCollection()getEmDashCollection()
富文本Markdown 或 MDXPortable 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("Could not load posts", { 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.rewrite("/404");

const { entry: post, error, cacheHint } = await getEmDashEntry("posts", slug);
if (error) return new Response("Could not load the post", { status: 500 });
if (!post) return Astro.rewrite("/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() 示例复制到沙箱包中。

下一步