Initial Commit

This commit is contained in:
khaccount
2026-05-18 14:46:25 +02:00
parent 4f495177e4
commit b6d2142214
166 changed files with 13952 additions and 178 deletions

View File

@@ -0,0 +1,624 @@
# VITEC Content Blocks Setup — Step-by-Step
**Strategie:** Content Blocks (YAML-basiert, deklarativ) als Haupt-Pattern für alle VITEC Content Elements. `nb-headless-content-blocks` übernimmt automatisch das JSON-Mapping.
**Stack-Basis bei dir (bereits vorhanden):**
- `evomedien/vitec` — deine Extension
- Site Set `evomedien/vitecset`
- `friendsoftypo3/headless` (`headless: 1`)
- `nb-headless-content-blocks` (Bridge)
- `b13/container` + `itplusx/headless-container` (für Layout-Wrapper, optional)
---
## 0. Aufräumen (zwei kleine Issues vorab)
### 0.1 `nb-headless-content-blocks` ins Site Set migrieren
Aktuell steht die Extension nur in der **Site-Config**, nicht im **Site Set**. Das sollte konsistent sein, damit das Set in sich abgeschlossen ist.
**Datei:** `packages/vitec/Configuration/Sets/VitecSet/config.yaml` (oder wo dein Set liegt)
```yaml
name: evomedien/vitecset
label: VITEC Set
settings:
website:
background:
color: '#386492'
dependencies:
- typo3/fluid-styled-content
- friendsoftypo3/headless
- b13/container
- itplusx/headless-container
- nb-headless-content-blocks/headless-content-blocks # NEU
```
Dann aus `config/sites/<identifier>/config.yaml` die Zeile `- nb-headless-content-blocks/headless-content-blocks` wieder rausnehmen — sie kommt jetzt transitiv über dein Set.
### 0.2 Version-Mismatch fixen
- `composer.json` sagt `1.0.1`
- `ext_emconf.php` sagt `0.0.1`
Einfach beide auf den gleichen Wert bringen (Empfehlung: `1.0.0` und bei jedem Deployment hochzählen). Nicht dringend, aber sauber:
```php
// ext_emconf.php
'version' => '1.0.0',
'state' => 'stable', // 'alpha' wirkt im Produktivbetrieb komisch
```
```json
// composer.json
"version": "1.0.0"
```
---
## 1. Ordnerstruktur anlegen
Content Blocks leben in der Extension unter einer festen Pfad-Konvention:
```
packages/vitec/
├── ContentBlocks/
│ └── ContentElements/
│ ├── hero-section/ ← erster Content Block (Beispiel)
│ │ ├── config.yaml ← Feld-Definitionen
│ │ ├── assets/
│ │ │ └── icon.svg ← Icon für CE-Wizard
│ │ ├── language/
│ │ │ └── labels.xlf ← Übersetzungen
│ │ └── templates/
│ │ └── EditorPreview.html ← optional: Backend-Preview
│ ├── feature-teaser/
│ ├── cta-banner/
│ └── ...
├── Classes/
├── Configuration/
├── composer.json
└── ext_emconf.php
```
Ein Content Block = ein Ordner. Name = Ordnername (kleingeschrieben, mit Bindestrichen).
---
## 2. Erster Content Block: VITEC Hero Section
Nachgebaut nach einem typischen Template aus deinen Figma-Wireframes: Eyebrow + Heading + Subline + Body + CTA + Hero-Image + Background-Variant.
### 2.1 `config.yaml`
**Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/config.yaml`
```yaml
name: vitec/hero-section
group: vitec
prefixFields: true
prefixType: full
fields:
- identifier: eyebrow
type: Text
max: 50
- identifier: header
useExistingField: true
required: true
- identifier: subheader
useExistingField: true
- identifier: bodytext
useExistingField: true
enableRichtext: true
- identifier: cta
type: Link
allowedTypes:
- page
- url
- file
- email
- identifier: cta_label
type: Text
default: 'Learn more'
max: 30
- identifier: hero_image
type: File
minitems: 0
maxitems: 1
allowed: common-image-types
extendedPalette: true
- identifier: background_variant
type: Select
renderType: selectSingle
default: none
items:
- label: None
value: none
- label: Orange
value: orange
- label: Blue
value: blue
- label: Graphite
value: graphite
- label: Midnight
value: midnight
- identifier: show_logo_wall
type: Checkbox
default: 0
```
**Was passiert hier:**
- `name: vitec/hero-section` → TCA-Typ `vitec_hero_section`, Tabelle `tt_content`
- `group: vitec` → eigene Sektion im CE-Wizard (muss noch registriert werden — siehe 2.5)
- `useExistingField: true` → recycled TYPO3-Core-Felder (`header`, `subheader`, `bodytext`). Keine neuen DB-Spalten, konsistente Bedeutung
- `prefixFields: true` + `prefixType: full` → neue Felder bekommen `tx_vitec_hero_section_*` als DB-Prefix (vermeidet Kollisionen)
- **Keine TCA-Override-Datei nötig** — Content Blocks generiert das TCA automatisch
### 2.2 `language/labels.xlf`
Content Blocks erwartet XLF-Files nach einer festen Namens-Konvention. Jedes Feld bekommt automatisch einen Key `<vendor>.<element>.<field>.label`.
**Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/language/labels.xlf`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2">
<file source-language="en" datatype="plaintext" original="messages">
<body>
<!-- Element-Labels -->
<trans-unit id="title">
<source>VITEC · Hero Section</source>
</trans-unit>
<trans-unit id="description">
<source>Haupt-Hero mit Headline, CTA, Background-Variante</source>
</trans-unit>
<!-- Field-Labels -->
<trans-unit id="eyebrow.label">
<source>Eyebrow Text</source>
</trans-unit>
<trans-unit id="eyebrow.description">
<source>Kleiner Label-Text über der Headline</source>
</trans-unit>
<trans-unit id="cta.label">
<source>CTA Link</source>
</trans-unit>
<trans-unit id="cta_label.label">
<source>CTA Button Text</source>
</trans-unit>
<trans-unit id="hero_image.label">
<source>Hero Image</source>
</trans-unit>
<trans-unit id="background_variant.label">
<source>Background Variant</source>
</trans-unit>
<trans-unit id="show_logo_wall.label">
<source>Show Logo Wall below</source>
</trans-unit>
</body>
</file>
</xliff>
```
> Die Core-Felder (`header`, `subheader`, `bodytext`) brauchen keine Labels — sie erben die vom TYPO3-Core.
### 2.3 `assets/icon.svg`
Content Blocks erkennt Icons automatisch, wenn sie unter `assets/icon.svg` (oder `icon.png`) liegen.
**Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/assets/icon.svg`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F47937" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<path d="M3 9h18"/>
<circle cx="8" cy="15" r="2"/>
<path d="M13 15h5M13 18h3"/>
</svg>
```
Platzhalter — tausch später gegen die echten VITEC-Icons aus.
### 2.4 `templates/EditorPreview.html` (optional aber empfohlen)
Im **Headless-Mode** wird kein Frontend-Template gerendert. Aber für die Redakteure ist eine Backend-Preview sinnvoll, damit sie im Seiten-Modul sehen, was sie gerade anlegen.
**Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/templates/EditorPreview.html`
```html
<div style="padding: 1rem; border-left: 4px solid #F47937; background: #f8f8f8;">
<div style="font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; color: #666;">
VITEC · Hero Section — {data.background_variant}
</div>
<f:if condition="{data.eyebrow}">
<div style="font-size: 0.75rem; color: #F47937; margin-top: 0.5rem;">{data.eyebrow}</div>
</f:if>
<h3 style="margin: 0.25rem 0;">{data.header}</h3>
<f:if condition="{data.subheader}">
<div style="color: #555;">{data.subheader}</div>
</f:if>
<f:if condition="{data.cta_label}">
<div style="margin-top: 0.5rem;">
<span style="display: inline-block; padding: 0.25rem 0.75rem; background: #F47937; color: white; border-radius: 3px; font-size: 0.8rem;">
{data.cta_label} →
</span>
</div>
</f:if>
</div>
```
### 2.5 Backend-Gruppe "VITEC" registrieren
Damit `group: vitec` aus der YAML eine saubere Sektion im CE-Wizard wird, muss die Gruppe einmal registriert sein:
**Datei:** `packages/vitec/Configuration/page.tsconfig` (deine existierende Datei — aktuell leer)
```tsconfig
mod.wizards.newContentElement.wizardItems.vitec {
header = VITEC
show = *
elements {
}
}
```
Das reicht — Content Blocks hängt die einzelnen Elemente dann automatisch unter `elements` ein.
---
## 3. Installation & Test
### 3.1 Caches und TCA flushen
Nach jeder Content-Block-Änderung:
```bash
vendor/bin/typo3 cache:flush
```
Oder im Backend: Admin Tools → Maintenance → Flush all caches.
### 3.2 Datenbank-Schema updaten
Content Blocks erstellt automatisch neue Spalten (z.B. `tx_vitec_hero_section_eyebrow`, `tx_vitec_hero_section_background_variant` etc.):
```bash
vendor/bin/typo3 database:updateschema
```
Oder Backend: Admin Tools → Maintenance → Analyze Database Structure → Run.
### 3.3 Im Backend anlegen
1. Seite im Seitenbaum öffnen → Page-Modul
2. "Neues Inhaltselement" → Tab **VITEC** → "VITEC · Hero Section"
3. Felder ausfüllen (Eyebrow, Heading, CTA, Image, Background-Variant "orange")
4. Speichern
### 3.4 JSON prüfen
```bash
curl -s https://dev.vitec.com/testseite | jq '.content.colPos0[] | select(.type == "vitec_hero_section")'
```
**Erwarteter JSON-Output** (durch `nb-headless-content-blocks` automatisch erzeugt):
```json
{
"id": 42,
"type": "vitec_hero_section",
"colPos": 0,
"categories": "",
"appearance": {
"layout": "default",
"frameClass": "default",
"spaceBefore": "",
"spaceAfter": ""
},
"content": {
"header": "Enterprise Video Solutions",
"subheader": "Reliable. Scalable. Proven.",
"bodytext": "<p>VITEC delivers mission-critical video...</p>",
"tx_vitec_hero_section_eyebrow": "Why VITEC",
"tx_vitec_hero_section_cta": {
"href": "/success-stories",
"target": null,
"class": null,
"title": null,
"linkText": "t3://page?uid=15",
"additionalAttributes": []
},
"tx_vitec_hero_section_cta_label": "Learn more",
"tx_vitec_hero_section_hero_image": [
{
"publicUrl": "https://dev.vitec.com/fileadmin/.../hero.png",
"properties": { ... }
}
],
"tx_vitec_hero_section_background_variant": "orange",
"tx_vitec_hero_section_show_logo_wall": false
}
}
```
Struktur passt zu deinem existierenden `textpic`-Pattern — `content.*` mit allen Feldern nebeneinander. ✅
---
## 4. JSON-Schema aufräumen (Field-Namen kürzen)
Du hast sicher gemerkt: die Feldnamen im JSON sind lang (`tx_vitec_hero_section_eyebrow`). Das kommt vom `prefixFields: true` — nötig für DB-Konsistenz, aber unschön fürs Frontend.
**Lösung:** Via `nb-headless-content-blocks` EventListener die Keys umbenennen.
### 4.1 EventListener anlegen
**Datei:** `packages/vitec/Classes/EventListener/NormalizeContentBlockKeys.php`
```php
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\EventListener;
use Netzbewegung\NbHeadlessContentBlocks\Event\ModifyArrayRecursiveToArrayEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vitec/normalize-content-block-keys')]
final class NormalizeContentBlockKeys
{
public function __invoke(ModifyArrayRecursiveToArrayEvent $event): void
{
$key = $event->getKey();
// Strip prefix "tx_vitec_<element>_" from field names
// e.g. "tx_vitec_hero_section_eyebrow" → "eyebrow"
if (preg_match('/^tx_vitec_[a-z_]+?_([a-z_]+)$/', $key, $matches)) {
$event->setKey($matches[1]);
}
}
}
```
### 4.2 Services.yaml
**Datei:** `packages/vitec/Configuration/Services.yaml` (anlegen falls nicht vorhanden)
```yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Evomedien\Vitec\:
resource: '../Classes/*'
exclude: '../Classes/Domain/Model/*'
```
> `AsEventListener` Attribute + autoconfigure = Event-Listener wird automatisch registriert. Keine weitere Konfiguration nötig.
### 4.3 Resultat nach Cache-Flush
```json
{
"type": "vitec_hero_section",
"content": {
"header": "Enterprise Video Solutions",
"eyebrow": "Why VITEC",
"cta": { ... },
"cta_label": "Learn more",
"hero_image": [ ... ],
"background_variant": "orange",
"show_logo_wall": false
}
}
```
Saubere, lesbare Keys — React-Kollege glücklich.
---
## 5. Pattern für weitere Content Blocks
Für jeden neuen Content Block brauchst du:
```
packages/vitec/ContentBlocks/ContentElements/<name>/
├── config.yaml ← Felder definieren
├── language/labels.xlf ← Labels übersetzen
├── assets/icon.svg ← Icon
└── templates/EditorPreview.html ← optional
```
Dann:
```bash
vendor/bin/typo3 database:updateschema
vendor/bin/typo3 cache:flush
```
Fertig. Keine PHP-Boilerplate, keine TypoScript-Overrides, kein TCA-Gefummel.
### Template-Vorschlag für deine 20 Figma-Templates
Nummerierung/Naming-Vorschlag an den Figma-Templates entlang:
| # | Name | Content Block |
|---|---|---|
| 01 | Hero | `vitec/hero-section` |
| 02 | Feature-Teaser Grid | `vitec/feature-teaser-grid` |
| 03 | CTA Banner | `vitec/cta-banner` |
| 04 | Quote / Testimonial | `vitec/testimonial` |
| 05 | Logo Wall | `vitec/logo-wall` |
| 06 | Product Card Grid | `vitec/product-card-grid` |
| ... | ... | ... |
| 16 | Content Page V1 | `vitec/content-page-v1` |
Wenn du einen Block gebaut hast, ist jeder weitere 10-20 Minuten Arbeit.
---
## 6. Wann brauchst du trotzdem `b13/container`?
**Antwort:** Für echte Layout-Wrapper mit nested Content Elements. Beispiele:
- **2-Spalten-Section:** Ein Container, in dessen linker Spalte ein `hero-section` + rechts ein `testimonial` liegt
- **Tabs / Accordion:** ein Tab-Container mit mehreren Content Blocks je Tab
- **Grid mit freier CE-Wahl:** 3-column-grid, wo der Redakteur pro Spalte frei wählt
Für diese Fälle:
1. `b13/container` Container registrieren (wie in der vorherigen Anleitung beschrieben, per PHP TCA-Override)
2. `itplusx/headless-container` mappt ihn automatisch ins JSON
3. Die Kinder sind dann Content Blocks → das Pattern spielt sauber zusammen
Laut Doku von `nb-headless-content-blocks`: **"Support for EXT:container"** ist eingebaut — die zwei Extensions beißen sich nicht.
---
## 7. Advanced: Sammlungen (Collections)
Für wiederkehrende Items (z.B. 3 Teaser-Cards in einem Grid) gibt es den `Collection`-Type:
```yaml
name: vitec/feature-teaser-grid
group: vitec
fields:
- identifier: header
useExistingField: true
- identifier: teasers
type: Collection
minitems: 1
maxitems: 6
fields:
- identifier: icon
type: File
maxitems: 1
allowed: common-image-types
- identifier: title
type: Text
required: true
- identifier: description
type: Textarea
- identifier: link
type: Link
```
Im JSON kommt das dann als Array raus:
```json
{
"type": "vitec_feature_teaser_grid",
"content": {
"header": "Our Solutions",
"teasers": [
{ "title": "...", "description": "...", "icon": [...], "link": {...} },
{ "title": "...", "description": "...", "icon": [...], "link": {...} }
]
}
}
```
---
## 8. Cheatsheet: Field-Types
| YAML `type` | Zweck | JSON-Output-Typ |
|---|---|---|
| `Text` | Einzeiliger Text | string |
| `Textarea` | Mehrzeilig; mit `enableRichtext: true` → RTE | string (HTML bei RTE) |
| `Number` | int/float | number |
| `Checkbox` | Boolean | boolean |
| `Select` `renderType: selectSingle` | Dropdown | string (value) |
| `Select` `renderType: selectMultipleSideBySide` | Multi-Select | string (comma-sep) |
| `Radio` | Radio-Button-Gruppe | string |
| `Link` | TYPO3-Link (Page/URL/File/Email) | object (`href`, `target`, `linkText`…) |
| `File` | File-Reference | array of file-objects |
| `Color` | Color-Picker | string (hex) |
| `DateTime` | Datum/Zeit | string (ISO) |
| `Collection` | Wiederholbare Feldgruppen | array of objects |
| `Category` | TYPO3-Kategorien | array |
| `Relation` | Referenz zu anderen Records | array |
---
## 9. Fehlerbild-Cheatsheet
| Symptom | Ursache | Fix |
|---|---|---|
| Content Block erscheint nicht im CE-Wizard | Cache oder Gruppe nicht registriert | `cache:flush`, dann Backend-User-Session refresh |
| Spalten fehlen in DB (`column not found`) | `database:updateschema` nicht ausgeführt | `vendor/bin/typo3 database:updateschema` |
| Feld-Label bleibt englisch/identifier | XLF-Key-Konvention falsch | Key muss exakt `<identifier>.label` heißen (ohne Vendor-Prefix) |
| JSON enthält veraltete Struktur | `nb-headless-content-blocks` nicht geladen oder Site-Set greift nicht | TypoScript-Analyzer prüfen |
| EventListener greift nicht | `Services.yaml` nicht geladen oder `autoconfigure: false` | Services.yaml prüfen, Cache flush |
| Icon wird nicht angezeigt | falsche Position oder SVG-Fehler | Pfad muss `assets/icon.svg` relativ zum CB-Ordner sein |
| "Type does not exist" nach Rename | TCA-Cache veraltet | `cache:flush --group=system` |
---
## 10. Quickstart
```bash
# 1. Aufräumen (einmalig)
# - nb-headless-content-blocks ins Site Set migrieren
# - Version in ext_emconf.php / composer.json angleichen
# 2. Ordnerstruktur für ersten Block
mkdir -p packages/vitec/ContentBlocks/ContentElements/hero-section/{assets,language,templates}
# 3. Files anlegen:
# - config.yaml
# - language/labels.xlf
# - assets/icon.svg
# - templates/EditorPreview.html (optional)
# 4. DB + Cache
vendor/bin/typo3 database:updateschema
vendor/bin/typo3 cache:flush
# 5. Backend: CE anlegen, speichern
# 6. Test
curl -s https://dev.vitec.com/testseite | jq '.content.colPos0'
```
---
## 11. Nächste Schritte
Nachdem der erste Content Block läuft:
1. **EventListener** für saubere Keys einbauen (Schritt 4)
2. Die 20 Templates aus Figma durchgehen → pro Template ein Content Block
3. Wenn Layouts mit nested CEs gebraucht werden → `b13/container` dazupacken
4. **Custom Records** (`tx_vitec_market`, `_solution`, `_product`, `_story`) — Content Blocks kann auch Record Types. Siehe YAML reference → RecordTypes
5. **m:n Relationen** zwischen Records — via `Relation`-Field-Type (mit `allowed` und `maxitems`)
Sobald der Hero-Section-Block JSON liefert, melden — dann bauen wir zusammen den ersten Record Type (`tx_vitec_market`) und die bidirektionalen Relationen.
---
## Referenzen
- **Content Blocks:** https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/
- **Field Types:** https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/YamlReference/FieldTypes/Index.html
- **nb-headless-content-blocks:** https://github.com/Netzbewegung-Backend/nb_headless_content_blocks
- **Headless Docs:** https://docs.typo3.org/p/friendsoftypo3/headless/main/en-us/
- **Beispiel-Repo (Content Blocks):** https://github.com/friendsoftypo3/content-blocks/tree/main/Build/content_blocks_examples