Creare pagine con i blocchi

In questa pagina

Un campo blocks memorizza una composizione di pagina ordinata. Ogni elemento registra un tipo di blocco, una versione dello schema conservata, una chiave stabile e i campi dichiarati da quella versione. Gli editor aggiungono e riordinano i blocchi nell’editor dei contenuti. La route Astro associa ogni tipo di blocco a un componente.

Definire i tipi di blocco

Definisci i tipi di blocco prima del campo della collezione che li utilizza. Un seed conserva ogni versione numerata e il puntatore attivo currentVersion.

Il seed seguente definisce i blocchi Hero e Feature grid, quindi li rende disponibili in un campo layout della collezione Pages:

{
  "$schema": "https://emdashcms.com/seed.schema.json",
  "version": "1",
  "blockTypes": [
    {
      "slug": "hero",
      "label": "Hero",
      "category": "Layout",
      "currentVersion": 1,
      "versions": [
        {
          "version": 1,
          "fields": [
            { "slug": "heading", "label": "Heading", "type": "string", "required": true },
            { "slug": "body", "label": "Body", "type": "portableText" },
            { "slug": "image", "label": "Image", "type": "image" },
            { "slug": "link_label", "label": "Link label", "type": "string" },
            { "slug": "link_url", "label": "Link URL", "type": "url" }
          ]
        }
      ]
    },
    {
      "slug": "feature_grid",
      "label": "Feature grid",
      "category": "Layout",
      "currentVersion": 1,
      "versions": [
        {
          "version": 1,
          "fields": [
            { "slug": "heading", "label": "Heading", "type": "string" },
            {
              "slug": "items",
              "label": "Items",
              "type": "repeater",
              "validation": {
                "subFields": [
                  { "slug": "title", "label": "Title", "type": "string", "required": true },
                  { "slug": "description", "label": "Description", "type": "text" }
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "collections": [
    {
      "slug": "pages",
      "label": "Pages",
      "fields": [
        { "slug": "title", "label": "Title", "type": "string", "required": true },
        {
          "slug": "layout",
          "label": "Layout",
          "type": "blocks",
          "validation": {
            "allowedTypes": ["hero", "feature_grid"],
            "maxItems": 20
          }
        }
      ]
    }
  ]
}

L’ordine di allowedTypes determina il selettore dei blocchi. Se in seguito rimuovi un tipo consentito, EmDash lo sposta nell’elenco retiredTypes gestito dal server. I blocchi esistenti restano modificabili, ma gli editor non possono aggiungere né duplicare quel tipo.

Creare i componenti Astro

Ogni componente riceve value, index e blockKey. Il valore include _type, _version e _key, così un componente può distinguere le versioni conservate quando lo schema del suo blocco evolve.

Il componente Hero legge ogni valore visualizzato dal blocco salvato:

---
import { sanitizeHref } from "emdash";
import { Image, PortableText, type BlockComponentProps } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";

type HeroBlock = Extract<PageLayoutBlock, { _type: "hero" }>;
type Props = BlockComponentProps<HeroBlock>;

const { value } = Astro.props;
---

<section class="hero">
  <div>
    <h1>{value.heading}</h1>
    {value.body && <PortableText value={value.body} />}
    {value.link_url && <a href={sanitizeHref(value.link_url)}>{value.link_label}</a>}
  </div>
  {value.image && <Image image={value.image} />}
</section>

Crea un componente per ogni tipo consentito. Il componente controlla il markup e lo stile; il valore del blocco fornisce contenuti e media.

Renderizzare la composizione

Usa defineBlockComponents per richiedere un componente per ogni _type dell’unione di campi generata. Passa questa mappa a <Blocks> nella route della pagina.

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Blocks, defineBlockComponents } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";
import FeatureGrid from "../../components/blocks/FeatureGrid.astro";
import Hero from "../../components/blocks/Hero.astro";

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

const { entry: page, cacheHint } = await getEmDashEntry("pages", slug);
if (!page) return Astro.redirect("/404");
Astro.cache.set(cacheHint);

const components = defineBlockComponents<PageLayoutBlock>({
  hero: Hero,
  feature_grid: FeatureGrid,
});
---

<Blocks value={page.data.layout} components={components} />

<Blocks> non esegue query su contenuti, schemi, media o rete. Renderizza l’array fornito nell’ordine salvato. Un componente di blocco può eseguire una query esplicita dell’applicazione quando ha bisogno di altri dati.

Gestire un componente mancante

Durante lo sviluppo, un tipo non associato produce un segnaposto visibile e un avviso nella console. Il segnaposto indica il _type ma non stampa il valore salvato del blocco.

In produzione, un tipo non associato renderizza il componente fallback, se fornito. Altrimenti non produce alcun output:

---
import MissingBlock from "../../components/blocks/MissingBlock.astro";
---

<Blocks value={page.data.layout} components={components} fallback={MissingBlock} />

Rilascia il supporto del renderer prima di abilitare o attivare un tipo di blocco usato da contenuti in produzione.

Modificare lo schema di un blocco

Le modifiche compatibili aggiornano la versione attiva. Aggiungere un campo facoltativo, aggiungere un valore predefinito o rendere meno rigida la validazione mantiene lo stesso numero di versione. I blocchi salvati ricevono i valori predefiniti alla successiva scrittura.

Una modifica incompatibile crea una versione inattiva. Rimuovere un campo, cambiare il tipo di un campo, aggiungere un campo obbligatorio o restringere la validazione sono modifiche incompatibili.

  1. Crea la versione incompatibile tramite l’API degli schemi o MCP. Mantienila inattiva.

  2. Aggiorna il renderer in modo che gestisca sia la versione conservata sia quella nuova. Distribuisci il renderer.

  3. Attiva la nuova versione. I nuovi blocchi la utilizzano dopo l’attivazione.

  4. Migra esplicitamente i blocchi salvati con migrateBlocks: true. Conserva la _key di ogni blocco mentre modifichi _version e i campi specifici della versione.

Le versioni precedenti restano disponibili per revisioni, bozze, tracciamento dei media e contenuti salvati. I tipi di blocco e le versioni conservate non prevedono un’operazione di eliminazione definitiva.

Campi annidati supportati

Le definizioni dei blocchi supportano string, text, url, number, integer, boolean, datetime, select, multiSelect, portableText, image, file e repeater.

Riferimenti, JSON, slug, blocchi annidati, widget personalizzati, indici fisici, unicità e localizzazione per singolo sottocampo non sono supportati all’interno di una definizione di blocco. Un campo blocchi di per sé non può essere obbligatorio, univoco, ricercabile o indicizzato, né ricevere un widget di campo personalizzato.

Per le regole esatte di validazione dei campi e dei valori salvati, consulta la guida di riferimento del campo blocks. Per il comportamento dei conflitti e dell’esportazione dei seed, consulta File seed.