JavaScript API 参考

本页内容

EmDash 导出用于查询内容以及处理预览、设置、菜单、分类法、小部件区域、区块和搜索的函数。

内容查询

EmDash 的查询函数遵循 Astro 的实时内容集合模式,返回 { entries, error }{ entry, error } 以实现优雅的错误处理。

getEmDashCollection()

获取集合中的所有条目。以下示例加载所有文章并检查错误:

import { getEmDashCollection } from "emdash";

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) {
	console.error("加载文章失败:", error);
}

参数

参数类型描述
collectionstring集合标识符
optionsCollectionFilter可选的过滤选项

选项

options 参数接受以下过滤器:

interface CollectionFilter {
	status?: "draft" | "published" | "archived";
	limit?: number;
	cursor?: string; // 键集分页 — 传递前一个 `nextCursor`
	offset?: number; // 偏移分页 — 跳过 N 个条目(与 `limit` 配合使用)
	where?: Record<string, string | string[]>; // 按字段或分类法过滤
}

返回值

函数解析为 CollectionResult

interface CollectionResult<T> {
	entries: ContentEntry<T>[]; // 出错或无结果时为空数组
	error?: Error; // 查询失败时设置
	nextCursor?: string; // 下一个键集页面的游标(如果有)
	hasMore?: boolean; // 此页面之后是否还有更多条目(当设置了 `limit` 时)
}

示例

以下示例按状态和分类法过滤、限制结果并处理错误:

// 获取所有已发布的文章
const { entries: posts } = await getEmDashCollection("posts", {
	status: "published",
});

// 获取最新的 5 篇文章
const { entries: latest } = await getEmDashCollection("posts", {
	limit: 5,
	status: "published",
});

// 按分类法过滤
const { entries: newsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { category: "news" },
});

// 带编号的归档页面(例如 /page/3)使用偏移分页
const perPage = 20;
const page = Number(Astro.params.page ?? 1);
const { entries: pagePosts, hasMore } = await getEmDashCollection("posts", {
	status: "published",
	limit: perPage,
	offset: (page - 1) * perPage,
	orderBy: { published_at: "desc" },
});

// 处理错误
const { entries, error } = await getEmDashCollection("posts");
if (error) {
	return new Response("服务器错误", { status: 500 });
}

getEmDashEntry()

通过标识符或 ID 获取单个条目。以下示例加载一篇文章,如果不存在则重定向:

import { getEmDashEntry } from "emdash";

const { entry: post, error } = await getEmDashEntry("posts", "my-post-slug");

if (!post) {
	return Astro.redirect("/404");
}

参数

参数类型描述
collectionstring集合标识符
slugOrIdstring条目标识符或 ID
options{ locale?: string }可选。用于标识符解析的语言环境

预览模式自动处理 — 中间件检测 _preview 令牌并通过 AsyncLocalStorage 提供草稿内容。可选的 options 参数仅接受用于标识符解析的 locale;预览状态不需要参数。

返回值

函数解析为 EntryResult

interface EntryResult<T> {
	entry: ContentEntry<T> | null; // 未找到时为 null
	error?: Error; // 仅在实际错误时设置,"未找到"不设置
	isPreview: boolean; // 提供草稿内容时为 true
}

示例

以下示例通过标识符和 ID 获取、读取预览状态,并区分错误和未找到:

// 通过标识符获取
const { entry: post } = await getEmDashEntry("posts", "hello-world");

// 通过 ID 获取
const { entry: post } = await getEmDashEntry("posts", "01HXK5MZSN0FVXT2Q3KPRT9M7D");

// 预览是自动的 — 存在有效的 _preview 令牌时 isPreview 为 true
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);

// 处理错误 vs 未找到
if (error) {
	return new Response("服务器错误", { status: 500 });
}
if (!entry) {
	return Astro.redirect("/404");
}

内容类型

ContentEntry

查询函数以以下形式返回条目:

interface ContentEntry<T = Record<string, unknown>> {
	id: string;
	data: T;
	edit: EditProxy; // 可视化编辑注解
}

edit 代理提供可视化编辑注解。将其展开到元素上以启用内联编辑:{...entry.edit.title}。在生产环境中,这不会产生输出。

data 对象包含所有内容字段和系统字段:

  • id - 唯一标识符
  • slug - URL 友好的标识符
  • status - “draft” | “published” | “archived”
  • createdAt - ISO 时间戳
  • updatedAt - ISO 时间戳
  • publishedAt - ISO 时间戳或 null
  • 加上集合模式中定义的所有自定义字段

预览系统

generatePreviewToken()

为草稿内容生成预览令牌。以下示例创建一个一小时后过期的令牌:

import { generatePreviewToken } from "emdash";

