Zum Inhalt springen
Docxcelerate

Nodes

Das Node-Modell

Was alle Nodes gemeinsam haben, worin sie sich unterscheiden und wo jeder von ihnen dokumentiert ist.

Ein Dokument ist ein Baum aus Nodes. Jeder Node ist drei Dinge: eine id, eine Art und eine Regel, die Inhalt erzeugt.

<Paragraph          // the kind
  id="greeting"     // the address
>
  Dear {state.name},  {/* the rule */}
</Paragraph>

Gegen Ihre Daten aufgelöst, wird daraus reines JSON:

{ "id": "greeting", "kind": "paragraph", "mode": "static", "text": "Dear Adaeze Nkemelu," }

Kein Styling, kein Layout. Das gehört dem Renderer.

Der Katalog

Jeder Typ hat eine eigene Seite, mit jeder Option, die er annimmt, und einer Vorschau zu jeder Schreibweise.

Struktur

Section Groups nodes under a titled heading.

The only construct that nests today. Its title carries into the document outline, so the structure you write is the structure the reader sees.

export const Opening: Section = () => (
  <Section id="opening" title="Your renewal">
    <Greeting />
    <PriceChange />
  </Section>
);

Section-Referenz, mit Vorschauen →

TableOfContents A marker for a contents list, ahead of the renderers that build one.

The kind is part of the document schema and both renderers accept it. What is missing is downstream rather than in the authoring: the element places the marker and carries its title, and building the entries from the surrounding section titles is a renderer's job.

export const Contents: TableOfContents = () => (
  <TableOfContents id="contents" title="What is in this letter" />
);

Table of contents-Referenz, mit Vorschauen →

Text

Paragraph A block of prose, written from your data or generated from prompts.

The workhorse. A static paragraph holds the text as its children; a dynamic one carries prompts and a placeholder, and is filled at request time. Both land as the same node kind, differing only by `mode` — which is inferred from what you supply, never declared. Supplying both text and a prompt on one element is an error rather than a precedence rule.

Helper: Paragraph, useSetPrompts, useSetPlaceholders

export const Greeting: Paragraph = () => {
  const [state] = useState((data: SampleData) => ({ name: data.memberName }));

  return <Paragraph id="greeting">Dear {state.name},</Paragraph>;
};

Paragraph-Referenz, mit Vorschauen →

Medien

Image A picture resolved from your data or described by a prompt.

A static image points at something you hold — a signature, a logo, a site photograph — with every field able to vary per recipient. A dynamic image describes what is wanted and leaves the endpoint to make it.

export const Signature: Image = () => {
  const [state] = useState((data: SampleData) => ({
    src: data.signatureUrl,
    manager: data.managerName,
  }));

  return (
    <Image
      id="signature"
      src={state.src}
      alt={`Signed by ${state.manager}`}
      width={180}
      height={60}
    />
  );
};

Image-Referenz, mit Vorschauen →

Shape A drawn rectangle with the document's own words on top of it.

Word draws a real rectangle and the paragraphs sit on its fill rather than beside it. What separates it from a paragraph with a background is the size: a shape is a box you decided the dimensions of and does not grow with its text, which is exactly what a banner needs — one that changed depth with its wording would be a different notice on every letter. When the box *should* grow with what is in it, a `Table` with one cell is the thing to reach for.

export const RenewalBanner: ShapeComponent = () => {
  const { date } = useFormat("en-GB");
  const [state] = useState((data: SampleData) => ({
    plan: data.plan,
    renewsOn: data.renewsOn,
  }));

  return (
    <Shape id="renewal-banner" variant="bannerAttention" height={44}>
      <Paragraph id="renewal-banner-line">
        {state.plan} renews on {date(state.renewsOn)}
      </Paragraph>
    </Shape>
  );
};

Shape-Referenz, mit Vorschauen →

clipArt Named visual blocks — rules, marks, callouts — drawn by the renderer. Geplant

Not built yet. Nothing is fetched: the node names a shape and the renderer draws it, so it stays sharp in the DOCX and ships no asset.

Daten

Graph A real Word chart, declared as data.

