Interrogare i contenuti

In questa pagina

Le pagine EmDash leggono i contenuti al momento della richiesta con getEmDashCollection() e getEmDashEntry(). La prima funzione restituisce un elenco, la seconda una voce tramite slug o ID contenuto. Entrambe restituiscono gli errori come dati così la pagina può decidere come rispondere.

I template in bundle usano l’output server di Astro. Un visitatore riceve quindi l’ultima revisione pubblicata al render successivo dopo che un editor la pubblica. Una bozza salvata resta privata finché non viene pubblicata o richiesta tramite un URL di anteprima valido.

Interrogare una collezione

La pagina seguente carica i sette post pubblicati più di recente. Le query pubbliche sulle collezioni usano per impostazione predefinita status: "published", quindi il filtro non deve ripeterlo.

---
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>

Ordinamento e limite avvengono nel database prima che EmDash idrati le voci. Così si evita di caricare un’intera collezione e ordinarla o ritagliarla nella pagina.

Il risultato contiene:

  • entries, un array vuoto quando non ci sono corrispondenze o la query è fallita;
  • error, impostato per una query fallita ma non per un risultato vuoto;
  • cacheHint, che porta i tag e l’ora di ultima modifica per la cache di Astro;
  • nextCursor, impostato quando una pagina a cursore limitata ha altre voci; e
  • hasMore, che indica se una pagina a cursore o offset limitata ha un’altra pagina.

Identificatori della voce

Ogni risultato ha due identificatori con compiti diversi:

  • entry.id è l’identificatore orientato all’URL prodotto dal content loader. Di norma è lo slug. Quando l’internazionalizzazione antepone una locale, il prefisso è incluso. Usa questo valore quando costruisci un link da un risultato di collezione.
  • entry.data.id è l’ID contenuto stabile memorizzato nel database. Non cambia quando un editor cambia lo slug. Usalo quando un’API, un helper di tassonomia, un contesto di pagina o una relazione si aspetta un ID contenuto.

getEmDashEntry() accetta lo slug o l’ID contenuto stabile. Una ricerca per slug è limitata a una locale quando l’internazionalizzazione è abilitata; un ID contenuto identifica la riga direttamente.

Filtrare una collezione

Passa i filtri nel secondo argomento. La query seguente restituisce i post pubblicati nella categoria news il cui campo series è engineering:

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

Le chiavi che nominano una tassonomia corrispondono agli slug dei termini assegnati. Altre chiavi corrispondono ai campi della collezione. Più chiavi sono combinate con logica AND. Un array su una chiave corrisponde a qualsiasi valore elencato, quindi { category: ["news", "updates"] } corrisponde a una delle due categorie.

Usa locale per richiedere una lingua esplicitamente:

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

Se locale è omesso, EmDash usa la locale della richiesta e poi la locale predefinita configurata. Vedi Internazionalizzazione per le regole di fallback e le route tradotte.

status accetta "published", "draft" o "archived". Non richiedere bozze da una route pubblica. Usa il flusso di anteprima quando un visitatore necessita di accesso temporaneo a una voce non pubblicata.

Ordinare i risultati nel database

orderBy mappa i nomi dei campi a "asc" o "desc". Usa i nomi di campo memorizzati, non i nomi in camelCase restituiti in entry.data:

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

Le colonne di sistema usano i nomi del database, come created_at, updated_at e published_at. I campi personalizzati usano lo slug della collezione, come title o priority. I dati restituiti mappano le date di sistema a createdAt, updatedAt e publishedAt, ma quei nomi di proprietà in camelCase non sono campi orderBy validi.

EmDash usa il primo campo orderBy valido come chiave di paginazione e l’ID contenuto come tie-breaker stabile. Senza orderBy, le collezioni sono ordinate per impostazione predefinita per created_at decrescente. Contrassegna i campi scalari personalizzati come indicizzati quando il sito ordina o filtra regolarmente su di essi; l’indice evita una scansione completa della tabella man mano che la collezione cresce.

Paginare una collezione

Usa un cursore per un feed continuo o un offset per pagine numerate. Sono modelli di paginazione separati e non possono essere combinati in una query tipizzata.

Paginazione a cursore

La paginazione a cursore continua dopo l’ultima voce restituita dalla query precedente. Mantieni l’ordinamento invariato tra le richieste e passa indietro nextCursor senza ispezionarlo o modificarlo.

La route seguente renderizza un link Older posts quando esiste un’altra pagina:

---
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 è assente sull’ultima pagina. La paginazione a cursore non calcola un conteggio totale di pagine e non fornisce un cursore della pagina precedente; conserva gli URL precedenti nella cronologia del browser se l’interfaccia necessita di navigazione indietro.

Paginazione a offset

La paginazione a offset si adatta a route come /posts/page/3. Converti il numero di pagina in un offset e usa hasMore per il link alla pagina successiva:

---
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>

Un offset deve essere un intero non negativo. La pagina 1 usa un offset di zero, che significa «inizia dalla prima voce». La paginazione a offset è facile da indirizzare per numero di pagina, ma le voci aggiunte tra le richieste possono spostare le pagine successive. Usa i cursori quando quel movimento sarebbe confuso.

Interrogare e renderizzare una voce

La seguente route runtime legge un post per slug, renderizza l’immagine in evidenza e il corpo Portable Text, e distingue un errore di query da una voce mancante:

