タクソノミー

このページ

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

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

タームを管理する

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

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

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

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

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

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

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

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

編集者はコレクション一覧で投稿を選択して Add tag をクリックするか、Tags を開いて Add to posts をクリックし、公開投稿 URL を貼り付けられます(1 行に 1 つ、最大 50)。既存のタグを選ぶかダイアログで作成し、Review posts をクリックします。一致したタイトルと言語をすべて確認し、何件の投稿がタグ付けされるかを示すボタンをクリックします。このサイトの公開投稿と完全一致しないリンクは推測されずフラグされます。

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

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

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

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

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

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

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

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

テンプレートは安定した名前をクエリします。表示ラベルを変更してもテンプレート変更は不要です。

カスタムタクソノミーは、カテゴリとタグと同じクエリ・フィルターヘルパーを使います。次の例は genre タームを読み、スラッグの 1 つで本をフィルタします。

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.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} を仮定せずそのパターンからリンクを組み立てます。

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

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 パネルで別のロケールを追加または開きます。翻訳タームは異なるスラッグとラベルを使えます。

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

クエリヘルパーは、指定があれば明示的なロケールを使います。なければ現在のリクエストロケール、次に設定済みデフォルトを使います。単一タームのルックアップは、要求された翻訳がない場合、設定済みフォールバックチェーンに従います。

ロケールルーティングとフォールバック設定は Internationalization、エントリ編集は Working with Content を参照してください。ランタイム API リファレンス はタクソノミークエリヘルパーを文書化しています。プログラムによる変更では、Bearer トークンで認証し、状態を変えるすべてのリクエストに X-EmDash-Request: 1 を追加します。リクエスト本体とレスポンスは タクソノミーエンドポイント を参照してください。