Charts are declared, never drawn: `graphType` fixes the form, `data` carries the payload. What lands in the `.docx` is a DrawingML chart part with every value cached in it — the chart Word builds from Insert → Chart — so a reader can select it, restyle it and open its numbers. Nothing is rasterised, which is why charts cost the package no drawing library and no native module.

export const VisitsByMonth: Graph = () => {
  const [state] = useState((data: SampleData) => ({
    centreName: data.centreName,
    months: data.visitsByMonth.map((entry) => entry.month),
    visits: data.visitsByMonth.map((entry) => entry.visits),
  }));

  return (
    <Graph
      id="visits-by-month"
      title={`Visits to ${state.centreName}`}
      graphType="bar"
      valueAxisTitle="Visits"
      data={{
        categories: state.months,
        series: [{ label: "Visits", values: state.visits }],
      }}
      caption={`Your visits to ${state.centreName}, last six months`}
    />
  );
};

Graph-Referenz, mit Vorschauen →

Table A grid of cells, with the columns declared once.

The columns belong to the table, because every row shares them — a table whose columns do not line up is not a table. Everything else is an ordinary node: a `.map()` produces rows, a condition drops one, and each names itself. That is what lets a published invoice carry one loop the engine walks rather than a table full of special cases.

Helper: Table, Row, Cell

export const VisitLog: Table = () => {
  const { number } = useFormat("en-GB");
  const [state] = useState((data: SampleData) => ({ months: data.visitsByMonth }));

  return (
    <Table id="visit-log" columns={[{ width: "auto" }, { width: 28, align: "right" }]}>
      <Row header>
        <Cell>Month</Cell>
        <Cell>Visits</Cell>
      </Row>
      {state.months.map((entry) => (
        <Row>
          <Cell>{entry.month}</Cell>
          <Cell>{number(entry.visits)}</Cell>
        </Row>
      ))}
    </Table>
  );
};

Table-Referenz, mit Vorschauen →

Ids sind Adressen

Über die id eines Nodes spricht ein Generierungs-Endpoint genau diesen Absatz an, und über sie liegen zwei Build-Artefakte in einem Diff nebeneinander. Eine Umbenennung bricht alles, was von außen darauf zeigt — genau wie das Umbenennen einer API-Route.

Halten Sie sie im Dokument eindeutig. Das kostet nichts und macht Logs lesbar.

Woher der Inhalt kommt

Jeder Node hat entweder seinen Inhalt oder Prompts, um ihn zu erzeugen, und das entscheidet die Komponente, statt dass es irgendwo deklariert würde. Geben Sie einem Absatz Text — oder einem Image ein src, oder einem Graph seine data —, und er löst auf Ihrem Rechner auf. Geben Sie ihm stattdessen Prompts und einen Platzhalter, wird er zur Anfragezeit ausgefüllt.

Beide lösen zur selben kind auf und unterscheiden sich im gebauten Dokument durch mode. mode ist Ausgabe, nicht Eingabe: Der Build leitet es aus dem ab, was die Komponente angegeben hat, und das Paket sagt einer Engine, welche Nodes sie auflösen muss. Beides an einem Element anzugeben ist ein Fehler und kein Münzwurf.

Nodes schreiben behandelt die Prompt-Slots und was Vorschauen an ihrer Stelle zeigen.

Verschachtelung

section ist heute der einzige Node, der Kinder aufnimmt, und er akzeptiert jede Art, auch weitere Abschnitte. Ein Tabellen-Node mit Nodes in seinen Zellen kommt als Nächstes, nach demselben Prinzip: Container nehmen die Komponenten auf, die Sie ohnehin schreiben, statt daneben ein zweites Inhaltsmodell zu stellen.

Zu diesen Vorschauen

Jede Vorschau hier ist ein echter Build. src/nodes/ im Repository dieser Site enthält eine Datei je Variante, geschrieben gegen das veröffentlichte Paket; ein Build-Schritt löst jede davon über buildDocument auf und rendert sie mit dem Renderer, den dxcl dev ausliefert. Der gezeigte Quelltext ist die Datei, die gelaufen ist, und das JSON ist das, was zurückkam — diese Seiten gehen also laut kaputt, statt still zu veralten.


Diese Seite auf GitHub bearbeiten ↗ Diese Seite als Markdown lesen