Technische Artikel ohne Block-Editor: Markdown, Git und WordPress-Entwürfe

AutomationContinuous Integration (CI) / Continuous Delivery (CD)

Wenn du diesen Blog liest, siehst du am Ende nur eine normale WordPress-Seite: Titel, Kategorien, Leselayout, manchmal Code mit Syntax-Farben. Was du nicht siehst, ist wie der Text entstanden. Für die meisten Beiträge gilt weiterhin: Autor öffnet WordPress im Browser, schreibt im Block-Editor (Gutenberg), setzt ein Titelbild, klickt auf Veröffentlichen.

Für technische Field Notes und alles, was ich mit Git, Terminal oder KI-Assistenten vorbereite, nutze ich einen zweiten Weg: Der Artikel lebt zuerst als Textdatei auf meinem Rechner (Markdown), wird per Skript als Entwurf nach WordPress geschickt, und erst dort schaue ich noch einmal drüber und veröffentliche manuell. Dieser Beitrag erklärt diesen Weg von Grund auf — auch wenn du WordPress nur als Leser kennst oder noch nie von „REST API“ oder „Frontmatter“ gehört hast.

Kurzfassung

  1. Schreiben passiert in einer Datei (.md), nicht zwingend im WordPress-Editor.
  2. Ein PowerShell-Skript übersetzt den Text in das Format, das WordPress intern für den Block-Editor braucht, und legt einen Entwurf an oder aktualisiert ihn.
  3. Veröffentlichen, Titelbild und letzte Korrekturen mache ich bewusst selbst im WordPress-Backend — nichts geht ohne meinen Klick live.

Der Rest des Textes geht in die Tiefe: Begriffe, Motivation, Ablauf, Grenzen und (am Ende) die technische Karte für Entwickler.

Was WordPress hier überhaupt ist

WordPress ist die Software hinter fabricioruch.ch: Sie speichert Beiträge, Kategorien, Tags, Bilder und liefert die Seiten aus. Man spricht oft von einem CMS (Content-Management-System) — einer Verwaltungsoberfläche für Inhalte, ohne dass jeder Artikel handcodiertes HTML sein muss.

Für Autoren gibt es das Backend (wp-admin): Login, Beitragsliste, Editor. Für Besucher gibt es das Theme — das Aussehen (Typografie, Abstände, Code-Blöcke, Kategorie-Navigation). Mein Theme liegt im gleichen Git-Repository wie die Skripte; der Blog bleibt also „WordPress“, nicht eine komplett separate Website.

Wie ein Artikel „normalerweise“ entsteht

Der Block-Editor (seit WordPress 5 auch Gutenberg genannt) zeigt den Text als Blöcke: Absatz, Überschrift, Liste, Code, Bild, Tabelle, … Jeder Block hat Einstellungen in der Seitenleiste. Das ist flexibel und für viele Autoren genau richtig.

Typischer Ablauf:

  1. Im Backend „Beitrag erstellen“.
  2. Titel und Blöcke im Editor bauen.
  3. Kategorie und Schlagwörter wählen.
  4. Vorschau ansehen.
  5. Veröffentlichen oder als Entwurf speichern.

Entwurf (draft) bedeutet: Der Beitrag ist gespeichert, aber für normale Besucher noch nicht sichtbar (nur mit spezieller Vorschau oder als eingeloggter Autor). Genau diesen Status setzt mein Import — bewusst, damit nichts versehentlich live geht.

Was ich anders mache — und warum

Ich schreibe viele technische Texte wie Quellcode: in Git versioniert, mit klaren Zeilenänderungen (git diff), oft zusammen mit Theme- oder Skript-Änderungen. Dafür eignet sich Markdown besser als Klicken im Editor.

Markdown ist eine einfache Schreibweise für strukturierten Text: ## für Zwischenüberschriften, **fett**, Listen mit -, Code in Zeilen mit drei Backticks, Tabellen mit |. Die Datei bleibt lesbar als Plain Text — in VS Code, Cursor oder jedem Editor.

