分類法

本頁內容

分類法是套用到一個或多個集合的命名分類。EmDash 從用於文章的階層 category 分類法和平坦的 tag 分類法開始。站台也可以定義諸如 genre、topic 或 difficulty 的分類法。

術語屬於某個分類法。像「Guides」這樣的分類可以有子分類,而標籤和其他平坦分類法只有一層。

管理術語

在 EmDash 管理後台中從 Taxonomies 開啟一個分類法。

  1. 點擊 Add Category、Add Tag,或目前分類法的對應操作。

  2. 輸入標籤和 slug。對於階層分類法,當術語屬於另一個術語之下時,選擇父層。

  3. 新增選用的描述,然後建立術語。

  4. 使用術語列表中的移動控制項,在目前父層群組內設定順序。

編輯者從內容條目中的分類法面板指派術語。分類法定義控制哪些集合顯示每個面板。

刪除術語會移除其在內容上的指派。不會刪除內容條目本身。

向多篇文章新增一個術語

編輯者可以在集合列表中選取文章,然後點擊 Add 加上該分類法的單數名稱(例如 Add tag),或開啟某個分類法的頁面並點擊 Add to posts 貼上公開文章 URL(每行一個,最多 50 個)。當集合適用多個分類法時,列表會顯示 Add term,對話方塊會詢問要使用哪個分類法。選擇現有術語或在對話方塊中建立一個,然後點擊 Review posts。檢查每個相符的標題和語言,然後點擊顯示將更新多少篇文章的按鈕。與本站台已發佈文章不完全相符的連結會被標記,而不是猜測。只有使用所選分類法的集合中的條目才能被比對。

新增術語會立即生效,即使文章還有其他未發佈的草稿編輯;它不會發佈那些編輯。現有術語保持不變,已有該術語的文章會被略過。結果列表會顯示哪些文章已更新、被略過或無法更新;對寫入失敗請使用 Retry failures。URL 比對使用站台設定的公開來源和集合 URL 模式,包括日期和語言路徑。

新增自訂分類法

當現有集合需要獨立的分類時,建立分類法。

  1. 開啟 Taxonomies 並點擊 New Taxonomy。

  2. 輸入標籤和穩定名稱。名稱以小寫字母開頭,僅包含小寫字母、數字和底線。

  3. 若術語需要父子關係,啟用 Hierarchical。

  4. 選取可以使用該分類法的每個集合,然後點擊 Create Taxonomy。

  5. 新增初始術語並將它們指派給內容。

範本查詢穩定名稱。變更顯示標籤不需要變更範本。

自訂分類法使用與分類和標籤相同的查詢與篩選輔助函式。以下範例讀取 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.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 以分類法名稱為鍵、術語 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 和標籤。

術語的父層和位置由所有語言環境共用。未指定父層而建立的翻譯,會採用其術語的父層和位置。在不同的父層下建立翻譯,或透過任一語言環境變更父層,會在所有語言環境中移動該術語。

查詢輔助函式在提供明確的語言環境時使用該語言環境。否則它們使用目前請求的語言環境,再退回到設定的預設值。請求的翻譯不存在時,單一術語的查找會遵循設定的後備鏈。

有關語言環境路由與後備設定,請參閱國際化 (i18n);有關編輯條目,請參閱處理內容。JavaScript API 參考記錄了分類法查詢輔助函式。對於程式化變更,請使用 Bearer 權杖進行驗證,並向每個會變更狀態的請求加入 X-EmDash-Request: 1。請求本文與回應請參閱分類法端點。