分类法是应用于一个或多个集合的命名分类。EmDash 从用于文章的层级 category 分类法和平坦的 tag 分类法开始。站点也可以定义诸如 genre、topic 或 difficulty 的分类法。
术语属于某个分类法。像「Guides」这样的分类可以有子分类,而标签和其他平坦分类法只有一层。
管理术语
在 EmDash 管理中从 Taxonomies 打开一个分类法。
-
点击 Add Category、Add Tag,或当前分类法的等效操作。
-
输入标签和 slug。对于层级分类法,当术语属于另一个术语之下时,选择父级。
-
添加可选描述,然后创建术语。
-
使用术语列表中的移动控件,在当前父组内设置顺序。
编辑者从内容条目中的分类法面板分配术语。分类法定义控制哪些集合显示每个面板。
删除术语会移除其在内容上的分配。不会删除内容条目本身。
向多篇文章添加一个标签
编辑者可以在集合列表中选择文章并点击 Add tag,或打开 Tags 并点击 Add to posts 粘贴公开文章 URL(每行一个,最多 50)。选择现有标签或在对话框中创建,然后点击 Review posts。检查每个匹配的标题和语言,然后点击显示将标记多少篇文章的按钮。与本站已发布文章不完全匹配的链接会被标记,而不是猜测。
添加标签会立即生效,即使文章还有其他未发布的草稿编辑;它不会发布那些编辑。现有标签保持不变,已有该标签的文章会被跳过。结果列表显示哪些文章被标记、跳过或无法标记;对写入失败使用 Retry failures。URL 匹配使用站点配置的公开源和集合 URL 模式,包括日期和语言路径。
添加自定义分类法
当现有集合需要单独分类时,创建分类法。
-
打开 Taxonomies 并点击 New Taxonomy。
-
输入标签和稳定名称。名称以小写字母开头,仅包含小写字母、数字和下划线。
-
若术语需要父子关系,启用 Hierarchical。
-
选择可以使用该分类法的每个集合,然后点击 Create Taxonomy。
-
添加初始术语并将它们分配给内容。
模板查询稳定名称。更改显示标签不需要更改模板。
自定义分类法使用与分类和标签相同的查询与过滤辅助函数。以下示例读取 genre 术语,并按其中一个 slug 过滤书籍:
import { getEmDashCollection, getTaxonomyTerms } from "emdash";
const genres = await getTaxonomyTerms("genre", { includeCounts: false });
const { entries: scienceFictionBooks } = await getEmDashCollection("books", {
where: { genre: "science-fiction" },
});
删除分类法
在管理中打开分类法,然后从页眉的操作菜单中选择 Delete taxonomy 并确认。该操作需要编辑者和管理员持有的 taxonomies:manage 权限。
删除分类法会删除其在每种语言中的术语,并从归入其下的内容中移除这些术语。内容条目本身会保留。
查询术语列表
使用 getTaxonomyTerms() 渲染分类法索引、导航列表或过滤器集。层级分类法通过每个术语的 children 数组返回树。
术语计数默认包含,并需要对分类法已分配集合进行聚合。当组件不显示计数时,跳过该工作。
以下组件在无计数的情况下渲染分类链接:
---
import { getTaxonomyTerms } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";
const locale = Astro.currentLocale;
const categories = await getTaxonomyTerms("category", {
locale,
includeCounts: false,
});
function categoryHref(slug: string) {
const path = `/category/${slug}`;
return locale ? getRelativeLocaleUrl(locale, path) : path;
}
---
<nav aria-label="Categories">
<ul>
{categories.map((category) => (
<li>
<a href={categoryHref(category.slug)}>{category.label}</a>
{category.children.length > 0 && (
<ul>
{category.children.map((child) => (
<li><a href={categoryHref(child.slug)}>{child.label}</a></li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
当组件显示使用情况时,省略 includeCounts: false 并渲染 term.count。计数包含查询所用语言环境中公开可见的条目。
构建分类法归档
在查找术语之前解码动态路由参数。用相同的语言环境查询术语和内容,并将生成的路径通过 Astro 的语言环境 URL 辅助函数。
以下路由列出一个分类中的已发布文章:
---
import { decodeSlug, getEmDashCollection, getTerm } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";
import Base from "../../layouts/Base.astro";
const locale = Astro.currentLocale;
const slug = decodeSlug(Astro.params.slug);
const category = slug
? await getTerm("category", slug, { locale, includeCounts: false })
: null;
if (!category) {
return Astro.redirect("/404");
}
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
locale,
where: { category: category.slug },
orderBy: { published_at: "desc" },
});
function postHref(postSlug: string) {
const path = `/posts/${postSlug}`;
return locale ? getRelativeLocaleUrl(locale, path) : path;
}
---
<Base title={`${category.label} posts`}>
<h1>{category.label}</h1>
{category.description && <p>{category.description}</p>}
{posts.length > 0 ? (
<ul>
{posts.map((post) => (
post.data.slug && (
<li>
<a href={postHref(post.data.slug)}>{post.data.title}</a>
</li>
)
))}
</ul>
) : (
<p>No posts in this category.</p>
)}
</Base>
where 使用分类法名称作为键,术语 slug 作为值。查询排序标识符使用数据库字段名,如 published_at;条目数据将对应值公开为 publishedAt。
在 postHref() 中使用集合的实际公开路由。若集合使用自定义 urlPattern,请从该模式构建链接,而不是假设 /posts/{slug}。
显示条目的术语
getEmDashEntry() 和 getEmDashCollection() 将已分配术语水合到 entry.data.terms。读取该值,而不是对列表中的每个条目运行一次 getEntryTerms() 查询。
以下组件渲染已随文章加载的分类和标签:
---
import type { ContentEntry, InferCollectionData } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";
interface Props {
post: ContentEntry<InferCollectionData<"posts">>;
}
const { post } = Astro.props;
const locale = Astro.currentLocale;
const categories = post.data.terms?.category ?? [];
const tags = post.data.terms?.tag ?? [];
function termHref(taxonomy: string, slug: string) {
const path = `/${taxonomy}/${slug}`;
return locale ? getRelativeLocaleUrl(locale, path) : path;
}
---
{categories.length > 0 && (
<ul aria-label="Categories">
{categories.map((category) => (
<li>
<a href={termHref("category", category.slug)}>{category.label}</a>
</li>
))}
</ul>
)}
{tags.length > 0 && (
<ul aria-label="Tags">
{tags.map((tag) => (
<li>
<a href={termHref("tag", tag.slug)}>{tag.label}</a>
</li>
))}
</ul>
)}
当你只有集合名称和条目 ID 时,使用 getEntryTerms()。当若干条目未被内容查询水合时,使用 getTermsForEntries() 批量获取术语。
翻译分类法与术语
分类法定义和术语每种语言环境有一行。EmDash 记录哪些行是同一分类法或术语的翻译。内容分配使用该共享身份,因此在一种语言环境中做出的分配,在存在翻译术语时会解析到另一种语言环境中的翻译术语。
分类法定义将其字段在分类法与各语言环境之间分配:
| 字段 | 属于 | 效果 |
|---|---|---|
name | 分类法 | 创建后固定。每种语言环境的定义使用相同名称。 |
hierarchical、collections | 分类法 | 每种语言环境相同。通过任一语言环境更改任一者,会对所有语言环境生效。 |
label、labelSingular | 语言环境 | 每种语言环境的定义有自己的。 |
为已在另一语言环境中存在的名称创建的定义会加入该分类法(无论有无 translationOf),并采用其 hierarchical 和 collections。用不同值创建会失败;请通过更新更改它们。没有自己定义的语言环境仍会列出分类法的术语,并显示其回退链上第一个有标签的语言环境的标签,否则默认语言环境的标签,否则语言环境代码最低的语言环境的标签。
使用分类法页面上的语言环境切换器,在每个已配置语言环境中管理术语。打开术语的编辑对话框,并使用其 Translations 面板添加或打开另一种语言环境。翻译术语可以使用不同的 slug 和标签。
术语的父级和位置由每种语言环境共享。在没有父级的情况下创建的翻译会采用其术语的父级和位置。在不同父级下创建翻译,或通过任一语言环境更改父级,会在每种语言环境中移动该术语。
查询辅助函数在提供时使用显式语言环境。否则使用当前请求语言环境,然后是配置的默认值。当请求的翻译不存在时,单术语查找遵循配置的回退链。
有关语言环境路由与回退配置,请参见 Internationalization;有关编辑条目,请参见 Working with Content。运行时 API 参考 记录了分类法查询辅助函数。对于程序化更改,使用 Bearer 令牌进行身份验证,并向每个更改状态的请求添加 X-EmDash-Request: 1。请求体与响应请参见 分类法端点。