Was ich brauche Block-Editor allein Markdown-Datei + Import
Änderungen nachvollziehen WordPress-Revisionen; schwer mit Code zu vergleichen Git zeigt jede Zeile
Text von KI / Skript erzeugen Copy-Paste, Format geht kaputt Agent schreibt eine Datei
Gleichen Artikel erneut hochladen Manuell im Editor nachziehen Gleicher Dateiname/Slug → Update
Lesen wie auf der Website Vorschau im Backend Entwurf im echten Theme nach Import
Kategorien & Tags Per Klick In der Datei als Metadaten

Das ist nicht „WordPress abschaffen“. Besucher merken nichts. Es ist nur ein anderer Eingang für die Artikel, die ich schon im Repository denke.

Content-as-Code heißt hier schlicht: Der Entwurf ist eine Datei im Projekt — ähnlich wie Konfiguration oder Tests — nicht nur ein Eintrag in der Datenbank. Live-Schalten bleibt eine bewusste menschliche Entscheidung im Backend.

Begriffe, die im Text vorkommen

Begriff Kurz erklärt
Slug Der URL-Teil des Beitrags, z. B. markdown-entwuerfe-wordpress-ohne-block-editor. Sollte stabil bleiben, wenn du denselben Artikel aktualisierst.
Frontmatter Metadaten oben in der Datei zwischen --- Zeilen: Titel, Slug, Kategorien, Tags. Kein Markdown, sondern YAML (eine listenfreundliche Konfigurationssyntax).
Kategorie Grobe Einordnung im Blog (hierarchisch, z. B. System Engineering → Automation). Im Theme zählt die erste Kategorie in der Liste als „Haupt“-Kategorie.
Tag Feineres Schlagwort (z. B. wordpress, field-note). Keine >-Pfade wie bei Kategorien.
Taxonomie Sammelwort für Kategorien und Tags — die „Schubladen“ des Blogs.
REST API Eine HTTP-Schnittstelle, mit der Programme Beiträge lesen/schreiben können — statt nur der Maus im Browser.
Application Password Ein von WordPress erzeugtes App-Passwort (nicht dein Login-Passwort), nur für Skripte. Liegt lokal in einer Datei, die nicht ins Git kommt.
Gutenberg / Blöcke WordPress speichert Inhalt intern als Blöcke mit speziellen HTML-Kommentaren. Der Import muss dieses Format treffen, sonst warnt der Editor.
Dry-Run Skript läuft durch, schreibt aber nichts — du siehst nur, was passieren würde.
Preflight Automatische Checks im Repository (Tests, Theme-Regeln), bevor etwas deployt wird.

Wenn du nur lesen willst, reichen Einleitung, Begriffe, Ablauf und „Was bleibt manuell“ — der Abschnitt Technische Vertiefung ist für Menschen, die selbst am Repository arbeiten.

Von der Datei zum WordPress-Entwurf (Schritt für Schritt)

Stell dir drei Stationen vor:

  [1] Textdatei auf dem PC          [2] Skript auf dem PC           [3] WordPress (Server)
      content/drafts/mein-post.md  →  import-post-draft.ps1      →   Beitrag als Entwurf
      Titel, Kategorien, Markdown     übersetzen & prüfen            im Backend sichtbar

Station 1: Die Datei anlegen

Die Arbeitskopie liegt unter content/drafts/ (bewusst nicht im öffentlichen Git — Entwürfe sind privat auf der Festplatte). Oben steht das Frontmatter, darunter der Artikeltext in Markdown.

Ein minimales Beispiel (nur zur Illustration):

---
title: "Kurznotiz: REST-Route"
slug: kurznotiz-rest-route
comments: closed
categories:
  - "System Engineering > Automation"
tags:
  - field-note
  - german
---

## Ein Abschnitt

Ein Absatz Text. Keine Überschrift mit einer einzelnen `#` Zeile — der **Titel** oben im Frontmatter ist die einzige H1; das Theme zeigt sie auf der Seite.

