EmDash 提供查詢函式,用於在 Astro 頁面和元件中擷取內容。這些函式遵循 Astro 的 Live Content Collections 模式,回傳帶有錯誤處理的結構化結果。
查詢函式
| 函式 | 用途 | 回傳值 |
|---|---|---|
getEmDashCollection | 擷取內容類型的所有條目 | { entries, error } |
getEmDashEntry | 按 ID 或 slug 擷取單一條目 | { entry, error, isPreview } |
import { getEmDashCollection, getEmDashEntry } from "emdash";
取得所有條目
---
import { getEmDashCollection } from "emdash";
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) {
console.error("載入文章失敗:", error);
}
---
<ul>
{posts.map((post) => (
<li>{post.data.title}</li>
))}
</ul>
按區域設定篩選
當 i18n 已啟用時,按區域設定篩選以擷取特定語言的內容:
const { entries: frenchPosts } = await getEmDashCollection("posts", {
locale: "fr",
status: "published",
});
const { entries: localizedPosts } = await getEmDashCollection("posts", {
locale: Astro.currentLocale,
status: "published",
});
對於單一條目,將 locale 作為第三個引數傳遞:
const { entry: post } = await getEmDashEntry("posts", "my-post", {
locale: Astro.currentLocale,
});
省略 locale 時,預設使用請求的當前區域設定。如果請求的區域設定不存在翻譯,則遵循回退鏈。
按狀態篩選
const { entries: published } = await getEmDashCollection("posts", {
status: "published",
});
const { entries: drafts } = await getEmDashCollection("posts", {
status: "draft",
});
限制結果
const { entries: recentPosts } = await getEmDashCollection("posts", {
status: "published",
limit: 5,
});
按分類法篩選
const { entries: newsPosts } = await getEmDashCollection("posts", {
status: "published",
where: { category: "news" },
});
const { entries: jsPosts } = await getEmDashCollection("posts", {
status: "published",
where: { tag: "javascript" },
});
const { entries: featuredNews } = await getEmDashCollection("posts", {
status: "published",
where: { category: ["news", "featured"] },
});
當為單一分類法提供多個值時,where 篩選器使用 OR 邏輯。
錯誤處理
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) {
console.error("載入文章失敗:", error);
return new Response("伺服器錯誤", { status: 500 });
}
取得單一條目
---
import { getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("伺服器錯誤", { status: 500 });
}
if (!post) {
return Astro.redirect("/404");
}
---
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
條目回傳類型
interface EntryResult<T> {
entry: ContentEntry<T> | null;
error?: Error;
isPreview: boolean;
}
interface ContentEntry<T> {
id: string;
data: T;
edit: EditProxy;
}
渲染 SEO 面板資料
對於具有 supports: ["seo"] 的集合,編輯人員可以在管理後台的 SEO 面板中設定 SEO 標題、中繼描述、OG 圖片、規範 URL 和「對搜尋引擎隱藏」(noindex) 開關。這些資料作為 entry.data.seo 傳遞。使用 getSeoMeta 將面板欄位轉換為可渲染的中繼標籤:
---
import { getEmDashEntry, getSeoMeta } from "emdash";
const { entry, error } = await getEmDashEntry("posts", Astro.params.slug);
if (error) {
return new Response("伺服器錯誤", { status: 500 });
}
if (!entry) return Astro.redirect("/404");
const seo = getSeoMeta(entry, {
siteTitle: "我的網站",
siteUrl: "https://example.com",
path: Astro.url.pathname,
});
---
<head>
<title>{seo.title}</title>
{seo.description && <meta name="description" content={seo.description} />}
{seo.ogImage && <meta property="og:image" content={seo.ogImage} />}
{seo.canonical && <link rel="canonical" href={seo.canonical} />}
{seo.robots && <meta name="robots" content={seo.robots} />}
</head>
預覽模式
EmDash 透過中介軟體自動處理預覽:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("伺服器錯誤", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
---
{isPreview && (
<div class="preview-banner">
正在查看預覽。此內容尚未發佈。
</div>
)}
<article>
<h1>{entry.data.title}</h1>
<PortableText value={entry.data.content} />
</article>
視覺化編輯
<article {...entry.edit}>
<h1 {...entry.edit.title}>{entry.data.title}</h1>
<div {...entry.edit.content}>
<PortableText value={entry.data.content} />
</div>
</article>
在編輯模式下,{...entry.edit.title} 會產生 data-emdash-ref 屬性。在生產環境中不會產生輸出。
內聯程式碼區塊樣式
| 屬性 | 用途 |
|---|---|
--emdash-inline-code-background | 程式碼區塊背景 |
--emdash-inline-code-foreground | 普通程式碼文字 |
--emdash-inline-code-muted | 註解和引用文字 |
--emdash-inline-code-keyword | 關鍵字、字面量、選擇器和刪除文字 |
--emdash-inline-code-string | 字串、屬性、符號和新增文字 |
--emdash-inline-code-number | 數字和中繼資料 |
--emdash-inline-code-title | 標題、名稱、類型和內建函式 |
--emdash-inline-code-border | 語言選擇器邊框 |
--emdash-inline-code-control-background | 語言選擇器背景 |
--emdash-inline-code-control-foreground | 語言選擇器文字和圖示 |
--emdash-inline-code-focus | 鍵盤焦點指示器 |
:root {
--emdash-inline-code-background: #f7f7f5;
--emdash-inline-code-foreground: #24292f;
--emdash-inline-code-muted: #57606a;
--emdash-inline-code-keyword: #b8172a;
--emdash-inline-code-string: #0a3069;
--emdash-inline-code-number: #0550ae;
--emdash-inline-code-title: #7545c7;
--emdash-inline-code-border: #7d8590;
--emdash-inline-code-control-background: #fff;
--emdash-inline-code-control-foreground: #24292f;
--emdash-inline-code-focus: #0550ae;
}
:root.dark {
--emdash-inline-code-background: #202020;
--emdash-inline-code-foreground: #f0f3f6;
--emdash-inline-code-muted: #c9d1d9;
--emdash-inline-code-keyword: #ffc1bb;
--emdash-inline-code-string: #b9ddff;
--emdash-inline-code-number: #a8d5ff;
--emdash-inline-code-title: #e5ccff;
--emdash-inline-code-border: #6e7681;
--emdash-inline-code-control-background: #161b22;
--emdash-inline-code-control-foreground: #f0f3f6;
--emdash-inline-code-focus: #a8d5ff;
}
排序結果
const { entries: posts } = await getEmDashCollection("posts", { status: "published" });
const sorted = posts.sort(
(a, b) => (b.data.publishedAt?.getTime() ?? 0) - (a.data.publishedAt?.getTime() ?? 0),
);
常見排序模式
posts.sort((a, b) => a.data.title.localeCompare(b.data.title));
posts.sort((a, b) => (a.data.order ?? 0) - (b.data.order ?? 0));
posts.sort(() => Math.random() - 0.5);
TypeScript 型別
npx emdash types
import { getEmDashCollection, getEmDashEntry } from "emdash";
import type { Post } from "../.emdash/types";
const { entries: posts } = await getEmDashCollection<Post>("posts");
const { entry: post } = await getEmDashEntry<Post>("posts", "my-post");
靜態 vs. 伺服器渲染
靜態(預先渲染)
---
import { getEmDashCollection, getEmDashEntry } from "emdash";
export async function getStaticPaths() {
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
return posts.map((post) => ({ params: { slug: post.data.slug } }));
}
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);
---
伺服器渲染
---
export const prerender = false;
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) return new Response("伺服器錯誤", { status: 500 });
if (!post) return new Response(null, { status: 404 });
---
效能考量
快取
---
const { entries: posts } = await getEmDashCollection("posts", { status: "published" });
Astro.response.headers.set("Cache-Control", "public, max-age=300");
---
避免冗餘查詢
---
import { getEmDashCollection } from "emdash";
import PostList from "../components/PostList.astro";
import Sidebar from "../components/Sidebar.astro";
const { entries: posts } = await getEmDashCollection("posts", { status: "published" });
const featured = posts.filter((p) => p.data.featured);
const recent = posts.slice(0, 5);
---
<PostList posts={featured} />
<Sidebar posts={recent} />