---
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 fornisce renderer per i blocchi standard di EmDash, comprese immagini, gallerie, codice, tabelle e blocchi HTML sanitizzati. Passa componenti personalizzati quando un sito aggiunge i propri blocchi Portable Text. Se sostituisci il renderer htmlBlock, sanitizza l’HTML e consenti solo gli host iframe a cui il sito intende fidarsi.

PortableText mostra le tabelle come segnaposto di sola lettura in modalità modifica. Per localizzare l’etichetta iniziale, passa tablePlaceholder={translatedLabel}. Il valore predefinito è "Table (edit in admin)" e non influisce sul contenuto delle tabelle pubblicate.

Leggere i campi di riferimento

Un campo reference collega una voce a voci di un’altra collezione, e Relazioni spiega come definirne una. Il suo valore non fa parte di data. Passa un’opzione references a getEmDashEntry con i campi che la pagina renderizza, chiave per slug di campo, e ciascuno torna come una pagina di voci:

---
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 richiede la prima pagina al limite predefinito di 50 voci. Usa { limit, cursor } per un campo che ne contiene di più, fino a 100 per pagina.

Ogni voce referenziata è un ContentEntry con la stessa forma di una caricata direttamente: un id, un oggetto data con date come oggetti Date e valori media risolti, e un proxy edit limitato alla voce referenziata, così un clic su una card in visual editing apre la voce di cui parla la card. Bylines e termini di tassonomia sono l’eccezione: EmDash non li idrata sulle voci referenziate, quindi leggi data.bylines e data.terms dalla voce stessa.

Le voci arrivano nell’ordine disposto dall’editor quando il campo è all’estremo genitore della relazione. Un campo all’estremo figlio elenca le voci che puntano a esso, che non hanno un ordine proprio.

L’opzione è opt-in in entrambe le direzioni. Una chiamata senza references non esegue query extra, e un campo escluso dalla selezione non viene letto. Una chiamata che seleziona campi costa una query di link per campo, più una query di voci per collezione di destinazione distinta, indipendentemente da quante voci contenga ciascun campo.

Paginare un campo di riferimento

getEmDashReferences recupera la pagina successiva di un singolo campo, usando il cursore restituito dalla pagina precedente:

import { getEmDashReferences } from "emdash";

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

Legge la visibilità delle bozze dallo stesso contesto di richiesta di getEmDashEntry, così un percorso iniziato in anteprima continua a vedere la selezione in sospeso. Un id di pagina porta la sua locale dove i18n ne antepone una (fr/about), ed è l’id da passare indietro.

Su una route che mette in cache l’output, unisci il cacheHint restituito dalla pagina con quello della route, così una scrittura a una delle voci nominate fa scadere la pagina.

Campi di riferimento in anteprima

Un render pubblico vede le voci pubblicate e la selezione pubblicata. Un’anteprima della voce, o un editor in visual editing, vede le voci non pubblicate e la selezione preparata nella bozza della voce, così un link di anteprima mostra i riferimenti che la pagina avrà una volta pubblicata.

Applicare funzioni SEO e di editing

Per una collezione con supporto SEO, getSeoMeta() risolve il titolo SEO, la descrizione, l’immagine, l’URL canonico e la scelta no-index dell’editor con fallback dalla voce. Il template blog attuale passa quel risultato al layout di base:

---
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>

Un layout che include <EmDashHead> può applicare gli stessi campi SEO e i contributi dei plugin alle pagine di contenuto renderizzate sul server. Meta tag scritti a mano che leggono solo data.title o data.excerpt non applicano l’URL canonico né l’impostazione no-index dell’editor.

Gli URL di anteprima non necessitano di una query separata. Il middleware convalida il token _preview e getEmDashEntry() restituisce la bozza corrispondente con isPreview: true. La guida ad anteprima e visual editing spiega la generazione degli URL e le annotazioni entry.edit usate per l’editing inline.

Generare tipi TypeScript

Il server di sviluppo genera emdash-env.d.ts dallo schema attivo. Mantieni quel file incluso nella configurazione TypeScript del progetto così un nome di collezione come "posts" seleziona automaticamente il tipo di dati Post generato.

Per un’istanza EmDash remota, la CLI può recuperare lo schema e scrivere i tipi in .emdash/types.ts:

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

Il comando accetta un token API o intestazioni di autenticazione personalizzate. Vedi Generare tipi per tali opzioni.

Ogni collezione con un campo di riferimento legato a una relazione ottiene una seconda interfaccia, {Collection}References, registrata sotto lo stesso slug. getEmDashEntry restringe il risultato ai campi nominati dall’opzione references, e le voci di ogni pagina portano l’interfaccia della collezione di destinazione:

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

Rendering runtime e caching

I template blog Node.js e Cloudflare impostano output: "server" in astro.config.mjs. Le loro query di contenuto vengono eseguite a ogni render del server, così una revisione appena pubblicata è eleggibile per la richiesta successiva. Se prerenderizzi deliberatamente una route, il suo HTML contiene il contenuto disponibile al build e cambia solo dopo un altro build.

Quando la cache di Astro è abilitata, passa il cacheHint della query a Astro.cache.set(). EmDash associa la risposta ai tag di collezione e voce così la pubblicazione può invalidare le pagine in cache interessate. Evita di sostituire quell’integrazione con una durata fissa e lunga di Cache-Control a meno che gli aggiornamenti ritardati non siano una decisione di prodotto esplicita.

Per firme esatte e filtri meno comuni, vedi il riferimento all’API JavaScript. Per costruire un esempio funzionante attorno a queste query, continua con Creare un blog.