Kommentare sind bei mir standardmäßig geschlossen (comments: closed), außer ich schreibe explizit open.

Station 2: Skript ausführen (erst ohne Schreiben)

Im Projektordner (PowerShell):

pwsh ./scripts/content/cmd/import-post-draft.ps1 -Path content/drafts/mein-post.md

Ohne Zusatzflag ist das ein Dry-Run: Du siehst u. a. welche Kategorie-IDs gesetzt würden, wie lang der Inhalt ist, ob ein neuer Entwurf oder ein Update ansteht. Nichts wird an WordPress geändert.

Wenn die Ausgabe passt, kommt der echte Upload (Produktion nur mit extra Bestätigung):

pwsh ./scripts/content/cmd/import-post-draft.ps1 `
  -Path content/drafts/mein-post.md `
  -Apply -AllowProduction

-AllowProduction ist eine Sicherheitsleine: Das Skript verweigert sonst Schreibzugriffe auf die Live-Website — damit ein Tippfehler im Terminal nicht sofort den echten Blog überschreibt.

Lokal testen (WordPress in Docker auf dem Rechner) geht mit anderer URL und anderer Credential-Datei — siehe docs/development/local-development.md.

Station 3: Im Backend fertig machen

Nach erfolgreichem Import:

  1. Im WordPress-Backend den Entwurf öffnen.
  2. Prüfen, ob der Block-Editor keine Meldung „ungültiger Block“ zeigt.
  3. Beitragsbild (Featured Image) setzen — das macht das Skript (noch) nicht.
  4. Vorschau im echten Theme: Code, Tabellen, Fußnoten.
  5. Veröffentlichen — oder als Entwurf lassen.

So bleibt die redaktionelle Kontrolle bei mir; das Skript ist Transport und Formatierung, nicht der rote Knopf „live“.

Was das Skript intern tut (ohne Code lesen zu müssen)

In groben Worten:

  1. Datei lesen und Frontmatter von Body trennen.
  2. Prüfen (z. B. Pflichtfelder, kaputte Fußnoten, gefährliche Link-Arten).
  3. Markdown in Block-Markup umwandeln — nicht nur „HTML“, sondern das spezielle Gutenberg-Format mit Kommentaren wie <!-- wp:paragraph -->.
  4. Kategorien und Tags auflösen (existieren sie? sonst optional anlegen — mit denselben Regeln wie andere Taxonomie-Skripte im Repo).
  5. Per REST API den Beitrag als Entwurf anlegen oder aktualisieren.
  6. Merken, welche WordPress-Post-ID zu welchem Slug gehört (lokale Datei .import-log.json, gitignored), damit ein erneuter Import denselben Beitrag aktualisiert statt einen Duplikat anzulegen.

Wenn ein Beitrag schon veröffentlicht war, wird er beim Update nicht automatisch wieder auf „Entwurf“ zurückgestellt — das Skript ändert den Status dann nicht.

Was bewusst nicht automatisiert ist

Thema Warum manuell
Veröffentlichen Kein versehentliches Live-Schalten durch Skript oder CI
Beitragsbild Bildauswahl und Rechte sind redaktionell
Letzte Feinheiten im Editor Manchmal ein Wort, ein Block-Abstand — schneller im Auge als in Regeln
Vollständiges „jedes Markdown der Welt“ Nur die Formate, die der Konverter und das Theme sauber darstellen

Plugins vom Typ „Markdown einfügen und fertig“ habe ich nicht als Kernlösung gewählt: Ich will Regeln, Tests und Agent-Briefings im gleichen Repository wie Theme und Skripte — nicht eine Blackbox im WordPress-Plugin-Verzeichnis.

Warum der Block-Editor trotzdem wichtig bleibt

WordPress erwartet für viele Inhalte Blöcke, nicht beliebiges HTML in einem Rutsch. Wenn der Import nur <p>Hello</p> schreibt, ohne die Block-Kommentare, kann der Editor warnen oder alles in einen „klassischen“ Block packen — unhandlich für spätere Bearbeitung.

