タクソノミー

このページ

タクソノミーは、1 つ以上のコレクションに適用される名前付き分類です。EmDash は投稿向けに階層型の category タクソノミーとフラットな tag タクソノミーから始まります。サイトは genre、topic、difficulty などのタクソノミーも定義できます。

タームはタクソノミーに属します。「Guides」のようなカテゴリは子カテゴリを持てますが、タグや他のフラットなタクソノミーは 1 レベルです。

タームを管理する

EmDash 管理画面の Taxonomies からタクソノミーを開きます。

  1. Add Category、Add Tag、または現在のタクソノミーに対応する操作をクリックします。

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

  3. 任意の説明を追加して、タームを作成します。

  4. ターム一覧の移動コントロールを使って、現在の親グループ内での順序を設定します。

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

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

複数の投稿に 1 つのタームを追加する

編集者は、コレクション一覧で投稿を選択し、Add に続けてタクソノミーの単数形の名前(たとえば Add tag)をクリックするか、タクソノミーのページを開いて Add to posts をクリックし、公開されている投稿の URL(1 行に 1 つ、最大 50 件)を貼り付けます。コレクションに複数のタクソノミーが適用される場合、一覧には Add term と表示され、ダイアログでどのタクソノミーを使うかを尋ねられます。ダイアログで既存のタームを選ぶか新しく作成し、Review posts をクリックします。一致した各タイトルと言語を確認してから、更新される投稿数が表示されたボタンをクリックします。このサイトの公開済みの投稿と完全には一致しないリンクは、推測されずにフラグが付けられます。選択したタクソノミーを使用するコレクションのエントリのみが一致の対象になります。

タームの追加は、投稿に未公開の下書き編集が残っていても即座に反映されます。それらの編集が公開されることはありません。既存のタームはそのまま残り、すでにそのタームを持つ投稿はスキップされます。結果の一覧には、更新された投稿、スキップされた投稿、更新できなかった投稿が表示されます。書き込みの失敗には Retry failures を使用してください。URL の照合には、サイトに設定された公開オリジンとコレクションの URL パターン(日付や言語のパスを含む)が使われます。

カスタムタクソノミーを追加する

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

  1. Taxonomies を開き、New Taxonomy をクリックします。

  2. ラベルと安定した名前を入力します。名前は小文字で始まり、小文字、数字、アンダースコアのみを含みます。

  3. タームに親子関係が必要な場合は Hierarchical を有効にします。

  4. このタクソノミーを使用できるコレクションをすべて選択し、Create Taxonomy をクリックします。

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

テンプレートは安定した名前でクエリします。表示ラベルを変更しても、テンプレートを変更する必要はありません。

カスタムタクソノミーは、カテゴリやタグと同じクエリおよびフィルターのヘルパーを使います。次の例では、genre のタームを読み取り、そのスラッグのいずれかで書籍をフィルターします。

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 ヘルパーに通します。

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

---
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.rewrite("/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} と決めつけず、そのパターンからリンクを組み立てます。

エントリのタームを表示する

getEmDashEntry() と getEmDashCollection() は、割り当てられたタームを entry.data.terms にハイドレートします。一覧の各エントリに対して getEntryTerms() クエリを 1 回ずつ実行するのではなく、この値を読み取ってください。

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

---
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 は、どの行が同じタクソノミーまたは同じタームの翻訳であるかを記録します。コンテンツへの割り当てはこの共有されたアイデンティティを使うため、あるロケールで行った割り当ては、別のロケールに翻訳されたタームがあれば、そのタームに解決されます。

タクソノミーの定義では、フィールドがタクソノミーとロケールに振り分けられます。

フィールド帰属先効果
nameタクソノミー作成後は固定されます。すべてのロケールの定義が同じ名前を使います。
hierarchical、collectionsタクソノミーすべてのロケールで同じです。いずれかのロケールを通じてどちらかを変更すると、すべてのロケールで変更されます。
label、labelSingularロケール各ロケールの定義がそれぞれ独自の値を持ちます。

別のロケールにすでに存在する名前で作成された定義は、translationOf の有無にかかわらずそのタクソノミーに参加し、その hierarchical と collections を引き継ぎます。異なる値で作成すると失敗します。値を変えるには更新を使ってください。独自の定義を持たないロケールでも、そのタクソノミーのタームは一覧表示され、ラベルにはフォールバックチェーン上で定義を持つ最初のロケールのラベルが使われます。それがなければデフォルトロケールのラベル、それもなければロケールコードが最も小さいロケールのラベルが使われます。

タクソノミーページのロケールスイッチャーを使って、設定済みの各ロケールでタームを管理します。タームの編集ダイアログを開き、Translations パネルから別のロケールを追加または開きます。翻訳されたタームには、異なるスラッグとラベルを使えます。

タームの親と位置は、すべてのロケールで共有されます。親を指定せずに作成された翻訳は、そのタームの親と位置を引き継ぎます。別の親の下に翻訳を作成したり、いずれかのロケールを通じて親を変更したりすると、すべてのロケールでそのタームが移動します。

クエリヘルパーは、明示的なロケールが渡された場合はそれを使います。そうでなければ現在のリクエストのロケールを使い、次に設定されたデフォルトを使います。要求された翻訳が存在しない場合、単一のタームの検索は、設定されたフォールバックチェーンに従います。

ロケールのルーティングとフォールバックの設定については 国際化 (i18n) を、エントリの編集については コンテンツの操作 を参照してください。タクソノミーのクエリヘルパーは JavaScript API リファレンス に記載されています。プログラムから変更する場合は、Bearer トークンで認証し、状態を変更するすべてのリクエストに X-EmDash-Request: 1 を追加します。リクエストボディとレスポンスについては、タクソノミーエンドポイント を参照してください。