面向 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("无法加载文章", { 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() 示例复制到沙盒包中。

下一步