Deshalb baut die Pipeline editor-taugliche Blöcke: Absätze, Überschriften, Listen, Code, Tabellen, Bilder, Trennlinien, Zitate. Das kostet Wartung (WordPress-Updates, Serialisierung), aber der Entwurf sieht im Backend aus wie ein normal geschriebener Post.

Praxis-Tipp für Neugierige: Code-Blöcke werden absichtlich schlicht importiert (ohne extra Sprach-Attribut im Block), weil mein Editor sonst „ungültiger Inhalt“ meldete; die Syntax-Farben auf der Website kommen vom Theme (code-highlighting.js), nicht zwingend vom Editor.

Workflow mit KI (Cursor o. Ä.) — in Alltagssprache

  1. Ich gebe Kontext aus einer Briefing-Datei im Repo (Sprache, Art des Posts, Kategorie-Vorschläge).
  2. Der Assistent erzeugt oder erweitert eine .md-Datei unter content/drafts/.
  3. Ich lese die Änderung wie bei Code (Diff), korrigiere Frontmatter (z. B. erste Kategorie = Hauptkategorie).
  4. Dry-Run, dann Apply.
  5. Im Backend: Bild, Vorschau, Publish.

Die KI ersetzt nicht die Verantwortung fürs Veröffentlichen; sie beschleunigt das erste und zweite Entwerfen in einem Format, das das Skript versteht.

Typische Probleme (auch ohne Entwickler-Jargon)

Was du siehst Was es meist bedeutet
Skript bricht mit „Footnote“ ab Im Text steht eine Fußnote ohne Erklärung am Ende der Datei
„Refusing … production“ -AllowProduction fehlt beim echten Upload
„Slug already exists“ Es gibt schon einen Beitrag mit dieser URL; Import-Log oder -AdoptExistingSlug
Gelber Banner im Editor „ungültiger Block“ Format passt nicht zu WordPress — Pipeline anpassen oder Post im Editor bereinigen
Login schlägt fehl Application Password oder Benutzername in .env/wp prüfen (Login-Name, nicht Anzeigename)

Nach dem Anlegen neuer Kategorien oder Tags im Import lohnt sich im Repo noch ein Taxonomie-Audit — damit die Schubladen langfristig sauber bleiben.

Checkliste vor dem Veröffentlichen

  • Titel, Slug, Kategorien und Tags im Frontmatter stimmen (erste Kategorie = Hauptkategorie).
  • Dry-Run ohne Fehler.
  • -Apply ausgeführt, Erfolgsmeldung mit Post-ID.
  • Entwurf im Backend: keine Block-Fehler.
  • Beitragsbild gesetzt.
  • Vorschau auf der Website (Handy/Desktop, Code, Tabellen).
  • Veröffentlichen — oder Entwurf löschen, wenn es nur ein Test war.

Technische Vertiefung (Repository & Module)

Der folgende Teil richtet sich an Leser, die im Projekt fabricio-technical-journal mitarbeiten oder einen ähnlichen Aufbau planen.

Architektur

  content/drafts/post.md          scripts/content/
  (YAML + Markdown)                    │
        │                              ├─ Parse (YamlDotNet)
        │                              ├─ Validate (Regeln, Fußnoten, URLs)
        ▼                              ├─ Convert → <!-- wp:* --> …
  import-post-draft.ps1 ──────────────├─ Resolve Taxonomie (wp-taxonomy-apply)
        │                              ├─ Build REST payload
        │                              └─ Invoke-ImportPostDraft
        ▼
  .env/wp (Application Password)
        │
        ▼
  REST: index.php?rest_route=/wp/v2/posts
        │
        ▼
  Post status: draft  →  manuell: Preview, Bild, Publish

Ein gemeinsamer REST-Client (scripts/taxonomy/lib/wp-rest.ps1) dient Taxonomie-Skripten und Content-Import. Auf dieser Installation ist index.php?rest_route=… der zuverlässige Einstieg (nicht immer /wp-json/).

Ziele und Grenzen (v1)

