Event-Export
Um Events aus copilot exportieren zu können, wurde ein flexibler Event-Export geschaffen. Hiermit lassen sich sowohl Dokumente wie z.B. Production Sheets auf Basis einzelner Events wie auch Auswert…
Um Events aus co*pilot exportieren zu können, wurde ein flexibler Event-Export geschaffen. Hiermit lassen sich sowohl Dokumente wie z.B. Production Sheets auf Basis einzelner Events wie auch Auswertungen über mehrere Events hinweg generieren. Exporte werden auf Basis von Export-Konfigurationen durchgeführt. In einer solchen Export-Konfiguration wird festgelegt, welche Daten eines Events in welchem Format ausgespielt werden.
Über den Unterpunkt “Exporte” des Event-Menus können im Anschluss Events selektiert und exportiert werden. Zudem lassen sich einzelne Events auch über die Event-Detail-Seite exportieren.
Wir haben einige Beispiel-Exporte ausgearbeitet, die du als Grundlage für deine Anforderungen verwenden kannst. Komplexere Anpassungen oder komplett neuartige Exporte erfordern grundlegende Kenntnisse in Markdown/CSS und ggfs. JavaScript. Gerne können wir das für euch im Auftrag erledigen.
Für experimentierfreudige Menschen, empfehlen wir auch die Nutzung von ChatGPT. Hier für findet ihr einen superprompt zum sofortigen Einfügen für direkte Anweisungen und Beispiel.
Export-Konfiguration
Beim Anlegen einer neuen Export-Konfiguration in den Einstellungen werden folgende Felder belegt:

