查詢內容

本頁內容

EmDash 頁面在請求時使用 getEmDashCollection() 和 getEmDashEntry() 讀取內容。前者傳回清單,後者按 slug 或內容 ID 傳回一筆項目。兩者都將錯誤作為資料傳回,以便頁面決定如何回應。

綁定範本使用 Astro 的伺服器輸出。因此,編輯者發佈後,造訪者會在下一次轉譯時收到最新已發佈修訂。已儲存的草稿在發佈或透過有效預覽 URL 請求之前保持私密。

查詢集合

以下頁面載入最近發佈的七篇文章。公開集合查詢預設 status: "published",因此篩選器無需重複。

---
import { getEmDashCollection } from "emdash";

const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
  orderBy: { published_at: "desc" },
  limit: 7,
});

if (error) {
  console.error("Failed to load posts:", error);
  return new Response("Unable to load posts", { status: 500 });
}

if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

<h1>Posts</h1>
<ul>
  {posts.map((post) => (
    <li>
      <a href={`/posts/${post.id}`}>{post.data.title}</a>
    </li>
  ))}
</ul>

排序與限制在 EmDash 水合項目之前於資料庫中完成。這避免了載入整個集合並在頁面中排序或切片。

結果包含:

  • entries,在無符合或查詢失敗時為空陣列;
  • error,對失敗的查詢設定,但不對空結果設定;
  • cacheHint,攜帶 Astro 快取的標籤與最後修改時間;
  • nextCursor,當有限的游標頁還有更多項目時設定;以及
  • hasMore,回報有限的游標或偏移頁是否還有下一頁。

項目識別碼

每個結果有兩個職責不同的識別碼:

  • entry.id 是內容載入器產生的面向 URL 的識別碼。通常是 slug。當國際化為地區設定加前綴時,會包含前綴。從集合結果建立連結時使用此值。
  • entry.data.id 是資料庫中儲存的穩定內容 ID。編輯者變更 slug 時它不會改變。當 API、分類法助手、頁面情境或關係期望內容 ID 時使用它。

getEmDashEntry() 接受 slug 或穩定內容 ID。啟用國際化時,slug 查詢限定在某個地區設定;內容 ID 直接識別該列。

篩選集合

在第二個參數中傳遞篩選器。以下查詢傳回 news 分類中 series 欄位為 engineering 的已發佈文章:

const { entries } = await getEmDashCollection("posts", {
  where: {
    category: "news",
    series: "engineering",
  },
});

命名分類法的鍵符合已指派的術語 slug。其他鍵符合集合欄位。多個鍵以 AND 邏輯組合。一個鍵上的陣列符合任一列出的值,因此 { category: ["news", "updates"] } 符合任一分類。

使用 locale 明確請求一種語言:

const { entries: frenchPosts } = await getEmDashCollection("posts", {
  locale: "fr",
  orderBy: { published_at: "desc" },
});

若省略 locale,EmDash 使用請求地區設定,然後使用設定的預設地區設定。回退規則與翻譯路由見國際化。

status 接受 "published"、"draft" 或 "archived"。不要從公開路由請求草稿。當造訪者需要暫時存取未發佈項目時,使用預覽流程。

在資料庫中排序結果

orderBy 將欄位名稱對應到 "asc" 或 "desc"。使用儲存的欄位名稱,而非 entry.data 中傳回的 camelCase 名稱:

const { entries } = await getEmDashCollection("posts", {
  orderBy: {
    published_at: "desc",
    title: "asc",
  },
});

系統欄使用其資料庫名稱,如 created_at、updated_at 與 published_at。自訂欄位使用其集合 slug,如 title 或 priority。傳回的資料將系統日期對應到 createdAt、updatedAt 與 publishedAt,但這些 camelCase 屬性名稱不是有效的 orderBy 欄位。

EmDash 使用第一個有效的 orderBy 欄位作為分頁鍵,並以內容 ID 作為穩定的決勝局。沒有 orderBy 時,集合預設按 created_at 降序。當網站經常按自訂純量欄位排序或篩選時,請將其標記為已索引;索引可避免集合成長時的全表掃描。

對集合分頁

對連續 feed 使用游標,對編號頁使用偏移。它們是獨立的分頁模型,不能在一個型別化查詢中組合。

游標分頁

游標分頁從上一次查詢傳回的最後一筆項目之後繼續。請求之間保持排序不變,並原樣傳回 nextCursor,不要檢查或變更它。

當存在另一頁時,以下路由轉譯 Older posts 連結:

---
import { getEmDashCollection } from "emdash";

