このページで分かること

  • タクソノミーとタームの関係、最初からある category(階層あり)と tag(階層なし)、管理画面でのタームの管理と独自のタクソノミーの追加
  • getTaxonomyTerms() によるターム一覧の取得と、タームごとの記事一覧(アーカイブページ)の作り方
  • エントリーに割り当てたタームの表示と、タクソノミー・タームの翻訳
難易度
実践
読む時間
4分
このページの目次

タクソノミー(Taxonomy)は、1つ以上のコレクションに適用する、名前の付いた分類です。EmDashには最初から、投稿(posts)用に、階層を持つ category タクソノミーと、階層のない tag タクソノミーがあります。サイトは genretopicdifficulty のようなタクソノミーを独自に定義することもできます。

ターム(Term)はタクソノミーに属します。「Guides」のようなカテゴリーは子カテゴリーを持てますが、タグなどの階層のないタクソノミーは1階層だけです。

タームの管理

EmDashの管理画面の「分類グループ」から、タクソノミーを開きます。

  1. 「Categoryを追加」、「Tagを追加」、または開いているタクソノミーの同様の操作をクリックします。

  2. ラベルとスラッグを入力します。階層のあるタクソノミーで、タームを別のタームの下に置く場合は、親を選びます。

  3. 必要に応じて説明を追加し、タームを作成します。

  4. ターム一覧の移動の操作を使い、同じ親のグループ内での順番を設定します。

編集者は、コンテンツのエントリーにあるタクソノミーのパネルからタームを割り当てます。どのコレクションにどのパネルを表示するかは、タクソノミーの定義で決まります。

タームを削除すると、コンテンツへの割り当ても削除されます。コンテンツのエントリーは削除されません。

本サイトの補足 やさしい解説

タクソノミーは、WordPressの「カテゴリー」と「タグ」にあたる仕組みです。EmDashにも最初から、親子関係を持てる category と、階層のない tag があります。管理画面では「分類グループ」というメニュー名で表示され、その中の1つ1つの項目(「Guides」など)がタームです。WordPressと同じく、タームを削除しても記事そのものは消えず、記事からそのタームの割り当てが外れるだけです。

独自のタクソノミーの追加

既存のコレクションに別の分類が必要になったら、タクソノミーを作成します。

  1. 「分類グループ」を開き、「分類グループを追加」をクリックします。

  2. ラベルと、変更しない前提の名前を入力します。名前は小文字の英字で始め、小文字の英字、数字、アンダースコアだけを使います。

  3. タームに親子関係が必要な場合は、「階層構造を使用する(カテゴリーのように親子関係を設定できます)」を有効にします。

  4. そのタクソノミーを使えるコレクションをすべて選択し、「分類グループを作成」をクリックします。

  5. 最初のタームを追加し、コンテンツに割り当てます。

テンプレートは、この変更しない名前でクエリします。表示用のラベルを変えても、テンプレートを変更する必要はありません。

独自のタクソノミーも、カテゴリーやタグと同じクエリ用・絞り込み用のヘルパーを使います。次の例は、genre のタームを読み込み、そのうち1つのスラッグで書籍(books)を絞り込みます。

import { getEmDashCollection, getTaxonomyTerms } from "emdash";

const genres = await getTaxonomyTerms("genre", { includeCounts: false });
const { entries: scienceFictionBooks } = await getEmDashCollection("books", {
  where: { genre: "science-fiction" },
});
本サイトの補足 やさしい解説

WordPressでは、独自の分類を追加するのにプラグインや register_taxonomy() のコードが必要でした。EmDashでは管理画面の「分類グループ」から作成でき、使うコレクションもそこで選びます。ここで入力する「名前」は、テンプレートのコードから呼び出すときの識別子です。あとから表示用のラベルは変えられますが、名前はテンプレートが参照し続けるため、最初に決めた名前を使い続けます。

ターム一覧の取得

タクソノミーの索引ページ、ナビゲーションの一覧、絞り込みの選択肢を描画するには、getTaxonomyTerms() を使います。階層のあるタクソノミーは、各タームの children 配列を通じてツリーを返します。

タームの件数は初期状態で含まれ、そのタクソノミーを割り当てたコレクション全体での集計が必要になります。コンポーネントが件数を表示しない場合は、この処理を省きます。

次のコンポーネントは、件数なしでカテゴリーのリンクを描画します。

src/components/CategoryList.astro
---
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ヘルパーに通します。

次のルートは、1つのカテゴリーに属する公開済みの投稿を一覧にします。

src/pages/category/[slug].astro
---
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 は、タクソノミー名をキー、タームのスラッグを値にします。クエリの並び順の識別子には published_at のようなデータベースのフィールド名を使います。エントリーのデータでは、対応する値が publishedAt として公開されます。

postHref() には、そのコレクションの実際の公開ルートを使います。コレクションが独自の urlPattern を使っている場合は、/posts/{slug} を前提にせず、そのパターンからリンクを組み立てます。

本サイトの補足 やさしい解説

WordPressでは、カテゴリーごとの記事一覧(アーカイブページ)をテーマの category.php などが自動で表示していました。EmDashでは、このページの例のように、Astroのページファイル(src/pages/category/[slug].astro)を自分で用意します。ページはURLからスラッグを取り出して getTerm() でタームを探し、見つかれば getEmDashCollection()where でそのタームの記事だけを取得します。タームが見つからない場合は /404 に移動します。

エントリーのタームの表示

getEmDashEntry()getEmDashCollection() は、割り当てられたタームを entry.data.terms に読み込みます。一覧のエントリーごとに getEntryTerms() のクエリを1回ずつ実行するのではなく、この値を読みます。

次のコンポーネントは、投稿と一緒に読み込み済みのカテゴリーとタグを描画します。

src/components/PostTerms.astro
---
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() を使います。

タクソノミーとタームの翻訳

タクソノミーの定義とタームは、ロケールごとに1行ずつ持ちます。EmDashは、どの行が同じタクソノミーやタームの翻訳なのかを記録します。コンテンツへの割り当てはこの共通のIDを使うため、あるロケールで行った割り当ては、別のロケールに翻訳済みのタームがあれば、そのタームに解決されます。

各ロケールのタームを管理するには、タクソノミーのページのロケール切り替えを使います。タームの編集ダイアログを開き、「翻訳」パネルから別のロケールを追加するか開きます。翻訳したタームは、別のスラッグとラベルを使えます。

クエリ用のヘルパーは、ロケールが明示的に指定されていればそれを使います。指定がなければ、現在のリクエストのロケールを使い、次に設定された既定のロケールを使います。1つのタームを検索する場合、要求された翻訳がなければ、設定されたフォールバックの順番に従います。

ロケールのルーティングとフォールバックの設定は多言語対応を、エントリーの編集はコンテンツの操作を参照してください。タクソノミーのクエリ用ヘルパーはランタイムAPIリファレンスで説明しています。プログラムから変更する場合は、Bearerトークンで認証し、状態を変更するすべてのリクエストに X-EmDash-Request: 1 を付けます。リクエストの本文とレスポンスはタクソノミーのエンドポイントを参照してください。