(1) Name
Name der Konfiguration
(2) Format
- HTML
- CSV
HTML
HTML ist eine Seitenbeschreibungssprache um Inhalte zu formatieren. Dieses Format eignet sich sehr gut, um Detailseiten auszugeben.
Ein HTML-Export kann als Word oder PDF File ausgegeben werden.
CSV
CSV ist ein gängiges Format um Informationen tabellarisch darzustellen. Hierbei werden Spalten mittels Komma oder Semikolon separiert.
- https://de.wikipedia.org/wiki/CSV_(Dateiformat)
(3) Default Seitenlayout
Das Seitenlayout ist für die Darstellung der Exporte nötig.
- A4 Hochformat – Klassisches A4-Layout ohne Seitenpaginierung.
- A4 Hochformat (Paged) – A4-Layout mit automatischer Seitenpaginierung. Der Export wird in echte Seiten aufgeteilt, mit Seitenumbrüchen und Seitenzahlen. Ideal für mehrseitige Dokumente wie Verträge oder Berichte. Alle mitgelieferten Beispiel-Templates sind für dieses Layout optimiert. Unterstützt eigene Kopf-/Fußzeilen und Seitenzahlen über CSS
@pagemargin boxes. - A4 Querformat – Wie A4 Hochformat, aber im Querformat.
- A4 Querformat (Paged)* – Wie A4 Hochformat (Paged), aber im Querformat.
- Frei – Keine festen Seitenmaße. Geeignet für tabellarische Daten oder Exporte, die nicht als Druckseite dargestellt werden sollen.
(4) Beispiel Template laden
Hinterlegte Vorlagen laden. Beim Laden eines Beispiel-Templates wird automatisch das passende Seitenlayout gesetzt (z.B. “A4 Hochformat (Paged)” für alle HTML-Templates oder “Frei” für CSV-Exporte).
(5) Preprocessing Script (Fortgeschritten)
Hier können die Input-Daten via JavaScript transformiert werden. Der Rückgabewert dieses Scripts ersetzt die eigentlichen Input-Daten des Exports. Es ist nur der Funktions-Rumpf anzugeben, wobei die Input-Daten als ctx zur Verfügung stehen.
Hier ein Beispiel:
return { ...ctx,
// Folgende Zeile ersetzt
// die ursprünglichen Events
events: ctx.events.map(e => e.start),
foo: "bar"
}
(6) Template
Die Vorlage wird in einer Handlebars-kompatiblen Templating-Sprache verfasst. Auf Basis dieser Vorlage und den Input-Daten wird die Ausgabe generiert. Daten werden mittels Platzhalter (= “Handlebars Expressions”) übergeben, die in “{{ }}” eingeschlossen sind.
(7) Vertraulich
Wird ein Export als “vertraulich” deklariert, werden zudem vertrauliche Daten (wie z.B. vertrauliche Benutzerfelder, Deals und Angebote) im Export verfügbar.
Vertrauliche Exporte können nur von Benutzer:innen mit der Berechtigung “ACCESS_CONFIDENTIAL” oder “ADMIN” erzeugt werden.
(8) Ansicht
Eine Ansicht aus dem Eventgrid auswählen um Events für die Vorschau zu laden.
(9) Event(s) nach Datum oder Name suchen
Bestimmte Events anhand des Namens oder des Datums suchen, um diese für die Vorschau zu laden.
(10) Vorschau
Vorschau der Ausgabe
(11) Code
Erzeugten Code anzeigen
(12) Input-Daten
Geladene Beispiel-Daten anzeigen
Beispiel-Exporte
Input-Daten
Aktuell steht die Liste an Events in der Vorlage zur Verfügung. Das können die selektierten Events auf der Export-Seite sein, alle Events, die eine Perspektive zurückliefert oder auch nur ein einzelnes Event, wenn ein Export von einer Event-Detail-Seite aus gestartet wird.
{
baseData: BaseDataPayload,
events: EventExportPayload[]
}
BaseData (Stammdaten)
BaseDataPayload {
"addStreet": string
"additionalName": string
"city": string
"countryIsoCode": string
"createdAt": Date
"createdById": Date
"department": string
"email": string
"homepage": string
"logo": ExportPayloadImage
"memo": string
"name": string
"phone": string
"street": string
"updatedAt": Date
"updatedById": string
"vvk_email": string
"zip": string
}
Event
EventExportPayload {
copilotUrl: string
id: string
slug: string
status: string
optionEnd?: Date
kind: string
start: Date
name: string
subTitle?: string
eventType: string
memo?: string
createdAt: Date
updatedAt: Date
displayNames: {
title: string
eventTitleWithArtists: string
eventTitleWithArtistsAndShows: string
artists: string
artistsAndShows: string
rooms: string
locationsWithRooms: string
locationsWithCityAndRooms: string
locationsWithAddressAndRooms: string
}
// Achtung deprecated **
activePromotions: { name: string }[]
images?: ExportPayloadImage[]
files?: ExportPayloadFile[]
links?: ExportPayloadLink[]
locations?: {
id: string
name: string
own: boolean
address: ExportPayloadAddress
operator?: {
displayName: string
email: string
phone: string
}
contacts?: ExportPayloadContact[]
benutzerfelder?: ExportPayloadCustomField[]
images: ExportPayloadImage[]
files: ExportPayloadFile[]
rooms?: {
name: string
blocked: boolean
images?: ExportPayloadImage[]
files?: ExportPayloadFile[]
}[]
}[]
artists?: {
name: string
description: string
deals?: {
name: string
description: string
guarantee: number
artistPercentage: number
breakEvenSales: number
}[]
show?: {
slug: string
name: string
releasesAt?: Date
memo?: string
images?: ExportPayloadImage[]
files?: ExportPayloadFile[]
artistShowInformations?: ExportPayloadCustomField[]
}
contacts?: ExportPayloadContact[]
images: ExportPayloadImage[]
files: ExportPayloadFile[]
links?: ExportPayloadLink[]
artistInformations?: ExportPayloadCustomField[]
}[]
contacts?: ExportPayloadContact[]
schedule?: {
name: string
date: Date
memo: string
}[]
eventInformations?: ExportPayloadCustomField[]
series?: {
id: string
title: string
subTitle: string
}[]
offersOnEvent?: {
offerStatus: string
name: string
description: string
discount: number
netTotal: number
grossTotal: number
netTotalByVat: Record<number, number>
roomsOnOffer?: {
room: string
roomConfiguration?: {
id: string
name: string
}
description: string
price: number
discount: number
vat: number
netTotal: number
grossTotal: number
commodityGroup?: {
name: string
vatBusiness: number
vatPrivate: number
}
}[]
resourcesOnOffer?: {
resource: string
description: string
amount: number
unit: string
price: number
discount: number
vat: number
netTotal: number
grossTotal: number
categories?: string[]
commodityGroup?: {
name: string
vatBusiness: number
vatPrivate: number
}
}[]
}[]
ticketing?: {
ticketOffice: string
category: string
contingent: number
vat: number
netPrice: number
grossPrice: number
timestamp: Date
soldTickets: number
saleTotal: number
zeroPriceTickets: number
}[]
shopEvent?: {
active: boolean
start: Date
name: string
subTitle?: string
description?: string
location?: string
image?: ExportPayloadImage
url?: string
fallbackUrl?: string
}
}
type ExportPayloadContact = {
contactId: string
role: string
displayName: string
firstName?: string
lastName?: string
gender?: string
title?: string
salutation?: string
letterSalutation?: string
birthdate?: Date
note?: string
address: ExportPayloadAddress
email: string
phone: string
contactInformations?: ExportPayloadCustomField[]
links?: ExportPayloadLink[]
linksByType?: Record<string, string>
bankAccounts?: ExportPayloadBankAccount[]
images?: ExportPayloadImage[]
files?: ExportPayloadFile[]
}
type ExportPayloadCustomField = {
name: string
type: string
value: any
confidential?: boolean
tags?: string[]
}
type ExportPayloadImage = {
id: string
name: string
description: string
tags: string[]
sizes: Record<string, string>
}
type ExportPayloadFile = {
id: string
name: string
description: string
path: string
preview: string
download: string
tags?: string[]
}
type ExportPayloadBankAccount = {
type: string
institute: string | null
username: string | null
owner: string | null
iban: string | null
bic: string | null
}
type ExportPayloadAddress = {
street: string
addStreet: string
zip: string
city: string
country: string
lat: string
lng: string
}
type ExportPayloadLink = {
type: string
value: string
}
** Unser Promotion-Modul wird durch eine neue Version ersetzt. Diese befindet sich gerade noch in der Beta-Phase.
[activePromotions] wird daher in Kürze ersetzt.
Weiter Infos bitte via support@copilot.events
Helper-Funktionen
Standard
Folgende Standard-Helper und Block-Parameter aus der Handlebars-Spezifikation werden unterstützt:
| Helper | Zweck | Kurz-Beispiel |
|---|---|---|
if | Bedingung — rendert Inhalt nur wenn Wert truthy ist | {{#if x}}…{{/if}} |
unless | Negation von if | {{#unless x}}…{{/unless}} |
each | Iteration über Array oder Objekt | {{#each items}}…{{/each}} |
with | Wechsel des Kontexts auf ein Sub-Objekt | {{#with obj}}…{{/with}} |
lookup | Dynamischer Property-Zugriff per Variable | {{lookup obj key}} |
Innerhalb von {{#each}} und {{#with}} stehen außerdem die Block-Parameter @first, @last, @index, @key und @root zur Verfügung.
each
Die each-Funktion iteriert (Schleife) über ein angegebenes Array oder Objekt. Sie beginnt mit {{#each}} und endet mit {{/each}}.
Sie verlangt das Array bzw. Objekt als Parameter. Innerhalb der Schleife referenziert this das aktuelle Element, und es stehen die Block-Parameter @first, @last, @index, @key zur Verfügung.
Beispiel
Eine Schleife über alle events. Die Daten eines Events werden mit this.* angesprochen:
{{#each events}}
<h1>{{{this.name}}}</h1>
<p>Datum: {{formatDate this.start "d.m.Y"}}</p>
{{/each}}
Optional kann mit as |element index| ein Alias für das aktuelle Element und den Index gesetzt werden — nützlich bei verschachtelten Schleifen, wo this bereits durch die innere Schleife belegt ist:
{{#each events as |event idx|}}
<p>{{idx}}: {{event.name}}</p>
{{/each}}
if
Die if-Funktion rendert ihren Inhalt nur, wenn der übergebene Wert “truthy” ist (also z.B. nicht null, leerer String, false oder 0). Sie beginnt mit {{#if}} und endet mit {{/if}} und kann optional einen {{else}}-Zweig enthalten.
Beispiel
Alle Event-Informationen ausgeben, sofern ein Wert gesetzt ist:
{{#each this.eventInformations}}
{{#if this.value}}
<p>{{ this.name }}</p>
{{ this.value }}
{{/if}}
{{/each}}
Mit {{else}} kann ein Alternativ-Zweig angegeben werden:
{{#if this.subTitle}}
<h2>{{this.subTitle}}</h2>
{{else}}
<h2>Kein Untertitel</h2>
{{/if}}
includeZero
Standardmäßig wird 0 als “falsy” behandelt und der if-Block nicht gerendert. Soll 0 als gültiger Wert gelten (z.B. bei Verkaufszahlen oder Gagen), kann das Hash-Argument includeZero=true gesetzt werden:
{{#if this.soldTickets includeZero=true}}
Verkauft: {{this.soldTickets}}
{{/if}}
unless
Die unless-Funktion ist die Negation von if — sie rendert ihren Inhalt nur, wenn der Wert “falsy” ist. Sie beginnt mit {{#unless}} und endet mit {{/unless}} und kann ebenfalls einen {{else}}-Zweig enthalten.
Beispiel
Alle Event-Informationen außer “Gästeliste” auflisten:
{{#each this.eventInformations}}
{{#unless (isEqual name "Gästeliste")}}
<h3>{{this.name}}</h3>
{{this.value}}
{{/unless}}
{{/each}}
In Kombination mit den Block-Parametern @first oder @last sehr praktisch, um z.B. einen Trenner nur zwischen Elementen einzufügen:
{{#each events}}
<p>{{this.name}}</p>
{{#unless @last}}, {{/unless}}
{{/each}}
with
Die with-Funktion wechselt den aktuellen Kontext auf ein Sub-Objekt — innerhalb des Blocks beziehen sich Platzhalter ohne Pfad direkt auf dieses Sub-Objekt. Spart wiederholte Pfad-Präfixe.
Beispiel
{{#with this.locations.[0].address}}
<p>{{street}}<br/>{{zip}} {{city}}</p>
{{/with}}
Optional kann mit as |alias| ein Alias gesetzt werden, der auch in tieferen Schleifen weiter ansprechbar bleibt:
{{#with (contactByRole this.contacts "Mieter") as |mieter|}}
<p>{{mieter.displayName}}</p>
<p>{{mieter.email}}</p>
{{/with}}
lookup
Funktion, um per Variable auf ein Property zuzugreifen — nützlich, wenn der Schlüssel zur Render-Zeit nicht statisch ist. Verlangt zwei Parameter:
- Ein Objekt, z.B. einen Kontakt
- den Namen des Elements (statisch oder als Variable)
Beispiel
{{ lookup (contactByRole this.contacts "Mieter") "displayName" }}
Block-Parameter (@first, @last, @index, @key, @root)
Innerhalb eines {{#each}}- oder {{#with}}-Blocks stehen automatisch zusätzliche Spezial-Variablen zur Verfügung. Sie werden mit einem führenden @ referenziert.
| Variable | Bedeutung | Verfügbar in |
|---|---|---|
@first | true bei der ersten Iteration | each |
@last | true bei der letzten Iteration | each |
@index | Numerischer Index (0-basiert) | each (über Arrays) |
@key | Schlüssel des aktuellen Eintrags | each (über Objekte) |
@root | Referenz auf den Root-Kontext (oberste Ebene der Input-Daten) | überall |
Beispiel: @first für Trenner zwischen Events
{{#each events}}
{{#unless @first}}
<hr/>
{{/unless}}
<h1>{{this.name}}</h1>
{{/each}}
Beispiel: @root für Zugriff auf baseData aus tiefen Schleifen
{{#each events}}
<p>Veranstalter: {{@root.baseData.name}}</p>
<h1>{{this.name}}</h1>
{{/each}}
Custom
formatDate
Funktion um Datum und Uhrzeit spezifisch auszugeben.
Die Funktion verlangt 2 Parameter:
- das Datum (Date ISO)
- das Datumsformat (Die Optionen sind hier gelistet)
Beispiel
{{ formatDate this.start "d.m.Y" }}
=> Ausgabe: deutsches Datum, z.B. 03.08.2022
{{ formatDate this.start "D d.m.Y" }}
=> Ausgabe: deutsches Datum mit Wochentag,
z.B. Mi. 03.08.2022
{{ formatDate this.start "H:i" }}
=> Ausgabe: Uhrzeit, z.B. 20:00
{{ formatDate this.start "d.m.Y H:i" }}
=> Ausgabe: Datum mit Uhrzeit, z.B. 20.11.2021 20:00
formatEuro
Funktion um Preis (in Cent) formatiert in Euro darzustellen.
Die Funktion verlangt 1 Parameter:
- Preis (in Cent)
Beispiel
{{ formatEuro this.netPrice }}
=> Ausgabe: 300,00 €
formatCurrency
Funktion um Preis formatiert in einer Währung darzustellen.
Im Moment wird nur EUR | USD | CHF | GBP unterstützt.
Die Funktion verlangt 2 Parameter:
- Preis (in Cent/Rappen/Pence)
- Währung (EUR/USD/CHF/GBP)
Beispiel
{{ formatCurrency this.netPrice "CHF" }}
=> Ausgabe: 300,00 CHF
formatNumberDE
Funktion um eine Zahl in ein deutsches Zahlenformat umzuwandeln
Die Funktion verlangt 1 Parameter:
- Zahl
Beispiel
{{ formatNumberDE countSeats }}
=> Ausgabe: 1.000
contactByRole
Funktion, um einen Kontakt aus einem Array anhand seiner Rolle zu extrahieren.
Die Funktion verlangt 2 Parameter:
- Ein Array mit Kontakten
- Den Namen der Rolle
Beispiel
{{#each (contactByRole this.contacts "Veranstalter:in")}}
{{@key}}: {{this}}
{{/each}}
=> Ausgabe
role: Veranstalter:in
displayName: ABC Firma
email: info@abc.de
phone: +030 8877665
{{ lookup (contactByRole this.contacts "Veranstalter:in") "displayName" }}
=> Ausgabe: ABC Firma
scheduleValueByName
Funktion, um einen Schedule-Eintrag anhand des Namens zu extrahieren.
Die Funktion hat 3 Parameter:
- Den Schedule
- Den Namen des Schedule-Eintrags
- Datum einblenden (optional): default false, auf true setzen um Datum und Uhrzeit anzuzeigen
Beispiel
{{ scheduleValueByName this.schedule "Curfew" }}
=> Ausgabe
22:00
{{ scheduleValueByName this.schedule "Curfew" true }}
=> Ausgabe
20.09.2023 22:00
linkByType
Funktion, um einen Eintrag der Links anhand des Typs zu extrahieren.
Die Funktion verlangt 2 Parameter:
- Die Links
- Den Typ des Eintrags (z.B. Web, Facebook)
{{ linkByType @root.baseData.links "Web" }}
eventInformationByName
Funktion, um einen Eintrag der Event Informationen anhand des Namens zu extrahieren.
Die Funktion verlangt 2 Parameter:
- Die Event Informationen
- Den Namen des Eintrags
{{ eventInformationByName this.eventInformations "Hotel" }}
md2html
Konvertiert Markdown to HTML
{{md2html memo}}
csvQuote
Tauchen in CSV Exporten Spalten mit Anführungszeichen auf, müssen diese Spalten mit dem Helper `csvQuote` escaped werden: z.B.
{{ start }};{{ csvQuote name }}
currentDate
Aktuellles Datum
Die Funktion verlangt 1 Parameter:
- das Datumsformat im Unicode Technical Standard
https://date-fns.org/v2.9.0/docs/format
{{ currentDate "P" }}
Beispiele
Einfaches Markdown Beispiel
{{#each events}}
<h1>{{this.name}}</h1>
<p>Type: {{this.eventType}}</p>
<p>Status: {{this.status}}</p>
<p>Datum: {{ formatDate this.start "P" }} | {{ formatDate this.start "p" }} Uhr </p>
<p>Name: {{this.displayNames.eventTitleWithArtistsAndShows}} </p>
<p>Ort: {{this.displayNames.locationsWithRooms}}</p>
{{/each}}
Einfaches CSV Beispiel
Status;Datum;Titel
{{#each events}}
{{this.status}};{{formatDate this.start "P"}};{{{this.name}}}
{{/each }}
VVK-Zahlen
<h1>Verkäufe</h1>
<p>Event: {{this.name}} | {{ formatDate this.start "P" }}</p>
<p>Location: {{this.displayNames.locationsWithRooms}}</p>
{{#if this.ticketing}}
<h2>Tickets</h2>
<table>
<thead>
<tr>
<th>VVK-Stelle</th>
<th>Kategory</th>
<th>Ticketpreis (brutto)</th>
<th>Verkauft</th>
<th>Kontingent</th>
<th>Gesamt (brutto)</th>
</tr>
</thead>
<tbody>
{{#each this.ticketing}}
<tr>
<td>{{ this.ticketOffice }}</td>
<td>{{ this.category }}</td>
<td>{{ formatEuro this.grossPrice }}</td>
<td>{{ this.soldTickets }}</td>
<td>{{ this.contingent }}</td>
<td>{{ formatEuro this.saleTotal }}</td>
</tr>
{{/each}}
<tr>
<th>Summe</th>
<th></th>
<th></th>
<th>{{ sumBy this.ticketing "soldTickets" }}</th>
<th>{{ sumBy this.ticketing "contingent" }}</th>
<th>{{ formatEuro (sumBy this.ticketing "saleTotal" ) }}</th>
</tr>
</tbody>
</table>
{{/if}}
Event-Informationen
Alle Event-Informationen außer “Gästeliste” auflisten.
<h2>Event Informationen</h2>
{{#each this.eventInformations}}
{{#unless (isEqual name "Gästeliste")}}
<h3>{{this.name}}</h3>
{{this.value}}
{{/unless}}
{{/each}}
Artist-Tabelle mit Bilder
<h2>Artists</h2>
<table class="artistTable">
<thead>
<tr>
<th>Pic</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{{#each this.artists}}
<tr>
<td><img src="{{ lookup this.images.0.sizes "640" }}" /></td>
<td>
<h3>{{ this.name }}</h3>
<p>{{ this.description }}</p>
</td>
</tr>
{{/each}}
</tbody>
</table>
Dateien verlinken
<h2>Dateien</h2>
{{#each this.files}}
<h3>{{this.name}}</h3>
<a href="{{this.path}}">{{this.path}}</a>
{{/each}}
Anmerkung: Nur als „öffentlich“ gekennzeichnete Dateien können im Export eingebunden werden. „Öffentlich“ bedeutet hierbei lediglich, dass eine Datei ohne Login verfügbar wird. Ohne die URL bzw. konkreter die zufallsgenerierte ID besteht aber trotzdem keine Zugriffsmöglichkeit. D.h. es können durchaus Dateien wie Tech Rider auf öffentlich gesetzt und somit in einen Export eingebunden werden.
Tipps
Bedingte Darstellung
Um eine Sektion nur anzuzeigen, wenn das entsprechende Feld gesetzt ist, kann sie mit einem #if Block-Helper umschlossen werden:
{{#if (eventInformationByName this.eventInformations "Hotel")}}
<h3>Hotel</h3>
{{eventInformationByName this.eventInformations "Hotel"}}
{{/if}}
Seitenumbruch
Für Seitenumbrüche in Paged-Layouts wird der Inhalt jedes Events in ein Wrapper-Div mit break-after: page eingeschlossen. Damit der Umbruch zusätzlich im Word-Export funktioniert, wird zwischen den Events ein <p> mit Inline-Style eingefügt — über {{#unless @first}} nur vor jedem Event ab dem zweiten:
<style>
.event-page {
break-after: page;
page-break-after: always;
}
.event-page:last-of-type {
break-after: auto;
page-break-after: auto;
}
</style>
{{#each events}}
{{#unless @first}}
<p style="page-break-before: always; margin: 0;"> </p>
{{/unless}}
<div class="event-page">
<h1>{{this.name}}</h1>
<!-- Inhalt des Events -->
</div>
{{/each}}
Das Pattern deckt alle drei Ausgabeformate ab: das break-after: page der CSS-Klasse greift in der Paged-Vorschau und im PDF, das Inline-<p> mit page-break-before sorgt für den Umbruch im Word-Export. Der CSS-Selektor :last-of-type verhindert eine leere Trailing-Seite nach dem letzten Event.
Wichtig: Der {{pagebreak}}-Helper funktioniert nur in Legacy-Layouts (ohne Paged.js). In Paged-Layouts muss stattdessen das oben gezeigte Pattern verwendet werden.
Styling
Styling von Exporten ist via CSS möglich. Globale Stile können ganz am Anfang einer Exportvorlage in einem Style-Tag angegeben werden:
<style>
...
</style>
Alternativ sind auch Inline-Stile auf z.B. einzelnen Absätzen oder Paragraphen anwendbar. Hierfür muss die entsprechende Passage dann allerdings in HTML statt Markdown geschrieben werden:
<p style="...">...</p>
Schriften
Folgende Schriftarten stehen zur Verfügung:

Beispiel: Überschriften mit Serifen, Absätze serifenlos
<style>
h1, h2, h3, h4 {
font-family: DM Serif Display;
}
body, p, th, td {
font-family: Ubuntu;
}
</style>
<h1>Globale Stile</h1>
<p>Ein Absatz</p>
<ul>
<li>eine</li>
<li>Liste</li>
</ul>
<h2>Tabelle</h2>
<table>
<thead>
<tr>
<th>Spalte A</th>
<th>Spalte B</th>
<th>Spalte C</th>
</tr>
</thead>
<tbody>
<tr>
<td>A1</td>
<td>B1</td>
<td>C1</td>
</tr>
<tr>
<td>A2</td>
<td>B2</td>
<td>C2</td>
</tr>
</tbody>
</table>

Beispiel: Einzelner Absatz in anderer Schriftart
Erster Absatz
<p style="font-family: Indie Flower">Zweiter Absatz</p>

Weitere Möglichkeiten
<p style="color: blue">blau</p>
<p style="color: blue; font-weight: bold;">blau & fettgedruckt</p>
<mark>highlighted</mark>
<p style="text-decoration: underline;">unterstrichen</p>

Formulare
Für eine Parametrisierung oder nachträgliche Bearbeitung von Exporten können HTML Formulare verwendet werden:
<form action="{{request.formAction}}" method="post">
Rechnungsnummer:
{{#if parameters.form.rechnungsnummer.[0]}}
{{parameters.form.rechnungsnummer.[0]}}
{{else}}
<input name="rechnungsnummer" />
<button>ok</button>
{{/if}}
</form>
Beim Aktualisieren wird der Export wieder in seinen Ausgangszustand versetzt. Sollte das nicht gewünscht sein, muss über den Export selbst aktualisiert werden:
<form action="{{request.formAction}}" method="post">
Rechnungsnummer:
{{#if parameters.form.rechnungsnummer.[0]}}
{{parameters.form.rechnungsnummer.[0]}}
<input name="rechnungsnummer" type="hidden" />
<button>aktualisieren</button>
{{else}}
<input name="rechnungsnummer" />
<button>ok</button>
{{/if}}
</form>
Custom Footer mit Seitenzahlen
Mit den Paged-Layouts (A4 Hochformat/Querformat Paged) können eigene Kopf- und Fußzeilen mit automatischen Seitenzahlen erstellt werden. Dafür wird die CSS-Technik @page margin boxes in Kombination mit position: running() verwendet.
So funktioniert’s
Im <style>-Block des Templates werden @page-Regeln definiert, die festlegen, wo Kopf- und Fußzeilen auf jeder Seite erscheinen. Die Inhalte werden als HTML-Elemente im Template platziert und per position: running() den jeweiligen Seitenbereichen zugewiesen.
Verfügbare Seitenbereiche
| Bereich | Position |
|---|---|
@top-left | Kopfzeile links |
@top-center | Kopfzeile mitte |
@top-right | Kopfzeile rechts |
@bottom-left | Fußzeile links |
@bottom-center | Fußzeile mitte |
@bottom-right | Fußzeile rechts |
Seitenzahlen
Für Seitenzahlen stehen zwei Platzhalter-Elemente zur Verfügung:
<span class="cp-page-number"></span>– Aktuelle Seitennummer<span class="cp-total-pages"></span>– Gesamtanzahl Seiten
Beispiel-Template
Das folgende Beispiel zeigt einen Export mit Logo in der Kopfzeile rechts, Adresse in der Fußzeile links und Seitenzahlen in der Fußzeile rechts:
<style>
@page {
margin: 20mm;
@top-right {
content: element(headerRight);
vertical-align: middle;
}
@bottom-left {
content: element(footerLeft);
vertical-align: middle;
}
@bottom-right {
content: element(footerRight);
vertical-align: middle;
}
}
.running-header-right {
position: running(headerRight);
}
.running-header-right img {
height: 1.2cm;
width: auto;
}
.running-footer-left {
position: running(footerLeft);
font-size: 7pt;
color: gray;
}
.running-footer-right {
position: running(footerRight);
font-size: 7pt;
color: gray;
text-align: right;
}
</style>
<div class="running-header-right">
<img src="{{baseData.logo.url}}" />
</div>
<div class="running-footer-left">
{{baseData.name}} · {{baseData.street}},
{{baseData.zip}} {{baseData.city}}
</div>
<div class="running-footer-right">
Seite <span class="cp-page-number"></span>
von <span class="cp-total-pages"></span>
</div>
<h1>Mein Dokument</h1>
<p>Inhalt...</p>
Wichtig: Dieses Template muss mit einem Paged-Layout (A4 Hochformat (Paged) oder A4 Querformat (Paged)) verwendet werden. Mit einem regulären Layout werden die @page-Regeln und position: running() ignoriert.
Word-Export
HTML-Exporte können zusätzlich als Word-Datei (.docx) heruntergeladen werden.
Die Konvertierung übernimmt die freie Bibliothek LibreOffice, dessen HTML Import leider nur bedingt funktioniert, weshalb ein Export nicht 1:1 dargestellt werden.
Styling
CSS Styles greifen i.d.R. nur inline, so dass z.B. das Styling innerhalb von Fließtext in Benutzerfeldern oft nicht einwandfrei funktioniert.
Seitenumbrüche
LibreOffice respektiert page-break-before bzw. page-break-after nur, wenn der Style als Inline-style-Attribut auf einem <p>-Element steht. CSS aus einem <style>-Block wird beim Import ignoriert, ebenso Inline-Styles auf <div>-Elementen. Für Seitenumbrüche im Word-Export muss daher zwischen den Inhalten ein expliziter Trenner-Absatz eingefügt werden:
<p style="page-break-before: always; margin: 0;"> </p>
Für eine Liste von Events kombiniert mit dem Wrapper-Div-Pattern (siehe Seitenumbruch) ergibt sich:
{{#each events}}
{{#unless @first}}
<p style="page-break-before: always; margin: 0;"> </p>
{{/unless}}
<div class="event-page">
...
</div>
{{/each}}
Der {{#unless @first}}-Block sorgt dafür, dass der Trenner nur ab dem zweiten Event eingefügt wird — sonst würde der Word-Export mit einer leeren ersten Seite starten.
Was nicht funktioniert
Folgende Konstrukte sind im Paged-Layout (Browser-Vorschau, PDF) gängig, werden aber von LibreOffice beim Word-Import ignoriert:
page-break-*oderbreak-*in einem<style>-Block (egal ob über Klasse oder Element-Selektor)style="page-break-before: ..."auf<div>,<section>,<h1>oder anderen Block-Elementen außer<p>- Der
{{pagebreak}}-Helper (rendert ein<div>mit Inline-Style)
Weitere Hinweise
- @page-Regeln und Running Headers/Footers aus dem Paged-Layout (siehe oben) werden im Word-Export nicht abgebildet — Word kennt diese CSS-Mechaniken nicht.
- Seitenzahlen über
<span class="cp-page-number">erscheinen im Word-Export als leere Spans, da sie zur Render-Zeit von Paged.js gefüllt werden. - Externe Schriften über
@importwerden von LibreOffice nicht geladen — das Word-Dokument verwendet die jeweilige System-Standardschrift, sofern die im Template referenzierte Schrift nicht installiert ist.