const cursor = Astro.url.searchParams.get("cursor") ?? undefined;
const { entries: posts, nextCursor, error } = await getEmDashCollection("posts", {
  limit: 10,
  cursor,
  orderBy: { published_at: "desc" },
});

if (error) return new Response("Unable to load posts", { status: 500 });
---

<ul>
  {posts.map((post) => <li><a href={`/posts/${post.id}`}>{post.data.title}</a></li>)}
</ul>

{nextCursor && (
  <a href={`/posts?cursor=${encodeURIComponent(nextCursor)}`}>Older posts</a>
)}

最後一頁沒有 nextCursor。游標分頁不計算總頁數,也不提供上一頁游標;若介面需要返回導覽,請在瀏覽器歷程中保留較早的 URL。

偏移分頁

偏移分頁適合 /posts/page/3 這類路由。將頁碼轉換為偏移,並用 hasMore 做下一頁連結:

---
import { getEmDashCollection } from "emdash";

const parsedPage = Number(Astro.params.page ?? "1");
if (!Number.isInteger(parsedPage) || parsedPage < 1) {
  return Astro.redirect("/404");
}

const perPage = 10;
const { entries: posts, hasMore, error } = await getEmDashCollection("posts", {
  limit: perPage,
  offset: (parsedPage - 1) * perPage,
  orderBy: { published_at: "desc" },
});

if (error) return new Response("Unable to load posts", { status: 500 });
---

<ul>
  {posts.map((post) => <li><a href={`/posts/${post.id}`}>{post.data.title}</a></li>)}
</ul>

<nav aria-label="Post pages">
  {parsedPage > 1 && <a href={`/posts/page/${parsedPage - 1}`}>Newer posts</a>}
  {hasMore && <a href={`/posts/page/${parsedPage + 1}`}>Older posts</a>}
</nav>

偏移必須是非負整數。第 1 頁使用偏移 0,表示「從第一筆項目開始」。偏移分頁易於按頁碼定址,但請求之間新增的項目可能使後面的頁發生移動。當這種移動會造成困惑時,請使用游標。

查詢並轉譯單筆項目

以下執行階段路由按 slug 讀取文章,轉譯其特色圖片與 Portable Text 正文,並區分查詢失敗與缺失項目:

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Image, PortableText } from "emdash/ui";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.redirect("/404");

const { entry: post, error, isPreview, cacheHint } = await getEmDashEntry("posts", slug);

if (error) {
  console.error("Failed to load post:", error);
  return new Response("Unable to load post", { status: 500 });
}