const token = await generatePreviewToken({
	contentId: "posts:01HXK5MZSN...",
	secret: process.env.EMDASH_ADMIN_SECRET,
	expiresIn: 3600, // 1 小时
});

verifyPreviewToken()

验证预览令牌并读取其有效载荷:

import { verifyPreviewToken } from "emdash";

const result = await verifyPreviewToken({
	token,
	secret: process.env.EMDASH_ADMIN_SECRET,
});

if (result.valid) {
	const { cid, exp, iat } = result.payload;
	// cid 格式为 "collection:id",例如 "posts:my-draft-post"
}

isPreviewRequest()

检查请求是否包含预览令牌,然后读取:

import { isPreviewRequest, getPreviewToken } from "emdash";

if (isPreviewRequest(Astro.url)) {
	const token = getPreviewToken(Astro.url);
	// 验证并显示预览内容
}

内容转换器

在 Portable Text 和 ProseMirror 格式之间转换:

import { prosemirrorToPortableText, portableTextToProsemirror } from "emdash";

// 从 ProseMirror(编辑器)到 Portable Text(存储)
const portableText = prosemirrorToPortableText(prosemirrorDoc);

// 从 Portable Text 到 ProseMirror
const prosemirrorDoc = portableTextToProsemirror(portableText);

站点设置

使用 getSiteSettingsgetSiteSetting 读取站点全局设置:

import { getSiteSettings, getSiteSetting } from "emdash";

// 获取所有设置
const settings = await getSiteSettings();

// 获取单个设置
const title = await getSiteSetting("title");

设置从运行时 API 是只读的。使用管理 API 来更新它们。

菜单

获取导航菜单并遍历其项目,包括嵌套子项:

import { getMenu, getMenus } from "emdash";

// 获取所有菜单
const menus = await getMenus();

// 获取特定菜单及其项目
const primaryMenu = await getMenu("primary");

if (primaryMenu) {
	primaryMenu.items.forEach(item => {
		console.log(item.label, item.url);
		// 下拉菜单的嵌套项目
		item.children.forEach(child => console.log("  -", child.label));
	});
}

分类法

获取分类法术语、单个术语、条目的术语或按术语获取条目:

import { getTaxonomyTerms, getTerm, getEntryTerms, getEntriesByTerm } from "emdash";

// 获取分类法的所有术语(层级的为树结构)
const categories = await getTaxonomyTerms("category");

// 获取单个术语
const news = await getTerm("category", "news");

// 获取分配给内容条目的术语
const postCategories = await getEntryTerms("posts", "post-123", "category");

// 获取具有特定术语的条目
const newsPosts = await getEntriesByTerm("posts", "category", "news");

小部件区域

获取小部件区域及其包含的小部件:

import { getWidgetArea, getWidgetAreas } from "emdash";

// 获取所有小部件区域
const areas = await getWidgetAreas();

// 获取特定小部件区域及其小部件
const sidebar = await getWidgetArea("sidebar");

if (sidebar) {
	sidebar.widgets.forEach(widget => {
		console.log(widget.type, widget.title);
	});
}

区块

获取区块并过滤:

import { getSection, getSections } from "emdash";

// 获取所有区块(分页)
const { items, nextCursor } = await getSections();

// 过滤区块
const { items: themeSections } = await getSections({ source: "theme" });
const { items: results } = await getSections({ search: "newsletter" });

// 通过标识符获取单个区块
const cta = await getSection("newsletter-cta");

getSections(options?) 返回 { items: Section[]; nextCursor?: string }。选项为 source"theme" | "user" | "import")、searchlimit(默认 50,最大 100)和 cursor

搜索

跨集合运行全局搜索。结果包含高亮的摘要片段:

import { search } from "emdash";

const results = await search("hello world", {
	collections: ["posts", "pages"],
	status: "published",
	limit: 20,
});

// search() 解析为 { items, nextCursor? }
results.items.forEach(result => {
	console.log(result.title);
	console.log(result.snippet); // 包含 <mark> 标签
	console.log(result.score);
});

// 分页:将前一个 nextCursor 作为 `cursor` 传递以获取下一页。
// 没有更多结果时 nextCursor 为 undefined。
if (results.nextCursor) {
	const next = await search("hello world", {
		collections: ["posts", "pages"],
		limit: 20,
		cursor: results.nextCursor,
	});
}

错误处理

EmDash 导出错误类用于处理特定故障。以下示例捕获验证和模式错误:

import {
  EmDashDatabaseError,
  EmDashValidationError,
  EmDashStorageError,
  SchemaError,
} from "emdash";

try {
  await repo.create({ ... });
} catch (error) {
  if (error instanceof EmDashValidationError) {
    console.error("验证失败:", error.message);
  }
  if (error instanceof SchemaError) {
    console.error("模式错误:", error.code, error.details);
  }
}