Kann:

  • Markdown oder format: html (mit Sanitizer) importieren.
  • Kategorien als Pfad Root > … > Leaf; Tags als Slugs; erste Kategorie = primary im Theme.
  • Kommentare default closed.
  • Re-Import per Slug → Post-ID in content/drafts/.import-log.json.
  • Markdown: Überschriften ##–####, Fett/Kursiv, Links, Bilder, Listen, Tasks, Tabellen, Zitate, Trennlinie, Code-Fences, Fußnoten1.

Kann nicht (bewusst):

  • Auto-Publish oder Scheduling.
  • Featured Image per REST.
  • Jedes GFM-Feature der Welt.
  • Taxonomie-Politik ersetzen — nach neuen Terms Audits fahren.

Frontmatter (Referenz)

Feld Pflicht Hinweis
title ja Kein einzelnes # im Body
slug empfohlen Bei Umlauten explizit (entwuerfe)
categories ja Erste Zeile = primary
tags ja lowercase-mit-bindestrich
comments nein Default closed
excerpt nein Mehrzeilig mit `excerpt: \ ` möglich
format nein markdown oder html

Kategorien: nur Blatt der Hierarchie zuweisen, nicht jeden Vorfahren doppelt. Tags: keine >-Pfade.

Module unter scripts/content/

Modul Aufgabe
cmd/import-post-draft.ps1 CLI
lib/Invoke-ImportPostDraft.ps1 Orchestrierung
lib/Parse-DraftMarkdown.ps1 Frontmatter / Body
lib/Validate-DraftFrontmatter.ps1 Regeln, URLs
lib/Convert-*.ps1 Markdown → Blöcke
lib/Gutenberg-Blocks.ps1 Block-Helfer
lib/Build-WpPostPayload.ps1 draft nur bei neuem Post
lib/Import-DraftState.ps1 Import-Log
lib/Resolve-DraftTaxonomy.ps1 Kategorien/Tags
test/run-all.ps1 Tests; Teil von Theme-Preflight

Einmal pro Clone: pwsh ./scripts/content/cmd/restore-content-deps.ps1 (YamlDotNet).

Gutenberg-Details

Serialisierung muss zu Core-Blöcken passen: wp:paragraph, wp:code ohne störendes language-Attribut im Kommentar, wp:list / Tasks mit contains-task-list, Tabellen als wp:table, Fußnoten-Abschnitt oft als wp:html plus Theme-CSS (reading.css). Golden-Tests: Gutenberg-Serialization.Tests.ps1.

Sicherheit

Application Password in .env/wp, nie committen. -AllowProduction für Prod--Apply. Validierung blockiert javascript: / data: in Links. Live-Debug-Skripte nur mit CONTENT_IMPORT_REQUIRE_LIVE_REST=1.

Sonderfälle

Situation Aktion
Slug in WP, nicht im Log -AdoptExistingSlug oder Log-Eintrag
Payload ohne Schreiben -Apply -WhatIf
Text geändert, gleicher Slug Erneut -Apply

Breiter Block-Smoke-Test: scripts/content/examples/content-entwuerfe-markdown-import-smoke-test.md.

pwsh ./scripts/content/test/run-all.ps1
pwsh ./scripts/theme/preflight.ps1 -Strict

Fazit

Zwei Wege, ein Blog: Im Editor schreiben bleibt der Default. Markdown plus Import ist mein Werkzeug für technische Texte, die ich versionieren, automatisieren oder mit KI vorbereiten will — ohne WordPress als Leser-Erlebnis abzuschaffen. Wer nur mitlesen will, merkt höchstens, dass Field Notes oft sehr strukturiert wirken; wer selbst baut, kann im Repository nachziehen, was hier in Worte gefasst ist.

Geplante Erweiterungen (noch offen): Beitragsbild per REST, optional CI nur für Content-Tests — getrennt vom Theme-Deploy.

  1. Vertrag und Beispiele: docs/development/content-draft-format.md, scripts/content/README.md, KI: docs/development/ai-post-briefing.md. ↩

Mehr zu diesem Thema