if (!post) return Astro.redirect("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

{isPreview && <p>This is an unpublished preview.</p>}

<article>
  {post.data.featured_image && <Image image={post.data.featured_image} priority />}
  <h1>{post.data.title}</h1>
  <PortableText value={post.data.content} />
</article>

PortableText 為 EmDash 的標準區塊提供轉譯器,包括圖片、圖庫、程式碼、表格與已淨化的 HTML 區塊。當網站新增自己的 Portable Text 區塊時,傳入自訂元件。若取代 htmlBlock 轉譯器,請淨化 HTML,並僅允許網站打算信任的 iframe 主機。

PortableText 在編輯模式下將表格顯示為唯讀預留位置。要在地化其初始標籤,請傳入 tablePlaceholder={translatedLabel}。預設值為 "Table (edit in admin)",不影響已發佈的表格內容。

讀取引用欄位

reference 欄位 將項目連結到另一集合中的項目,關係 介紹如何定義。其值不是 data 的一部分。向 getEmDashEntry 傳入按欄位 slug 鍵控的 references 選項,命名頁面要轉譯的欄位,每個欄位會作為一頁項目傳回:

---
import { getEmDashEntry } from "emdash";

const { entry: post } = await getEmDashEntry("posts", Astro.params.slug, {
  references: { author: true, related_posts: { limit: 6 } },
});

if (!post) return Astro.redirect("/404");

const author = post.references?.author.entries[0];
---

<article>
  <h1>{post.data.title}</h1>
  {author && <p>By {author.data.name}</p>}
  <ul>
    {post.references?.related_posts.entries.map((related) => (
      <li><a href={`/posts/${related.data.slug}`}>{related.data.title}</a></li>
    ))}
  </ul>
</article>

true 請求預設上限 50 筆的第一頁。對包含更多內容的欄位使用 { limit, cursor },每頁最多 100 筆。

每個被引用的項目都是與直接載入相同形狀的 ContentEntry:一個 id、一個帶 Date 物件日期與已解析媒體值的 data 物件,以及作用範圍限定在被引用項目上的 edit 代理,因此在視覺化編輯中點擊卡片會開啟該卡片所關於的項目。署名與分類術語是例外——EmDash 不會將它們水合到被引用項目上,因此請從項目本身讀取 data.bylines 與 data.terms。

當欄位位於關係的父端時,項目按編輯者排列的順序到達。子端的欄位列出指向它的項目,這些項目沒有自己的順序。

該選項在兩個方向都是選擇性啟用。不傳 references 的呼叫不會執行額外查詢,未納入選取的欄位不會被讀取。選取欄位的呼叫每個欄位一次連結查詢,加上每個不同目標集合一次項目查詢,無論每個欄位持有多少項目。

對引用欄位分頁

getEmDashReferences 使用上一頁傳回的游標取得單一欄位的下一頁:

import { getEmDashReferences } from "emdash";

const { entries, nextCursor } = await getEmDashReferences("posts", post.id, "related_posts", {
	cursor,
	limit: 20,
});

它從與 getEmDashEntry 相同的請求情境讀取草稿可見性,因此在預覽中開始的遍歷會繼續看到待定選取。頁面 id 在 i18n 加前綴時攜帶其地區設定(fr/about),傳回的就是該 id。

在快取輸出的路由上,將頁面傳回的 cacheHint 與路由自身的合併,以便對所命名項目的寫入會使頁面過期。

預覽中的引用欄位

公開轉譯看到已發佈項目與已發佈選取。項目的預覽,或處於視覺化編輯中的編輯者,會看到未發佈項目與項目草稿中暫存的選取,因此預覽連結會顯示頁面發佈後將擁有的引用。

套用 SEO 與編輯功能

對於支援 SEO 的集合,getSeoMeta() 會解析編輯者的 SEO 標題、描述、圖片、規範 URL 與無索引選擇,並帶來自項目的回退。目前部落格範本將該結果傳給其基礎版面配置:

---
import { getSeoMeta } from "emdash";

const seo = getSeoMeta(post, {
  siteTitle: "My Blog",
  siteUrl: Astro.url.origin,
  path: Astro.url.pathname,
});
---

<Base
  title={seo.title}
  pageTitle={seo.ogTitle}
  description={seo.description}
  image={seo.ogImage}
  canonical={seo.canonical}
  robots={seo.robots}
>
  <!-- Post content -->
</Base>

包含 <EmDashHead> 的版面配置可將相同的 SEO 欄位與外掛貢獻套用到伺服器轉譯的內容頁。僅讀取 data.title 或 data.excerpt 的手寫中繼標籤不會套用編輯者的規範 URL 或無索引設定。

預覽 URL 無需單獨查詢。中介軟體驗證 _preview 權杖,且 getEmDashEntry() 傳回符合的草稿並帶有 isPreview: true。預覽與視覺化編輯指南 說明了 URL 產生以及用於內嵌編輯的 entry.edit 註解。

產生 TypeScript 型別

開發伺服器根據作用中架構產生 emdash-env.d.ts。在專案的 TypeScript 設定中包含該檔案,以便像 "posts" 這樣的集合名稱自動選取產生的 Post 資料型別。

對於遠端 EmDash 執行個體,CLI 可以取得架構並將型別寫入 .emdash/types.ts:

npx emdash types --url https://cms.example.com

該命令接受 API 權杖或自訂驗證標頭。這些選項見產生型別。

每個繫結到關係的引用欄位的集合都會獲得第二個介面 {Collection}References,註冊在同一 slug 下。getEmDashEntry 將其結果收窄到 references 選項命名的欄位,每頁的項目攜帶目標集合的介面:

const { entry: post } = await getEmDashEntry("posts", "my-post", {
	references: { author: true },
});

// post?.references?.author.entries[0] carries the Author interface
// post?.references?.related_posts is a type error: it was not selected

執行階段轉譯與快取

Node.js 與 Cloudflare 部落格範本在 astro.config.mjs 中設定 output: "server"。它們的內容查詢在每次伺服器轉譯時執行,因此新發佈的修訂有資格進入下一次請求。若您有意預先轉譯某條路由,其 HTML 包含建置時可用的內容,只有再次建置後才會改變。

當 Astro 的快取啟用時,將查詢的 cacheHint 傳給 Astro.cache.set()。EmDash 將回應對應到集合與項目標籤,以便發佈可以使受影響的快取頁面失效。除非延遲更新是明確的產品決策,否則不要用很長的固定 Cache-Control 壽命取代該整合。

有關精確簽章與較少見的篩選器,請參見 JavaScript API 參考。要圍繞這些查詢建置可執行的範例,請繼續參閱建立部落格。